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,176 @@
|
|
|
1
|
+
import Ajv2020Module from 'ajv/dist/2020.js';
|
|
2
|
+
import { loadSourceDocument } from '../source-document.js';
|
|
3
|
+
import { exportLikeC4, } from './likec4-export.js';
|
|
4
|
+
import likeC4ProjectSchema from '../../schema/yarramate-likec4-project.schema.json' with {
|
|
5
|
+
type: 'json'
|
|
6
|
+
};
|
|
7
|
+
const Ajv2020 = Ajv2020Module.default;
|
|
8
|
+
const validateLikeC4Project = new Ajv2020({ allErrors: true }).compile(likeC4ProjectSchema);
|
|
9
|
+
export const loadLikeC4ProjectDefinition = (source) => loadSourceDocument(source, validateLikeC4Project, 'LikeC4 project');
|
|
10
|
+
const quote = (value) => `'${value.replaceAll('\\', '\\\\').replaceAll("'", "\\'").replaceAll('\n', '\\n')}'`;
|
|
11
|
+
const unionProjection = (project, views) => ({
|
|
12
|
+
format: 'yarramate/projection-result/v1',
|
|
13
|
+
projection: `${project.id}@${project.version}`,
|
|
14
|
+
presentation: { title: project.title },
|
|
15
|
+
documents: [
|
|
16
|
+
...new Map(views.flatMap(({ prepared }) => prepared.projection.documents.map((document) => [
|
|
17
|
+
document.id,
|
|
18
|
+
document,
|
|
19
|
+
]))).values(),
|
|
20
|
+
].sort((left, right) => left.id.localeCompare(right.id)),
|
|
21
|
+
subjects: [
|
|
22
|
+
...new Map(views.flatMap(({ prepared }) => prepared.projection.subjects.map((subject) => [
|
|
23
|
+
subject.id,
|
|
24
|
+
subject,
|
|
25
|
+
]))).values(),
|
|
26
|
+
].sort((left, right) => left.id.localeCompare(right.id)),
|
|
27
|
+
claims: [
|
|
28
|
+
...new Map(views.flatMap(({ prepared }) => prepared.projection.claims.map((claim) => [claim.id, claim]))).values(),
|
|
29
|
+
].sort((left, right) => left.id.localeCompare(right.id)),
|
|
30
|
+
});
|
|
31
|
+
const viewBody = ({ id, prepared, dynamic, deployment, }) => {
|
|
32
|
+
const source = prepared.source;
|
|
33
|
+
const startToken = '\nviews {\n';
|
|
34
|
+
const start = source.indexOf(startToken);
|
|
35
|
+
const end = source.lastIndexOf('\n}\n');
|
|
36
|
+
const externalByNative = new Map(prepared.subjectMapping.mappings
|
|
37
|
+
.filter(({ type }) => type === 'concept')
|
|
38
|
+
.map(({ native, external }) => [native, external]));
|
|
39
|
+
const includedConcepts = prepared.projection.subjects
|
|
40
|
+
.filter(({ type }) => type === 'concept')
|
|
41
|
+
.flatMap(({ id }) => {
|
|
42
|
+
const external = externalByNative.get(id);
|
|
43
|
+
return external === undefined ? [] : [external];
|
|
44
|
+
})
|
|
45
|
+
.sort();
|
|
46
|
+
const includeRule = includedConcepts.length === 0
|
|
47
|
+
? ` include * where metadata.yarramateId is '__yarramate_no_match__'`
|
|
48
|
+
: ` include ${includedConcepts.join(', ')}`;
|
|
49
|
+
const relationshipRules = prepared.projection.subjects
|
|
50
|
+
.filter(({ type }) => type === 'relationship')
|
|
51
|
+
.map(({ id }) => ` include * -> * where metadata.yarramateId is '${id}'`)
|
|
52
|
+
.sort();
|
|
53
|
+
if (deployment !== undefined) {
|
|
54
|
+
const roots = deployment.nodes
|
|
55
|
+
.filter(({ parent }) => parent === undefined)
|
|
56
|
+
.map(({ id }) => `${id}.**`)
|
|
57
|
+
.sort();
|
|
58
|
+
const viewId = id ?? prepared.projection.projection.split('@')[0];
|
|
59
|
+
return [
|
|
60
|
+
` deployment view ${viewId} {`,
|
|
61
|
+
...(prepared.projection.presentation?.title === undefined
|
|
62
|
+
? []
|
|
63
|
+
: [` title ${quote(prepared.projection.presentation.title)}`]),
|
|
64
|
+
...(prepared.projection.presentation?.description === undefined
|
|
65
|
+
? []
|
|
66
|
+
: [
|
|
67
|
+
` description ${quote(prepared.projection.presentation.description)}`,
|
|
68
|
+
]),
|
|
69
|
+
` include ${roots.join(', ')}`,
|
|
70
|
+
' autoLayout LeftRight',
|
|
71
|
+
' }',
|
|
72
|
+
].join('\n');
|
|
73
|
+
}
|
|
74
|
+
if (dynamic !== undefined) {
|
|
75
|
+
const claimsById = new Map(prepared.projection.claims.map((claim) => [claim.id, claim]));
|
|
76
|
+
const lines = dynamic.steps.map((step) => {
|
|
77
|
+
const structural = claimsById.get(step.relationship);
|
|
78
|
+
const source = externalByNative.get(structural.subject);
|
|
79
|
+
const target = 'ref' in structural.object
|
|
80
|
+
? externalByNative.get(structural.object.ref)
|
|
81
|
+
: '';
|
|
82
|
+
const declaredTitle = prepared.projection.claims.find((claim) => claim.subject === step.relationship &&
|
|
83
|
+
claim.predicate === 'yarramate/relationship/name' &&
|
|
84
|
+
'value' in claim.object);
|
|
85
|
+
const title = step.title ??
|
|
86
|
+
(declaredTitle !== undefined && 'value' in declaredTitle.object
|
|
87
|
+
? declaredTitle.object.value
|
|
88
|
+
: undefined);
|
|
89
|
+
return ` ${source} -> ${target}${title === undefined ? '' : ` ${quote(title)}`}`;
|
|
90
|
+
});
|
|
91
|
+
const viewId = id ?? prepared.projection.projection.split('@')[0];
|
|
92
|
+
return [
|
|
93
|
+
` dynamic view ${viewId} {`,
|
|
94
|
+
...(prepared.projection.presentation?.title === undefined
|
|
95
|
+
? []
|
|
96
|
+
: [` title ${quote(prepared.projection.presentation.title)}`]),
|
|
97
|
+
...(prepared.projection.presentation?.description === undefined
|
|
98
|
+
? []
|
|
99
|
+
: [
|
|
100
|
+
` description ${quote(prepared.projection.presentation.description)}`,
|
|
101
|
+
]),
|
|
102
|
+
...lines,
|
|
103
|
+
' }',
|
|
104
|
+
].join('\n');
|
|
105
|
+
}
|
|
106
|
+
const membershipRules = [
|
|
107
|
+
includeRule,
|
|
108
|
+
' exclude * -> *',
|
|
109
|
+
...relationshipRules,
|
|
110
|
+
].join('\n');
|
|
111
|
+
return source
|
|
112
|
+
.slice(start + startToken.length, end)
|
|
113
|
+
.replace(/^ view [A-Za-z_][A-Za-z0-9_-]* \{/, ` view ${id ?? prepared.projection.projection.split('@')[0]} {`)
|
|
114
|
+
.replace(' include *', membershipRules);
|
|
115
|
+
};
|
|
116
|
+
const deploymentBody = ({ deployment, prepared, }) => {
|
|
117
|
+
if (deployment === undefined)
|
|
118
|
+
return undefined;
|
|
119
|
+
const externalByNative = new Map(prepared.subjectMapping.mappings
|
|
120
|
+
.filter(({ type }) => type === 'concept')
|
|
121
|
+
.map(({ native, external }) => [native, external]));
|
|
122
|
+
const childrenByParent = new Map();
|
|
123
|
+
for (const node of deployment.nodes) {
|
|
124
|
+
childrenByParent.set(node.parent, [
|
|
125
|
+
...(childrenByParent.get(node.parent) ?? []),
|
|
126
|
+
node,
|
|
127
|
+
]);
|
|
128
|
+
}
|
|
129
|
+
const instancesByNode = new Map();
|
|
130
|
+
for (const instance of deployment.instances) {
|
|
131
|
+
instancesByNode.set(instance.node, [
|
|
132
|
+
...(instancesByNode.get(instance.node) ?? []),
|
|
133
|
+
instance,
|
|
134
|
+
]);
|
|
135
|
+
}
|
|
136
|
+
const renderNode = (node, indentation) => [
|
|
137
|
+
`${indentation}${node.kind} ${node.id} ${quote(node.name)} {`,
|
|
138
|
+
...(childrenByParent.get(node.id) ?? [])
|
|
139
|
+
.slice()
|
|
140
|
+
.sort((left, right) => left.id.localeCompare(right.id))
|
|
141
|
+
.flatMap((child) => renderNode(child, `${indentation} `)),
|
|
142
|
+
...(instancesByNode.get(node.id) ?? [])
|
|
143
|
+
.slice()
|
|
144
|
+
.sort((left, right) => left.id.localeCompare(right.id))
|
|
145
|
+
.map((instance) => `${indentation} ${instance.id} = instanceOf ${externalByNative.get(instance.subject)}`),
|
|
146
|
+
`${indentation}}`,
|
|
147
|
+
];
|
|
148
|
+
return [
|
|
149
|
+
'deployment {',
|
|
150
|
+
...(childrenByParent.get(undefined) ?? [])
|
|
151
|
+
.slice()
|
|
152
|
+
.sort((left, right) => left.id.localeCompare(right.id))
|
|
153
|
+
.flatMap((node) => renderNode(node, ' ')),
|
|
154
|
+
'}',
|
|
155
|
+
].join('\n');
|
|
156
|
+
};
|
|
157
|
+
export function exportLikeC4Project(project, views) {
|
|
158
|
+
const first = views[0];
|
|
159
|
+
if (first === undefined) {
|
|
160
|
+
throw new Error('LikeC4 project requires at least one view');
|
|
161
|
+
}
|
|
162
|
+
const model = exportLikeC4(unionProjection(project, views), first.prepared.subjectMapping, first.prepared.kindMapping);
|
|
163
|
+
if (!model.ok)
|
|
164
|
+
return model;
|
|
165
|
+
const startToken = '\nviews {\n';
|
|
166
|
+
const modelEnd = model.source.indexOf(startToken);
|
|
167
|
+
const deployments = views.flatMap((view) => {
|
|
168
|
+
const rendered = deploymentBody(view);
|
|
169
|
+
return rendered === undefined ? [] : [rendered];
|
|
170
|
+
});
|
|
171
|
+
const renderedViews = views.map(viewBody);
|
|
172
|
+
return {
|
|
173
|
+
ok: true,
|
|
174
|
+
source: `${model.source.slice(0, modelEnd)}${deployments.length === 0 ? '' : `\n${deployments.join('\n')}\n`}\nviews {\n${renderedViews.join('\n')}\n}\n`,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { exportLikeC4, type LikeC4ExportDiagnostic, type LikeC4ExportOptions, type LikeC4ExportResult, } from './likec4-export.js';
|
|
2
|
+
export { loadLikeC4KindMapping, type LikeC4KindEntry, type LikeC4KindMapping, type LikeC4KindMappingLoadResult, } from './likec4-kind-mapping.js';
|
|
3
|
+
export { prepareLikeC4Export, type LikeC4PreparationDiagnostic, type LikeC4PreparationInput, type LikeC4PreparationResult, } from './likec4-prepare.js';
|
|
4
|
+
export { exportLikeC4Project, loadLikeC4ProjectDefinition, type LikeC4ProjectDefinition, type PreparedLikeC4ProjectView, } from './likec4-project.js';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { SemanticGraph } from './compiler.js';
|
|
2
|
+
export interface StateComparison {
|
|
3
|
+
readonly format: 'yarramate/state-comparison/v1';
|
|
4
|
+
readonly from: string;
|
|
5
|
+
readonly to: string;
|
|
6
|
+
readonly added: SemanticGraph['subjects'];
|
|
7
|
+
readonly removed: SemanticGraph['subjects'];
|
|
8
|
+
readonly retained: SemanticGraph['subjects'];
|
|
9
|
+
}
|
|
10
|
+
export interface StateComparisonIssue {
|
|
11
|
+
readonly code: 'YMS101';
|
|
12
|
+
readonly message: string;
|
|
13
|
+
readonly state: string;
|
|
14
|
+
}
|
|
15
|
+
export type StateComparisonResult = {
|
|
16
|
+
readonly ok: true;
|
|
17
|
+
readonly comparison: StateComparison;
|
|
18
|
+
} | {
|
|
19
|
+
readonly ok: false;
|
|
20
|
+
readonly issues: readonly StateComparisonIssue[];
|
|
21
|
+
};
|
|
22
|
+
export declare function compareArchitectureStates(graph: SemanticGraph, from: string, to: string): StateComparisonResult;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
const references = (graph, subject, predicate) => graph.claims.flatMap((claim) => claim.subject === subject &&
|
|
2
|
+
claim.predicate === predicate &&
|
|
3
|
+
'ref' in claim.object
|
|
4
|
+
? [claim.object.ref]
|
|
5
|
+
: []);
|
|
6
|
+
export function compareArchitectureStates(graph, from, to) {
|
|
7
|
+
const architectureStates = new Set(graph.claims
|
|
8
|
+
.filter(({ predicate }) => predicate === 'yarramate/state/type')
|
|
9
|
+
.map(({ subject }) => subject));
|
|
10
|
+
const issues = [...new Set([from, to])]
|
|
11
|
+
.filter((state) => !architectureStates.has(state))
|
|
12
|
+
.map((state) => ({
|
|
13
|
+
code: 'YMS101',
|
|
14
|
+
message: `Architecture state "${state}" does not exist`,
|
|
15
|
+
state,
|
|
16
|
+
}));
|
|
17
|
+
if (issues.length > 0)
|
|
18
|
+
return { ok: false, issues };
|
|
19
|
+
const subjectsIn = (state) => {
|
|
20
|
+
const concepts = new Set(graph.subjects
|
|
21
|
+
.filter(({ id, type }) => type === 'concept' && !architectureStates.has(id))
|
|
22
|
+
.filter(({ id }) => {
|
|
23
|
+
const presence = references(graph, id, 'yarramate/state/present-in');
|
|
24
|
+
return presence.length === 0 || presence.includes(state);
|
|
25
|
+
})
|
|
26
|
+
.map(({ id }) => id));
|
|
27
|
+
const relationships = graph.subjects
|
|
28
|
+
.filter(({ type }) => type === 'relationship')
|
|
29
|
+
.filter(({ id }) => {
|
|
30
|
+
const structural = graph.claims.find((claim) => claim.id === id && 'ref' in claim.object);
|
|
31
|
+
if (structural === undefined || !('ref' in structural.object)) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
const presence = references(graph, id, 'yarramate/state/present-in');
|
|
35
|
+
return ((presence.length === 0 || presence.includes(state)) &&
|
|
36
|
+
concepts.has(structural.subject) &&
|
|
37
|
+
concepts.has(structural.object.ref));
|
|
38
|
+
})
|
|
39
|
+
.map(({ id }) => id);
|
|
40
|
+
return new Set([...concepts, ...relationships]);
|
|
41
|
+
};
|
|
42
|
+
const fromSubjects = subjectsIn(from);
|
|
43
|
+
const toSubjects = subjectsIn(to);
|
|
44
|
+
const classify = (predicate) => [...graph.subjects]
|
|
45
|
+
.sort((left, right) => left.id.localeCompare(right.id) ||
|
|
46
|
+
left.type.localeCompare(right.type))
|
|
47
|
+
.filter(({ id }) => {
|
|
48
|
+
if (architectureStates.has(id))
|
|
49
|
+
return false;
|
|
50
|
+
return predicate(fromSubjects.has(id), toSubjects.has(id));
|
|
51
|
+
});
|
|
52
|
+
return {
|
|
53
|
+
ok: true,
|
|
54
|
+
comparison: {
|
|
55
|
+
format: 'yarramate/state-comparison/v1',
|
|
56
|
+
from,
|
|
57
|
+
to,
|
|
58
|
+
added: classify((inFrom, inTo) => !inFrom && inTo),
|
|
59
|
+
removed: classify((inFrom, inTo) => inFrom && !inTo),
|
|
60
|
+
retained: classify((inFrom, inTo) => inFrom && inTo),
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import Ajv2020Module from 'ajv/dist/2020.js';
|
|
4
|
+
import { parseDocument } from 'yaml';
|
|
5
|
+
import { loadAdapterMapping, validateAdapterMappings, } from './adapter-mapping.js';
|
|
6
|
+
import { checkResultJson, humanDiagnostics, resolveCliWorkspaceSources, sortDiagnostics, usage, } from './cli-support.js';
|
|
7
|
+
import { compileWorkspace } from './compiler.js';
|
|
8
|
+
import { checkCoreContract, loadCoreContract, } from './core-contract.js';
|
|
9
|
+
import { evaluateEvidenceWorkspace, loadEvidence, } from './evidence.js';
|
|
10
|
+
import { loadProjection } from './projection.js';
|
|
11
|
+
const Ajv2020 = Ajv2020Module.default;
|
|
12
|
+
export function runCheckCommand(options, cwd) {
|
|
13
|
+
const json = options.includes('--json');
|
|
14
|
+
const paths = options.filter((option) => option !== '--json');
|
|
15
|
+
const unknownOption = paths.find((path) => path.startsWith('-'));
|
|
16
|
+
if (unknownOption !== undefined || paths.length === 0) {
|
|
17
|
+
return { exitCode: 2, stdout: '', stderr: usage };
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
const resolved = resolveCliWorkspaceSources(paths, cwd, {
|
|
21
|
+
includeAdapterMappings: true,
|
|
22
|
+
});
|
|
23
|
+
if (!resolved.ok) {
|
|
24
|
+
const output = json
|
|
25
|
+
? checkResultJson(false, resolved.diagnostics)
|
|
26
|
+
: humanDiagnostics(resolved.diagnostics);
|
|
27
|
+
return { exitCode: 1, stdout: output, stderr: '' };
|
|
28
|
+
}
|
|
29
|
+
const contractDiagnostics = sortDiagnostics(resolved.contracts.flatMap((path) => {
|
|
30
|
+
const source = {
|
|
31
|
+
path,
|
|
32
|
+
source: readFileSync(resolve(cwd, path), 'utf8'),
|
|
33
|
+
};
|
|
34
|
+
const loaded = loadCoreContract(source);
|
|
35
|
+
if (!loaded.ok)
|
|
36
|
+
return loaded.diagnostics;
|
|
37
|
+
let packageManifest;
|
|
38
|
+
try {
|
|
39
|
+
packageManifest = JSON.parse(readFileSync(resolve(cwd, loaded.contract.packageManifest), 'utf8'));
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
packageManifest = undefined;
|
|
43
|
+
}
|
|
44
|
+
const packageRecord = typeof packageManifest === 'object' &&
|
|
45
|
+
packageManifest !== null &&
|
|
46
|
+
!Array.isArray(packageManifest)
|
|
47
|
+
? packageManifest
|
|
48
|
+
: undefined;
|
|
49
|
+
const exportsRecord = typeof packageRecord?.exports === 'object' &&
|
|
50
|
+
packageRecord.exports !== null
|
|
51
|
+
? Object.fromEntries(Object.entries(packageRecord.exports).filter((entry) => typeof entry[1] === 'string'))
|
|
52
|
+
: {};
|
|
53
|
+
const binaries = typeof packageRecord?.bin === 'object' &&
|
|
54
|
+
packageRecord.bin !== null
|
|
55
|
+
? Object.keys(packageRecord.bin)
|
|
56
|
+
: typeof packageRecord?.bin === 'string' &&
|
|
57
|
+
typeof packageRecord.name === 'string'
|
|
58
|
+
? [packageRecord.name]
|
|
59
|
+
: [];
|
|
60
|
+
const schemas = {};
|
|
61
|
+
for (const { schema } of loaded.contract.formats) {
|
|
62
|
+
if (!existsSync(resolve(cwd, schema)))
|
|
63
|
+
continue;
|
|
64
|
+
try {
|
|
65
|
+
const value = JSON.parse(readFileSync(resolve(cwd, schema), 'utf8'));
|
|
66
|
+
const record = typeof value === 'object' && value !== null
|
|
67
|
+
? value
|
|
68
|
+
: undefined;
|
|
69
|
+
const properties = typeof record?.properties === 'object' &&
|
|
70
|
+
record.properties !== null
|
|
71
|
+
? record.properties
|
|
72
|
+
: undefined;
|
|
73
|
+
const format = typeof properties?.format === 'object' &&
|
|
74
|
+
properties.format !== null
|
|
75
|
+
? properties.format
|
|
76
|
+
: undefined;
|
|
77
|
+
let validSchema = true;
|
|
78
|
+
try {
|
|
79
|
+
new Ajv2020({ strict: false }).compile(value);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
validSchema = false;
|
|
83
|
+
}
|
|
84
|
+
schemas[schema] = {
|
|
85
|
+
ok: true,
|
|
86
|
+
validSchema,
|
|
87
|
+
...(typeof format?.const === 'string'
|
|
88
|
+
? { format: format.const }
|
|
89
|
+
: {}),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
schemas[schema] = { ok: false };
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const checked = checkCoreContract(source, {
|
|
97
|
+
files: [
|
|
98
|
+
loaded.contract.packageManifest,
|
|
99
|
+
...loaded.contract.formats.map(({ schema }) => schema),
|
|
100
|
+
].filter((file) => existsSync(resolve(cwd, file))),
|
|
101
|
+
packageManifestValid: packageRecord !== undefined,
|
|
102
|
+
packageExports: exportsRecord,
|
|
103
|
+
packageBinaries: binaries,
|
|
104
|
+
schemas,
|
|
105
|
+
});
|
|
106
|
+
return checked.ok ? [] : checked.diagnostics;
|
|
107
|
+
}));
|
|
108
|
+
if (contractDiagnostics.length > 0) {
|
|
109
|
+
const output = json
|
|
110
|
+
? checkResultJson(false, contractDiagnostics)
|
|
111
|
+
: humanDiagnostics(contractDiagnostics);
|
|
112
|
+
return { exitCode: 1, stdout: output, stderr: '' };
|
|
113
|
+
}
|
|
114
|
+
const projectionDiagnostics = sortDiagnostics(resolved.projections.flatMap((path) => {
|
|
115
|
+
const loaded = loadProjection({
|
|
116
|
+
path,
|
|
117
|
+
source: readFileSync(resolve(cwd, path), 'utf8'),
|
|
118
|
+
});
|
|
119
|
+
return loaded.ok ? [] : loaded.diagnostics;
|
|
120
|
+
}));
|
|
121
|
+
if (projectionDiagnostics.length > 0) {
|
|
122
|
+
const output = json
|
|
123
|
+
? checkResultJson(false, projectionDiagnostics)
|
|
124
|
+
: humanDiagnostics(projectionDiagnostics);
|
|
125
|
+
return { exitCode: 1, stdout: output, stderr: '' };
|
|
126
|
+
}
|
|
127
|
+
const loadedEvidence = resolved.evidence.map((path) => loadEvidence({
|
|
128
|
+
path,
|
|
129
|
+
source: readFileSync(resolve(cwd, path), 'utf8'),
|
|
130
|
+
}));
|
|
131
|
+
const evidenceLoadDiagnostics = sortDiagnostics(loadedEvidence.flatMap((loaded) => loaded.ok ? [] : loaded.diagnostics));
|
|
132
|
+
if (evidenceLoadDiagnostics.length > 0) {
|
|
133
|
+
const output = json
|
|
134
|
+
? checkResultJson(false, evidenceLoadDiagnostics)
|
|
135
|
+
: humanDiagnostics(evidenceLoadDiagnostics);
|
|
136
|
+
return { exitCode: 1, stdout: output, stderr: '' };
|
|
137
|
+
}
|
|
138
|
+
const sources = resolved.paths.map((path) => ({
|
|
139
|
+
path,
|
|
140
|
+
source: readFileSync(resolve(cwd, path), 'utf8'),
|
|
141
|
+
}));
|
|
142
|
+
const mappingSources = sources.filter(({ source }) => parseDocument(source).get('format') ===
|
|
143
|
+
'yarramate/adapter-mapping/v1');
|
|
144
|
+
const coreSources = sources.filter((source) => !mappingSources.includes(source));
|
|
145
|
+
const loadedMappings = mappingSources.map((source) => loadAdapterMapping(source));
|
|
146
|
+
const mappingLoadDiagnostics = sortDiagnostics(loadedMappings.flatMap((loaded) => loaded.ok ? [] : loaded.diagnostics));
|
|
147
|
+
if (mappingLoadDiagnostics.length > 0) {
|
|
148
|
+
const output = json
|
|
149
|
+
? checkResultJson(false, mappingLoadDiagnostics)
|
|
150
|
+
: humanDiagnostics(mappingLoadDiagnostics);
|
|
151
|
+
return { exitCode: 1, stdout: output, stderr: '' };
|
|
152
|
+
}
|
|
153
|
+
const result = compileWorkspace(coreSources);
|
|
154
|
+
const mappingValidation = result.ok
|
|
155
|
+
? validateAdapterMappings(result.graph, loadedMappings.flatMap((loaded) => loaded.ok ? [loaded.mapping] : []))
|
|
156
|
+
: undefined;
|
|
157
|
+
const mappingDiagnostics = mappingValidation === undefined || mappingValidation.ok
|
|
158
|
+
? []
|
|
159
|
+
: mappingValidation.diagnostics;
|
|
160
|
+
const evidenceEvaluation = result.ok
|
|
161
|
+
? evaluateEvidenceWorkspace(result.graph, loadedEvidence.flatMap((loaded) => loaded.ok ? [loaded.evidence] : []))
|
|
162
|
+
: undefined;
|
|
163
|
+
const evidenceDiagnostics = evidenceEvaluation === undefined || evidenceEvaluation.ok
|
|
164
|
+
? []
|
|
165
|
+
: evidenceEvaluation.diagnostics;
|
|
166
|
+
const optionalDiagnostics = sortDiagnostics([
|
|
167
|
+
...mappingDiagnostics,
|
|
168
|
+
...evidenceDiagnostics,
|
|
169
|
+
]);
|
|
170
|
+
const ok = result.ok && optionalDiagnostics.length === 0;
|
|
171
|
+
const diagnostics = result.ok
|
|
172
|
+
? optionalDiagnostics
|
|
173
|
+
: result.diagnostics;
|
|
174
|
+
if (json) {
|
|
175
|
+
return {
|
|
176
|
+
exitCode: ok ? 0 : 1,
|
|
177
|
+
stdout: checkResultJson(ok, ok ? [] : diagnostics),
|
|
178
|
+
stderr: '',
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
if (ok && result.ok) {
|
|
182
|
+
const documentCount = result.graph.documents.length;
|
|
183
|
+
const profileCount = coreSources.length - documentCount;
|
|
184
|
+
const mappingCount = mappingSources.length;
|
|
185
|
+
const projectionCount = resolved.projections.length;
|
|
186
|
+
const evidenceCount = resolved.evidence.length;
|
|
187
|
+
const contractCount = resolved.contracts.length;
|
|
188
|
+
const checked = [
|
|
189
|
+
`${documentCount} ${documentCount === 1 ? 'document' : 'documents'}`,
|
|
190
|
+
...(profileCount > 0
|
|
191
|
+
? [
|
|
192
|
+
`${profileCount} ${profileCount === 1 ? 'profile' : 'profiles'}`,
|
|
193
|
+
]
|
|
194
|
+
: []),
|
|
195
|
+
...(mappingCount > 0
|
|
196
|
+
? [
|
|
197
|
+
`${mappingCount} ${mappingCount === 1 ? 'adapter mapping' : 'adapter mappings'}`,
|
|
198
|
+
]
|
|
199
|
+
: []),
|
|
200
|
+
...(projectionCount > 0
|
|
201
|
+
? [
|
|
202
|
+
`${projectionCount} ${projectionCount === 1 ? 'projection' : 'projections'}`,
|
|
203
|
+
]
|
|
204
|
+
: []),
|
|
205
|
+
...(evidenceCount > 0
|
|
206
|
+
? [
|
|
207
|
+
`${evidenceCount} ${evidenceCount === 1 ? 'evidence document' : 'evidence documents'}`,
|
|
208
|
+
]
|
|
209
|
+
: []),
|
|
210
|
+
...(contractCount > 0
|
|
211
|
+
? [
|
|
212
|
+
`${contractCount} ${contractCount === 1 ? 'Core contract' : 'Core contracts'}`,
|
|
213
|
+
]
|
|
214
|
+
: []),
|
|
215
|
+
].join(' and ');
|
|
216
|
+
return {
|
|
217
|
+
exitCode: 0,
|
|
218
|
+
stdout: `Checked ${checked}: no errors\n`,
|
|
219
|
+
stderr: '',
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
return {
|
|
223
|
+
exitCode: 1,
|
|
224
|
+
stdout: humanDiagnostics(diagnostics),
|
|
225
|
+
stderr: '',
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
catch (error) {
|
|
229
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
230
|
+
return { exitCode: 2, stdout: '', stderr: `${message}\n` };
|
|
231
|
+
}
|
|
232
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Diagnostic } from './compiler.js';
|
|
2
|
+
export interface CliResult {
|
|
3
|
+
readonly exitCode: 0 | 1 | 2;
|
|
4
|
+
readonly stdout: string;
|
|
5
|
+
readonly stderr: string;
|
|
6
|
+
}
|
|
7
|
+
export declare const isMainModule: (moduleUrl: string, entrypoint: string | undefined) => boolean;
|
|
8
|
+
export declare const usage = "Usage:\n yarramate init <directory>\n yarramate add <document.yaml> --id <id> --kind <kind> --name <name> [--status <status>] [--description <text>] [--owner <ref>] [--constraint <id>=<ref> ...] [--present-in <state-ref> ...] [--source <source.yaml> ...]\n yarramate connect <document.yaml> --id <id> --kind <kind> --from <ref> --to <ref> [--name <name>] [--status <status>] [--mode <mode>] [--content <text>] [--present-in <state-ref> ...] [--source <source.yaml> ...]\n yarramate check <source.yaml> [source.yaml ...] [--json]\n yarramate compile <source.yaml> [source.yaml ...]\n yarramate context <projection.yaml> <source.yaml> [source.yaml ...]\n yarramate view <projection.yaml> <source.yaml> [source.yaml ...]\n yarramate compare <from-state> <to-state> <source.yaml> [source.yaml ...]\n yarramate evidence <evidence.yaml> <source.yaml> [source.yaml ...]\n yarramate reconcile <workspace.yaml>\n";
|
|
9
|
+
export declare const diagnosticJson: (diagnostics: unknown) => string;
|
|
10
|
+
export declare const checkResultJson: (ok: boolean, diagnostics: unknown) => string;
|
|
11
|
+
export declare const humanDiagnostics: (diagnostics: readonly Pick<Diagnostic, "path" | "line" | "column" | "code" | "message">[]) => string;
|
|
12
|
+
export declare const sortDiagnostics: <T extends Diagnostic>(diagnostics: readonly T[]) => T[];
|
|
13
|
+
export declare const resolveCliWorkspaceSources: (paths: readonly string[], cwd: string, options?: {
|
|
14
|
+
readonly includeAdapterMappings?: boolean;
|
|
15
|
+
}) => {
|
|
16
|
+
readonly ok: true;
|
|
17
|
+
readonly paths: readonly string[];
|
|
18
|
+
readonly projections: readonly string[];
|
|
19
|
+
readonly evidence: readonly string[];
|
|
20
|
+
readonly contracts: readonly string[];
|
|
21
|
+
} | {
|
|
22
|
+
readonly ok: false;
|
|
23
|
+
readonly diagnostics: readonly Diagnostic[];
|
|
24
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { readFileSync, realpathSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { parseDocument } from 'yaml';
|
|
5
|
+
import { loadWorkspaceManifest } from './workspace.js';
|
|
6
|
+
export const isMainModule = (moduleUrl, entrypoint) => {
|
|
7
|
+
if (entrypoint === undefined)
|
|
8
|
+
return false;
|
|
9
|
+
try {
|
|
10
|
+
return (realpathSync(fileURLToPath(moduleUrl)) ===
|
|
11
|
+
realpathSync(entrypoint));
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
export const usage = 'Usage:\n yarramate init <directory>\n yarramate add <document.yaml> --id <id> --kind <kind> --name <name> [--status <status>] [--description <text>] [--owner <ref>] [--constraint <id>=<ref> ...] [--present-in <state-ref> ...] [--source <source.yaml> ...]\n yarramate connect <document.yaml> --id <id> --kind <kind> --from <ref> --to <ref> [--name <name>] [--status <status>] [--mode <mode>] [--content <text>] [--present-in <state-ref> ...] [--source <source.yaml> ...]\n yarramate check <source.yaml> [source.yaml ...] [--json]\n yarramate compile <source.yaml> [source.yaml ...]\n yarramate context <projection.yaml> <source.yaml> [source.yaml ...]\n yarramate view <projection.yaml> <source.yaml> [source.yaml ...]\n yarramate compare <from-state> <to-state> <source.yaml> [source.yaml ...]\n yarramate evidence <evidence.yaml> <source.yaml> [source.yaml ...]\n yarramate reconcile <workspace.yaml>\n';
|
|
18
|
+
export const diagnosticJson = (diagnostics) => `${JSON.stringify({
|
|
19
|
+
format: 'yarramate/diagnostic-result/v1',
|
|
20
|
+
diagnostics,
|
|
21
|
+
}, null, 2)}\n`;
|
|
22
|
+
export const checkResultJson = (ok, diagnostics) => `${JSON.stringify({
|
|
23
|
+
format: 'yarramate/check-result/v1',
|
|
24
|
+
ok,
|
|
25
|
+
diagnostics,
|
|
26
|
+
}, null, 2)}\n`;
|
|
27
|
+
export const humanDiagnostics = (diagnostics) => diagnostics
|
|
28
|
+
.map((diagnostic) => `${diagnostic.path}:${diagnostic.line}:${diagnostic.column} error ${diagnostic.code} ${diagnostic.message}\n`)
|
|
29
|
+
.join('');
|
|
30
|
+
export const sortDiagnostics = (diagnostics) => [...diagnostics].sort((left, right) => left.path.localeCompare(right.path) ||
|
|
31
|
+
left.line - right.line ||
|
|
32
|
+
left.column - right.column ||
|
|
33
|
+
left.code.localeCompare(right.code) ||
|
|
34
|
+
left.message.localeCompare(right.message));
|
|
35
|
+
export const resolveCliWorkspaceSources = (paths, cwd, options = {}) => {
|
|
36
|
+
if (paths.length !== 1) {
|
|
37
|
+
return {
|
|
38
|
+
ok: true,
|
|
39
|
+
paths,
|
|
40
|
+
projections: [],
|
|
41
|
+
evidence: [],
|
|
42
|
+
contracts: [],
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
const manifestPath = paths[0];
|
|
46
|
+
if (manifestPath === undefined) {
|
|
47
|
+
return {
|
|
48
|
+
ok: true,
|
|
49
|
+
paths,
|
|
50
|
+
projections: [],
|
|
51
|
+
evidence: [],
|
|
52
|
+
contracts: [],
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
const source = readFileSync(resolve(cwd, manifestPath), 'utf8');
|
|
56
|
+
if (parseDocument(source).get('format') !== 'yarramate/workspace/v1') {
|
|
57
|
+
return {
|
|
58
|
+
ok: true,
|
|
59
|
+
paths,
|
|
60
|
+
projections: [],
|
|
61
|
+
evidence: [],
|
|
62
|
+
contracts: [],
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
const loaded = loadWorkspaceManifest({ path: manifestPath, source }, cwd);
|
|
66
|
+
return loaded.ok
|
|
67
|
+
? {
|
|
68
|
+
ok: true,
|
|
69
|
+
paths: [
|
|
70
|
+
...loaded.workspace.profiles,
|
|
71
|
+
...loaded.workspace.documents,
|
|
72
|
+
...(options.includeAdapterMappings === true
|
|
73
|
+
? loaded.workspace.adapterMappings
|
|
74
|
+
: []),
|
|
75
|
+
],
|
|
76
|
+
projections: loaded.workspace.projections,
|
|
77
|
+
evidence: loaded.workspace.evidence,
|
|
78
|
+
contracts: loaded.workspace.contracts,
|
|
79
|
+
}
|
|
80
|
+
: { ok: false, diagnostics: loaded.diagnostics };
|
|
81
|
+
};
|
package/dist/cli.d.ts
ADDED