@atolis-hq/corum 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/README.md +223 -0
- package/dist/src/adapters/index.js +12 -0
- package/dist/src/adapters/openapi/index.js +12 -0
- package/dist/src/adapters/openapi/mapper.js +218 -0
- package/dist/src/adapters/openapi/parser.js +16 -0
- package/dist/src/bin/corum.js +164 -0
- package/dist/src/cli.js +20 -0
- package/dist/src/graph/index.js +128 -0
- package/dist/src/graph/overlay.js +136 -0
- package/dist/src/import/config.js +39 -0
- package/dist/src/import/runner.js +56 -0
- package/dist/src/loader/cluster-loader.js +120 -0
- package/dist/src/loader/constants.js +32 -0
- package/dist/src/loader/edge-loader.js +59 -0
- package/dist/src/loader/fs-utils.js +20 -0
- package/dist/src/loader/index.js +108 -0
- package/dist/src/loader/pack-loader.js +99 -0
- package/dist/src/mcp/index.js +333 -0
- package/dist/src/mcp/serializers.js +68 -0
- package/dist/src/openapi-to-api-endpoints.js +240 -0
- package/dist/src/reconcile/index.js +46 -0
- package/dist/src/schema/index.js +16 -0
- package/dist/src/source/config-file.js +22 -0
- package/dist/src/source/config.js +71 -0
- package/dist/src/source/content-utils.js +13 -0
- package/dist/src/source/file-source.js +135 -0
- package/dist/src/source/git-cache.js +54 -0
- package/dist/src/source/git-source.js +333 -0
- package/dist/src/source/index.js +8 -0
- package/dist/src/web/server.js +557 -0
- package/dist/src/writer/graph-writer.js +153 -0
- package/package.json +36 -0
- package/web/app.jsx +668 -0
- package/web/favicon.svg +19 -0
- package/web/index.html +41 -0
- package/web/nav.js +141 -0
- package/web/primitives.jsx +583 -0
- package/web/router.js +49 -0
- package/web/style.css +827 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
|
|
4
|
+
import { getOwnedSections } from '../loader/pack-loader.js';
|
|
5
|
+
import { isPackRef } from '../loader/fs-utils.js';
|
|
6
|
+
import { FileGraphSource } from '../source/file-source.js';
|
|
7
|
+
const STRUCTURAL_EDGE_TYPES = new Set(['has-field', 'has-value']);
|
|
8
|
+
const YAML_STRINGIFY_OPTIONS = { singleQuote: true };
|
|
9
|
+
export function serializeGraph(graph, options = {}) {
|
|
10
|
+
const map = new Map();
|
|
11
|
+
map.set('graph.yaml', buildGraphYaml(graph, options));
|
|
12
|
+
for (const root of getRootNodes(graph)) {
|
|
13
|
+
map.set(clusterPath(root), stringifyGraphYaml(toClusterDocument(graph, root)));
|
|
14
|
+
}
|
|
15
|
+
const explicitEdges = getAllEdges(graph)
|
|
16
|
+
.filter(edge => !STRUCTURAL_EDGE_TYPES.has(edge.type))
|
|
17
|
+
.sort((a, b) => a.id.localeCompare(b.id));
|
|
18
|
+
if (explicitEdges.length > 0) {
|
|
19
|
+
map.set('edges/corum.edges.yaml', stringifyGraphYaml({ edges: explicitEdges.map(toEdgeDocument) }));
|
|
20
|
+
}
|
|
21
|
+
return map;
|
|
22
|
+
}
|
|
23
|
+
function clusterPath(node) {
|
|
24
|
+
const parts = node.id.split('.');
|
|
25
|
+
if (parts.length !== 3)
|
|
26
|
+
throw new Error(`clusterPath: expected 3-segment root node ID, got: ${node.id}`);
|
|
27
|
+
const [component, template, name] = parts;
|
|
28
|
+
return `components/${component}/${template}s/${name}.yaml`;
|
|
29
|
+
}
|
|
30
|
+
export async function saveGraph(graph, options) {
|
|
31
|
+
const { sourceGraphPath, outputGraphPath, replace = true } = options;
|
|
32
|
+
if (fs.existsSync(outputGraphPath)) {
|
|
33
|
+
if (!replace) {
|
|
34
|
+
throw new Error(`output graph folder already exists: ${outputGraphPath}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const source = new FileGraphSource({ graphDir: outputGraphPath, defaultBranch: 'local' });
|
|
38
|
+
await source.commit('local', serializeGraph(graph, { sourceGraphPath, outputGraphPath }), 'save graph', { replaceGraphContent: replace });
|
|
39
|
+
}
|
|
40
|
+
function buildGraphYaml(graph, options) {
|
|
41
|
+
const content = graph.sourceContent?.get('graph.yaml');
|
|
42
|
+
if (!content)
|
|
43
|
+
return stringifyGraphYaml({ templatePacks: [] });
|
|
44
|
+
if (!options.sourceGraphPath || !options.outputGraphPath)
|
|
45
|
+
return content;
|
|
46
|
+
const doc = parseYaml(content);
|
|
47
|
+
const packs = Array.isArray(doc.templatePacks) ? doc.templatePacks : [];
|
|
48
|
+
doc.templatePacks = packs.map(pack => {
|
|
49
|
+
if (!isPackRef(pack))
|
|
50
|
+
return pack;
|
|
51
|
+
const absolutePackPath = path.resolve(options.sourceGraphPath, pack.path);
|
|
52
|
+
return {
|
|
53
|
+
...pack,
|
|
54
|
+
path: normalizeContentKey(path.relative(options.outputGraphPath, absolutePackPath)),
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
return stringifyGraphYaml(doc);
|
|
58
|
+
}
|
|
59
|
+
function normalizeContentKey(value) {
|
|
60
|
+
return value.split(path.sep).join('/');
|
|
61
|
+
}
|
|
62
|
+
function toClusterDocument(graph, root) {
|
|
63
|
+
const metadata = {
|
|
64
|
+
component: root.component,
|
|
65
|
+
state: root.state,
|
|
66
|
+
stability: root.stability,
|
|
67
|
+
lastModifiedAt: root.lastModifiedAt,
|
|
68
|
+
};
|
|
69
|
+
if (root.extractedFrom !== undefined)
|
|
70
|
+
metadata.extractedFrom = root.extractedFrom;
|
|
71
|
+
if (root.derivation !== undefined)
|
|
72
|
+
metadata.derivation = root.derivation;
|
|
73
|
+
if (root.derivedBy !== undefined)
|
|
74
|
+
metadata.derivedBy = root.derivedBy;
|
|
75
|
+
const doc = {
|
|
76
|
+
id: root.id,
|
|
77
|
+
template: root.template,
|
|
78
|
+
schemaVersion: root.schemaVersion,
|
|
79
|
+
metadata,
|
|
80
|
+
};
|
|
81
|
+
if (Object.keys(root.properties).length > 0) {
|
|
82
|
+
doc.properties = root.properties;
|
|
83
|
+
}
|
|
84
|
+
appendOwnedSections(graph, root, doc);
|
|
85
|
+
return doc;
|
|
86
|
+
}
|
|
87
|
+
function appendOwnedSections(graph, parent, target) {
|
|
88
|
+
const template = graph.templates.get(parent.template);
|
|
89
|
+
if (!template)
|
|
90
|
+
return;
|
|
91
|
+
for (const [sectionName] of Object.entries(getOwnedSections(template))) {
|
|
92
|
+
const children = getDirectOwnedChildren(graph, parent.id, sectionName);
|
|
93
|
+
if (children.length === 0)
|
|
94
|
+
continue;
|
|
95
|
+
const section = {};
|
|
96
|
+
for (const child of children) {
|
|
97
|
+
const childDoc = { ...child.properties };
|
|
98
|
+
if (child.state !== parent.state)
|
|
99
|
+
childDoc.state = child.state;
|
|
100
|
+
if (child.stability !== parent.stability)
|
|
101
|
+
childDoc.stability = child.stability;
|
|
102
|
+
appendOwnedSections(graph, child, childDoc);
|
|
103
|
+
section[getLocalName(child.id, parent.id, sectionName)] = childDoc;
|
|
104
|
+
}
|
|
105
|
+
target[sectionName] = section;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function getDirectOwnedChildren(graph, parentId, sectionName) {
|
|
109
|
+
const prefix = `${parentId}.${sectionName}.`;
|
|
110
|
+
return [...graph.nodesById.values()]
|
|
111
|
+
.filter(node => {
|
|
112
|
+
if (!node.id.startsWith(prefix))
|
|
113
|
+
return false;
|
|
114
|
+
const remaining = node.id.slice(prefix.length);
|
|
115
|
+
return !remaining.includes('.');
|
|
116
|
+
})
|
|
117
|
+
.sort((a, b) => a.id.localeCompare(b.id));
|
|
118
|
+
}
|
|
119
|
+
function getLocalName(childId, parentId, sectionName) {
|
|
120
|
+
return childId.slice(`${parentId}.${sectionName}.`.length);
|
|
121
|
+
}
|
|
122
|
+
function stringifyGraphYaml(value) {
|
|
123
|
+
return stringifyYaml(value, YAML_STRINGIFY_OPTIONS);
|
|
124
|
+
}
|
|
125
|
+
function toEdgeDocument(edge) {
|
|
126
|
+
const doc = {
|
|
127
|
+
from: edge.from,
|
|
128
|
+
to: edge.to,
|
|
129
|
+
type: edge.type,
|
|
130
|
+
};
|
|
131
|
+
if (edge.state !== 'proposed')
|
|
132
|
+
doc.state = edge.state;
|
|
133
|
+
if (edge.stability !== 'unstable')
|
|
134
|
+
doc.stability = edge.stability;
|
|
135
|
+
if (edge.notes !== undefined)
|
|
136
|
+
doc.notes = edge.notes;
|
|
137
|
+
return doc;
|
|
138
|
+
}
|
|
139
|
+
function getAllEdges(graph) {
|
|
140
|
+
const edges = new Map();
|
|
141
|
+
for (const edgeList of graph.edgesByFrom.values()) {
|
|
142
|
+
for (const edge of edgeList) {
|
|
143
|
+
edges.set(edge.id, edge);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return [...edges.values()];
|
|
147
|
+
}
|
|
148
|
+
function getRootNodes(graph) {
|
|
149
|
+
const nodes = [...graph.nodesById.values()];
|
|
150
|
+
return nodes
|
|
151
|
+
.filter(node => !nodes.some(other => other.id !== node.id && node.id.startsWith(`${other.id}.`)))
|
|
152
|
+
.sort((a, b) => a.id.localeCompare(b.id));
|
|
153
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@atolis-hq/corum",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"files": [
|
|
6
|
+
"dist/src/",
|
|
7
|
+
"web/"
|
|
8
|
+
],
|
|
9
|
+
"bin": {
|
|
10
|
+
"corum": "./dist/src/bin/corum.js"
|
|
11
|
+
},
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsc",
|
|
14
|
+
"test": "tsc && node --test dist/test",
|
|
15
|
+
"wireframes": "node design/wireframes/server.mjs",
|
|
16
|
+
"mcp": "node dist/src/mcp/index.js",
|
|
17
|
+
"mcp:smoke": "npm run build && node scripts/mcp-smoke.mjs",
|
|
18
|
+
"web": "node dist/src/web/server.js"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@apidevtools/swagger-parser": "^12.1.0",
|
|
22
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
23
|
+
"@toon-format/toon": "^2.1.0",
|
|
24
|
+
"commander": "^14.0.3",
|
|
25
|
+
"express": "^4.22.1",
|
|
26
|
+
"isomorphic-git": "^1.37.6",
|
|
27
|
+
"openapi-types": "^12.1.3",
|
|
28
|
+
"yaml": "^2.3.4"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@types/express": "^4.17.25",
|
|
32
|
+
"@types/node": "^20.0.0",
|
|
33
|
+
"@types/swagger-parser": "^4.0.3",
|
|
34
|
+
"typescript": "^5.0.0"
|
|
35
|
+
}
|
|
36
|
+
}
|