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.
Files changed (76) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +174 -0
  3. package/dist/adapter-mapping.d.ts +40 -0
  4. package/dist/adapter-mapping.js +200 -0
  5. package/dist/adapters/graphify-cli.d.ts +3 -0
  6. package/dist/adapters/graphify-cli.js +127 -0
  7. package/dist/adapters/graphify-entry.d.ts +1 -0
  8. package/dist/adapters/graphify-entry.js +1 -0
  9. package/dist/adapters/graphify.d.ts +23 -0
  10. package/dist/adapters/graphify.js +47 -0
  11. package/dist/adapters/likec4-cli.d.ts +3 -0
  12. package/dist/adapters/likec4-cli.js +662 -0
  13. package/dist/adapters/likec4-export.d.ts +25 -0
  14. package/dist/adapters/likec4-export.js +204 -0
  15. package/dist/adapters/likec4-kind-mapping.d.ts +22 -0
  16. package/dist/adapters/likec4-kind-mapping.js +60 -0
  17. package/dist/adapters/likec4-prepare.d.ts +28 -0
  18. package/dist/adapters/likec4-prepare.js +154 -0
  19. package/dist/adapters/likec4-project.d.ts +58 -0
  20. package/dist/adapters/likec4-project.js +176 -0
  21. package/dist/adapters/likec4.d.ts +4 -0
  22. package/dist/adapters/likec4.js +4 -0
  23. package/dist/architecture-state.d.ts +22 -0
  24. package/dist/architecture-state.js +63 -0
  25. package/dist/check-command.d.ts +2 -0
  26. package/dist/check-command.js +232 -0
  27. package/dist/cli-support.d.ts +24 -0
  28. package/dist/cli-support.js +81 -0
  29. package/dist/cli.d.ts +4 -0
  30. package/dist/cli.js +575 -0
  31. package/dist/compiler.d.ts +66 -0
  32. package/dist/compiler.js +940 -0
  33. package/dist/core-contract.d.ts +43 -0
  34. package/dist/core-contract.js +162 -0
  35. package/dist/evidence.d.ts +58 -0
  36. package/dist/evidence.js +161 -0
  37. package/dist/graph.d.ts +2 -0
  38. package/dist/graph.js +37 -0
  39. package/dist/index.d.ts +11 -0
  40. package/dist/index.js +9 -0
  41. package/dist/profile.d.ts +30 -0
  42. package/dist/profile.js +162 -0
  43. package/dist/projection.d.ts +42 -0
  44. package/dist/projection.js +183 -0
  45. package/dist/reconciliation.d.ts +26 -0
  46. package/dist/reconciliation.js +47 -0
  47. package/dist/source-document.d.ts +24 -0
  48. package/dist/source-document.js +80 -0
  49. package/dist/workspace.d.ts +29 -0
  50. package/dist/workspace.js +114 -0
  51. package/docs/CONSUMING-YARRAMATE.md +145 -0
  52. package/package.json +110 -0
  53. package/schema/yarramate-adapter-mapping.schema.json +59 -0
  54. package/schema/yarramate-check-result.schema.json +92 -0
  55. package/schema/yarramate-core-contract.schema.json +132 -0
  56. package/schema/yarramate-diagnostic-result.schema.json +64 -0
  57. package/schema/yarramate-document.schema.json +168 -0
  58. package/schema/yarramate-evidence-report.schema.json +73 -0
  59. package/schema/yarramate-evidence.schema.json +92 -0
  60. package/schema/yarramate-graph-v2.schema.json +170 -0
  61. package/schema/yarramate-likec4-check-result.schema.json +50 -0
  62. package/schema/yarramate-likec4-diagnostic-result.schema.json +69 -0
  63. package/schema/yarramate-likec4-generated-project-v2.schema.json +100 -0
  64. package/schema/yarramate-likec4-generated-project.schema.json +76 -0
  65. package/schema/yarramate-likec4-kind-mapping.schema.json +65 -0
  66. package/schema/yarramate-likec4-project.schema.json +160 -0
  67. package/schema/yarramate-profile.schema.json +113 -0
  68. package/schema/yarramate-projection-result.schema.json +177 -0
  69. package/schema/yarramate-projection.schema.json +126 -0
  70. package/schema/yarramate-reconciliation-report.schema.json +90 -0
  71. package/schema/yarramate-state-comparison.schema.json +61 -0
  72. package/schema/yarramate-workspace.schema.json +55 -0
  73. package/skills/yarramate-architecture/SKILL.md +135 -0
  74. package/skills/yarramate-architecture/agents/openai.yaml +4 -0
  75. package/skills/yarramate-architecture/references/journey-checklists.md +57 -0
  76. 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,4 @@
1
+ export { exportLikeC4, } from './likec4-export.js';
2
+ export { loadLikeC4KindMapping, } from './likec4-kind-mapping.js';
3
+ export { prepareLikeC4Export, } from './likec4-prepare.js';
4
+ export { exportLikeC4Project, loadLikeC4ProjectDefinition, } 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,2 @@
1
+ import { type CliResult } from './cli-support.js';
2
+ export declare function runCheckCommand(options: readonly string[], cwd: string): CliResult;
@@ -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
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { type CliResult } from './cli-support.js';
3
+ export type { CliResult } from './cli-support.js';
4
+ export declare function runCli(args: readonly string[], cwd?: string): CliResult;