@runcontext/site 0.1.1 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/index.cjs +609 -495
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +75 -13
- package/dist/index.d.ts +75 -13
- package/dist/index.mjs +608 -494
- package/dist/index.mjs.map +1 -1
- package/package.json +17 -10
package/dist/index.mjs
CHANGED
|
@@ -1,549 +1,663 @@
|
|
|
1
1
|
// src/generator.ts
|
|
2
|
-
import { mkdir, writeFile, readFile } from "fs/promises";
|
|
3
|
-
import { join, dirname } from "path";
|
|
4
|
-
import { fileURLToPath } from "url";
|
|
5
2
|
import ejs from "ejs";
|
|
3
|
+
import * as fs from "fs";
|
|
4
|
+
import * as path from "path";
|
|
6
5
|
|
|
7
6
|
// src/templates.ts
|
|
8
|
-
var
|
|
7
|
+
var HEAD = `<!DOCTYPE html>
|
|
9
8
|
<html lang="en">
|
|
10
9
|
<head>
|
|
11
10
|
<meta charset="UTF-8">
|
|
12
11
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
13
|
-
<title><%= pageTitle %>
|
|
12
|
+
<title><%= pageTitle %> \u2014 <%= siteTitle %></title>
|
|
14
13
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
<
|
|
18
|
-
<
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
14
|
+
</head>`;
|
|
15
|
+
var NAV = `<nav class="bg-gray-900 text-white px-6 py-3 flex items-center gap-6">
|
|
16
|
+
<a href="<%= basePath %>/" class="font-bold text-lg"><%- siteTitle %></a>
|
|
17
|
+
<a href="<%= basePath %>/" class="hover:underline">Models</a>
|
|
18
|
+
<a href="<%= basePath %>/glossary.html" class="hover:underline">Glossary</a>
|
|
19
|
+
<a href="<%= basePath %>/search.html" class="hover:underline">Search</a>
|
|
20
|
+
</nav>`;
|
|
21
|
+
var FOOTER = `<footer class="mt-12 border-t py-4 px-6 text-gray-500 text-sm">
|
|
22
|
+
Generated by <a href="https://github.com/erickittelson/ContextKit" class="underline">ContextKit</a>
|
|
23
|
+
</footer>`;
|
|
24
|
+
var TIER_BADGE = `<% function tierBadge(tier) {
|
|
25
|
+
var colors = { none: 'bg-gray-200 text-gray-700', bronze: 'bg-amber-700 text-white', silver: 'bg-gray-400 text-white', gold: 'bg-yellow-400 text-black' };
|
|
26
|
+
var cls = colors[tier] || colors.none;
|
|
27
|
+
return '<span class="px-2 py-0.5 rounded text-xs font-semibold uppercase ' + cls + '">' + tier + '</span>';
|
|
28
|
+
} %>`;
|
|
29
|
+
var indexTemplate = `${HEAD}
|
|
30
|
+
<body class="bg-white text-gray-900 min-h-screen">
|
|
31
|
+
${NAV}
|
|
32
|
+
${TIER_BADGE}
|
|
33
|
+
<main class="max-w-5xl mx-auto p-6">
|
|
34
|
+
<h1 class="text-3xl font-bold mb-6"><%- siteTitle %></h1>
|
|
35
|
+
|
|
36
|
+
<section>
|
|
37
|
+
<h2 class="text-xl font-semibold mb-4">Models</h2>
|
|
38
|
+
<% if (Object.keys(models).length === 0) { %>
|
|
39
|
+
<p class="text-gray-500">No models found.</p>
|
|
40
|
+
<% } else { %>
|
|
41
|
+
<div class="grid gap-4">
|
|
42
|
+
<% for (var name of Object.keys(models)) { %>
|
|
43
|
+
<div class="border rounded-lg p-4 hover:shadow transition">
|
|
44
|
+
<div class="flex items-center gap-3">
|
|
45
|
+
<a href="<%= basePath %>/models/<%= name %>.html" class="text-lg font-medium text-blue-600 hover:underline"><%= name %></a>
|
|
46
|
+
<% if (tiers[name]) { %><%- tierBadge(tiers[name].tier) %><% } %>
|
|
47
|
+
</div>
|
|
48
|
+
<% if (models[name].description) { %>
|
|
49
|
+
<p class="text-gray-600 mt-1"><%= models[name].description %></p>
|
|
50
|
+
<% } %>
|
|
51
|
+
<% if (governance[name]) { %>
|
|
52
|
+
<div class="flex gap-2 mt-2 text-xs text-gray-500">
|
|
53
|
+
<% if (governance[name].owner) { %>
|
|
54
|
+
<span>Owner: <a href="<%= basePath %>/owners/<%= governance[name].owner %>.html" class="underline"><%= governance[name].owner %></a></span>
|
|
55
|
+
<% } %>
|
|
56
|
+
<% if (governance[name].trust) { %>
|
|
57
|
+
<span>Trust: <%= governance[name].trust %></span>
|
|
58
|
+
<% } %>
|
|
59
|
+
</div>
|
|
60
|
+
<% } %>
|
|
61
|
+
</div>
|
|
62
|
+
<% } %>
|
|
25
63
|
</div>
|
|
26
|
-
|
|
27
|
-
</
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
64
|
+
<% } %>
|
|
65
|
+
</section>
|
|
66
|
+
|
|
67
|
+
<% if (Object.keys(owners).length > 0) { %>
|
|
68
|
+
<section class="mt-8">
|
|
69
|
+
<h2 class="text-xl font-semibold mb-4">Owners</h2>
|
|
70
|
+
<ul class="space-y-1">
|
|
71
|
+
<% for (var oid of Object.keys(owners)) { %>
|
|
72
|
+
<li><a href="<%= basePath %>/owners/<%= oid %>.html" class="text-blue-600 hover:underline"><%= owners[oid].display_name %></a></li>
|
|
73
|
+
<% } %>
|
|
74
|
+
</ul>
|
|
75
|
+
</section>
|
|
76
|
+
<% } %>
|
|
77
|
+
</main>
|
|
78
|
+
${FOOTER}
|
|
34
79
|
</body>
|
|
35
80
|
</html>`;
|
|
36
|
-
var
|
|
37
|
-
<
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
<
|
|
43
|
-
|
|
44
|
-
<div class="bg-white rounded-lg shadow p-4 text-center">
|
|
45
|
-
<div class="text-2xl font-bold text-green-600"><%= products.length %></div>
|
|
46
|
-
<div class="text-sm text-gray-500">Products</div>
|
|
47
|
-
</div>
|
|
48
|
-
<div class="bg-white rounded-lg shadow p-4 text-center">
|
|
49
|
-
<div class="text-2xl font-bold text-purple-600"><%= policies.length %></div>
|
|
50
|
-
<div class="text-sm text-gray-500">Policies</div>
|
|
51
|
-
</div>
|
|
52
|
-
<div class="bg-white rounded-lg shadow p-4 text-center">
|
|
53
|
-
<div class="text-2xl font-bold text-orange-600"><%= entities.length %></div>
|
|
54
|
-
<div class="text-sm text-gray-500">Entities</div>
|
|
81
|
+
var modelTemplate = `${HEAD}
|
|
82
|
+
<body class="bg-white text-gray-900 min-h-screen">
|
|
83
|
+
${NAV}
|
|
84
|
+
${TIER_BADGE}
|
|
85
|
+
<main class="max-w-5xl mx-auto p-6">
|
|
86
|
+
<div class="flex items-center gap-3 mb-2">
|
|
87
|
+
<h1 class="text-3xl font-bold"><%= model.name %></h1>
|
|
88
|
+
<% if (tier) { %><%- tierBadge(tier.tier) %><% } %>
|
|
55
89
|
</div>
|
|
56
|
-
|
|
57
|
-
<
|
|
58
|
-
|
|
90
|
+
<% if (model.description) { %>
|
|
91
|
+
<p class="text-gray-600 mb-4"><%= model.description %></p>
|
|
92
|
+
<% } %>
|
|
93
|
+
|
|
94
|
+
<div class="flex gap-3 mb-6 text-sm">
|
|
95
|
+
<a href="<%= basePath %>/models/<%= model.name %>/schema.html" class="text-blue-600 hover:underline">Schema Browser</a>
|
|
96
|
+
<a href="<%= basePath %>/models/<%= model.name %>/rules.html" class="text-blue-600 hover:underline">Rules & Queries</a>
|
|
59
97
|
</div>
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
98
|
+
|
|
99
|
+
<% if (gov) { %>
|
|
100
|
+
<section class="mb-6">
|
|
101
|
+
<h2 class="text-xl font-semibold mb-2">Governance</h2>
|
|
102
|
+
<table class="table-auto border-collapse text-sm">
|
|
103
|
+
<tbody>
|
|
104
|
+
<tr><td class="pr-4 font-medium">Owner</td><td><a href="<%= basePath %>/owners/<%= gov.owner %>.html" class="text-blue-600 underline"><%= gov.owner %></a></td></tr>
|
|
105
|
+
<% if (gov.trust) { %><tr><td class="pr-4 font-medium">Trust</td><td><%= gov.trust %></td></tr><% } %>
|
|
106
|
+
<% if (gov.security) { %><tr><td class="pr-4 font-medium">Security</td><td><%= gov.security %></td></tr><% } %>
|
|
107
|
+
<% if (gov.tags && gov.tags.length > 0) { %><tr><td class="pr-4 font-medium">Tags</td><td><%= gov.tags.join(', ') %></td></tr><% } %>
|
|
108
|
+
</tbody>
|
|
109
|
+
</table>
|
|
110
|
+
</section>
|
|
111
|
+
<% } %>
|
|
112
|
+
|
|
113
|
+
<% if (model.datasets && model.datasets.length > 0) { %>
|
|
114
|
+
<section class="mb-6">
|
|
115
|
+
<h2 class="text-xl font-semibold mb-2">Datasets</h2>
|
|
116
|
+
<div class="space-y-3">
|
|
117
|
+
<% for (var ds of model.datasets) { %>
|
|
118
|
+
<div class="border rounded p-3">
|
|
119
|
+
<div class="font-medium"><%= ds.name %></div>
|
|
120
|
+
<div class="text-xs text-gray-500">Source: <%= ds.source %></div>
|
|
121
|
+
<% if (ds.description) { %><p class="text-sm text-gray-600 mt-1"><%= ds.description %></p><% } %>
|
|
122
|
+
<% if (ds.fields && ds.fields.length > 0) { %>
|
|
123
|
+
<div class="text-xs text-gray-500 mt-1"><%= ds.fields.length %> field(s)</div>
|
|
124
|
+
<% } %>
|
|
125
|
+
</div>
|
|
126
|
+
<% } %>
|
|
127
|
+
</div>
|
|
128
|
+
</section>
|
|
129
|
+
<% } %>
|
|
130
|
+
|
|
131
|
+
<% if (model.relationships && model.relationships.length > 0) { %>
|
|
132
|
+
<section class="mb-6">
|
|
133
|
+
<h2 class="text-xl font-semibold mb-2">Relationships</h2>
|
|
134
|
+
<table class="table-auto w-full text-sm border-collapse">
|
|
135
|
+
<thead>
|
|
136
|
+
<tr class="border-b"><th class="text-left py-1 pr-3">Name</th><th class="text-left py-1 pr-3">From</th><th class="text-left py-1">To</th></tr>
|
|
137
|
+
</thead>
|
|
138
|
+
<tbody>
|
|
139
|
+
<% for (var rel of model.relationships) { %>
|
|
140
|
+
<tr class="border-b"><td class="py-1 pr-3"><%= rel.name %></td><td class="py-1 pr-3"><%= rel.from %></td><td class="py-1"><%= rel.to %></td></tr>
|
|
141
|
+
<% } %>
|
|
142
|
+
</tbody>
|
|
143
|
+
</table>
|
|
144
|
+
</section>
|
|
145
|
+
<% } %>
|
|
146
|
+
|
|
147
|
+
<% if (model.metrics && model.metrics.length > 0) { %>
|
|
148
|
+
<section class="mb-6">
|
|
149
|
+
<h2 class="text-xl font-semibold mb-2">Metrics</h2>
|
|
150
|
+
<div class="space-y-2">
|
|
151
|
+
<% for (var metric of model.metrics) { %>
|
|
152
|
+
<div class="border rounded p-3">
|
|
153
|
+
<div class="font-medium"><%= metric.name %></div>
|
|
154
|
+
<% if (metric.description) { %><p class="text-sm text-gray-600"><%= metric.description %></p><% } %>
|
|
155
|
+
</div>
|
|
156
|
+
<% } %>
|
|
157
|
+
</div>
|
|
158
|
+
</section>
|
|
159
|
+
<% } %>
|
|
160
|
+
|
|
161
|
+
<% if (tier) { %>
|
|
162
|
+
<section class="mb-6">
|
|
163
|
+
<h2 class="text-xl font-semibold mb-2">Tier Details</h2>
|
|
164
|
+
<% var tierLevels = ['bronze', 'silver', 'gold']; %>
|
|
165
|
+
<% for (var lvl of tierLevels) { %>
|
|
166
|
+
<div class="mb-3">
|
|
167
|
+
<h3 class="font-medium capitalize flex items-center gap-2">
|
|
168
|
+
<%= lvl %>
|
|
169
|
+
<% if (tier[lvl].passed) { %>
|
|
170
|
+
<span class="text-green-600 text-xs">Passed</span>
|
|
171
|
+
<% } else { %>
|
|
172
|
+
<span class="text-red-500 text-xs">Not passed</span>
|
|
173
|
+
<% } %>
|
|
174
|
+
</h3>
|
|
175
|
+
<% if (tier[lvl].checks && tier[lvl].checks.length > 0) { %>
|
|
176
|
+
<ul class="text-sm ml-4 list-disc">
|
|
177
|
+
<% for (var chk of tier[lvl].checks) { %>
|
|
178
|
+
<li class="<%= chk.passed ? 'text-green-700' : 'text-red-600' %>"><%= chk.label %><% if (chk.detail) { %> \u2014 <%= chk.detail %><% } %></li>
|
|
179
|
+
<% } %>
|
|
180
|
+
</ul>
|
|
181
|
+
<% } %>
|
|
182
|
+
</div>
|
|
183
|
+
<% } %>
|
|
184
|
+
</section>
|
|
185
|
+
<% } %>
|
|
186
|
+
</main>
|
|
187
|
+
${FOOTER}
|
|
188
|
+
</body>
|
|
189
|
+
</html>`;
|
|
190
|
+
var schemaTemplate = `${HEAD}
|
|
191
|
+
<body class="bg-white text-gray-900 min-h-screen">
|
|
192
|
+
${NAV}
|
|
193
|
+
${TIER_BADGE}
|
|
194
|
+
<main class="max-w-5xl mx-auto p-6">
|
|
195
|
+
<div class="flex items-center gap-3 mb-2">
|
|
196
|
+
<h1 class="text-3xl font-bold"><%= model.name %> \u2014 Schema Browser</h1>
|
|
197
|
+
<% if (tier) { %><%- tierBadge(tier.tier) %><% } %>
|
|
63
198
|
</div>
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
<% if (concepts.length > 0) { %>
|
|
67
|
-
<h2 class="text-xl font-semibold mb-3">Concepts</h2>
|
|
68
|
-
<ul class="mb-6 space-y-1">
|
|
69
|
-
<% concepts.forEach(function(c) { %>
|
|
70
|
-
<li><a href="<%= basePath %>/concepts/<%= c.id %>.html" class="text-blue-600 hover:underline"><%= c.id %></a>
|
|
71
|
-
<% if (c.certified) { %><span class="ml-1 text-xs bg-green-100 text-green-800 px-1.5 py-0.5 rounded">certified</span><% } %>
|
|
72
|
-
</li>
|
|
73
|
-
<% }); %>
|
|
74
|
-
</ul>
|
|
75
|
-
<% } %>
|
|
76
|
-
|
|
77
|
-
<% if (products.length > 0) { %>
|
|
78
|
-
<h2 class="text-xl font-semibold mb-3">Products</h2>
|
|
79
|
-
<ul class="mb-6 space-y-1">
|
|
80
|
-
<% products.forEach(function(p) { %>
|
|
81
|
-
<li><a href="<%= basePath %>/products/<%= p.id %>.html" class="text-blue-600 hover:underline"><%= p.id %></a></li>
|
|
82
|
-
<% }); %>
|
|
83
|
-
</ul>
|
|
84
|
-
<% } %>
|
|
85
|
-
|
|
86
|
-
<% if (policies.length > 0) { %>
|
|
87
|
-
<h2 class="text-xl font-semibold mb-3">Policies</h2>
|
|
88
|
-
<ul class="mb-6 space-y-1">
|
|
89
|
-
<% policies.forEach(function(p) { %>
|
|
90
|
-
<li><a href="<%= basePath %>/policies/<%= p.id %>.html" class="text-blue-600 hover:underline"><%= p.id %></a></li>
|
|
91
|
-
<% }); %>
|
|
92
|
-
</ul>
|
|
93
|
-
<% } %>
|
|
94
|
-
|
|
95
|
-
<% if (owners.length > 0) { %>
|
|
96
|
-
<h2 class="text-xl font-semibold mb-3">Owners</h2>
|
|
97
|
-
<ul class="mb-6 space-y-1">
|
|
98
|
-
<% owners.forEach(function(o) { %>
|
|
99
|
-
<li><a href="<%= basePath %>/owners/<%= o.id %>.html" class="text-blue-600 hover:underline"><%= o.displayName %></a></li>
|
|
100
|
-
<% }); %>
|
|
101
|
-
</ul>
|
|
102
|
-
<% } %>`;
|
|
103
|
-
var conceptTemplate = `<nav class="text-sm text-gray-500 mb-4">
|
|
104
|
-
<a href="<%= basePath %>/" class="hover:text-blue-600">Home</a> › Concepts › <%= concept.id %>
|
|
105
|
-
</nav>
|
|
106
|
-
|
|
107
|
-
<h1 class="text-3xl font-bold mb-2"><%= concept.id %>
|
|
108
|
-
<% if (concept.certified) { %><span class="ml-2 text-sm bg-green-100 text-green-800 px-2 py-0.5 rounded">certified</span><% } %>
|
|
109
|
-
</h1>
|
|
110
|
-
|
|
111
|
-
<p class="text-lg text-gray-700 mb-6"><%= concept.definition %></p>
|
|
112
|
-
|
|
113
|
-
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
|
|
114
|
-
<% if (concept.owner) { %>
|
|
115
|
-
<div class="bg-white rounded-lg shadow p-4">
|
|
116
|
-
<div class="text-sm text-gray-500">Owner</div>
|
|
117
|
-
<a href="<%= basePath %>/owners/<%= concept.owner %>.html" class="text-blue-600 hover:underline"><%= concept.owner %></a>
|
|
199
|
+
<div class="mb-4 text-sm">
|
|
200
|
+
<a href="<%= basePath %>/models/<%= model.name %>.html" class="text-blue-600 hover:underline">← Back to model</a>
|
|
118
201
|
</div>
|
|
202
|
+
|
|
203
|
+
<% if (model.datasets && model.datasets.length > 0) { %>
|
|
204
|
+
<% for (var ds of model.datasets) { %>
|
|
205
|
+
<section class="mb-8 border rounded-lg p-4">
|
|
206
|
+
<h2 class="text-xl font-semibold mb-1"><%= ds.name %></h2>
|
|
207
|
+
<div class="text-xs text-gray-500 mb-2">Source: <%= ds.source %></div>
|
|
208
|
+
<% if (ds.description) { %><p class="text-sm text-gray-600 mb-3"><%= ds.description %></p><% } %>
|
|
209
|
+
|
|
210
|
+
<% var dsGov = gov && gov.datasets && gov.datasets[ds.name]; %>
|
|
211
|
+
<% if (dsGov) { %>
|
|
212
|
+
<div class="text-xs text-gray-500 mb-3 flex gap-4">
|
|
213
|
+
<% if (dsGov.grain) { %><span>Grain: <strong><%= dsGov.grain %></strong></span><% } %>
|
|
214
|
+
<% if (dsGov.table_type) { %><span>Type: <strong><%= dsGov.table_type %></strong></span><% } %>
|
|
215
|
+
<% if (dsGov.refresh) { %><span>Refresh: <strong><%= dsGov.refresh %></strong></span><% } %>
|
|
216
|
+
<% if (dsGov.security) { %><span>Security: <strong><%= dsGov.security %></strong></span><% } %>
|
|
217
|
+
</div>
|
|
218
|
+
<% } %>
|
|
219
|
+
|
|
220
|
+
<% if (ds.fields && ds.fields.length > 0) { %>
|
|
221
|
+
<table class="table-auto w-full text-sm border-collapse">
|
|
222
|
+
<thead>
|
|
223
|
+
<tr class="border-b bg-gray-50">
|
|
224
|
+
<th class="text-left py-2 px-2">Field</th>
|
|
225
|
+
<th class="text-left py-2 px-2">Description</th>
|
|
226
|
+
<th class="text-left py-2 px-2">Semantic Role</th>
|
|
227
|
+
<th class="text-left py-2 px-2">Aggregation</th>
|
|
228
|
+
</tr>
|
|
229
|
+
</thead>
|
|
230
|
+
<tbody>
|
|
231
|
+
<% for (var field of ds.fields) { %>
|
|
232
|
+
<% var fieldKey = ds.name + '.' + field.name; %>
|
|
233
|
+
<% var fGov = gov && gov.fields && gov.fields[fieldKey]; %>
|
|
234
|
+
<tr class="border-b">
|
|
235
|
+
<td class="py-1 px-2 font-mono text-xs"><%= field.name %></td>
|
|
236
|
+
<td class="py-1 px-2"><%= field.description || '' %></td>
|
|
237
|
+
<td class="py-1 px-2"><%= fGov && fGov.semantic_role ? fGov.semantic_role : '' %></td>
|
|
238
|
+
<td class="py-1 px-2"><%= fGov && fGov.default_aggregation ? fGov.default_aggregation : '' %></td>
|
|
239
|
+
</tr>
|
|
240
|
+
<% } %>
|
|
241
|
+
</tbody>
|
|
242
|
+
</table>
|
|
243
|
+
<% } else { %>
|
|
244
|
+
<p class="text-gray-400 text-sm">No fields defined.</p>
|
|
245
|
+
<% } %>
|
|
246
|
+
</section>
|
|
247
|
+
<% } %>
|
|
248
|
+
<% } else { %>
|
|
249
|
+
<p class="text-gray-500">No datasets found.</p>
|
|
119
250
|
<% } %>
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
251
|
+
</main>
|
|
252
|
+
${FOOTER}
|
|
253
|
+
</body>
|
|
254
|
+
</html>`;
|
|
255
|
+
var rulesTemplate = `${HEAD}
|
|
256
|
+
<body class="bg-white text-gray-900 min-h-screen">
|
|
257
|
+
${NAV}
|
|
258
|
+
<main class="max-w-5xl mx-auto p-6">
|
|
259
|
+
<h1 class="text-3xl font-bold mb-2"><%= modelName %> \u2014 Rules & Queries</h1>
|
|
260
|
+
<div class="mb-4 text-sm">
|
|
261
|
+
<a href="<%= basePath %>/models/<%= modelName %>.html" class="text-blue-600 hover:underline">← Back to model</a>
|
|
124
262
|
</div>
|
|
263
|
+
|
|
264
|
+
<% if (rules && rules.golden_queries && rules.golden_queries.length > 0) { %>
|
|
265
|
+
<section class="mb-8">
|
|
266
|
+
<h2 class="text-xl font-semibold mb-3">Golden Queries</h2>
|
|
267
|
+
<div class="space-y-4">
|
|
268
|
+
<% for (var gq of rules.golden_queries) { %>
|
|
269
|
+
<div class="border rounded-lg p-4">
|
|
270
|
+
<div class="font-medium mb-2"><%= gq.question %></div>
|
|
271
|
+
<pre class="bg-gray-100 rounded p-3 text-sm overflow-x-auto"><code><%= gq.sql %></code></pre>
|
|
272
|
+
<% if (gq.dialect) { %><div class="text-xs text-gray-500 mt-1">Dialect: <%= gq.dialect %></div><% } %>
|
|
273
|
+
<% if (gq.tags && gq.tags.length > 0) { %><div class="text-xs text-gray-500 mt-1">Tags: <%= gq.tags.join(', ') %></div><% } %>
|
|
274
|
+
</div>
|
|
275
|
+
<% } %>
|
|
276
|
+
</div>
|
|
277
|
+
</section>
|
|
125
278
|
<% } %>
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
<
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
<%
|
|
136
|
-
|
|
137
|
-
<%
|
|
138
|
-
|
|
139
|
-
<
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
</
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
<
|
|
150
|
-
<
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
<div class="
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
</div>
|
|
157
|
-
<% } %>
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
<%
|
|
163
|
-
<
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
<%
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
<
|
|
170
|
-
<
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
</
|
|
175
|
-
<% }
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
<h1 class="text-3xl font-bold mb-2"><%= policy.id %></h1>
|
|
181
|
-
<p class="text-lg text-gray-700 mb-6"><%= policy.description %></p>
|
|
182
|
-
|
|
183
|
-
<% if (policy.owner) { %>
|
|
184
|
-
<div class="mb-4">
|
|
185
|
-
<span class="text-sm text-gray-500 mr-2">Owner:</span>
|
|
186
|
-
<a href="<%= basePath %>/owners/<%= policy.owner %>.html" class="text-blue-600 hover:underline"><%= policy.owner %></a>
|
|
187
|
-
</div>
|
|
188
|
-
<% } %>
|
|
189
|
-
|
|
190
|
-
<% if (policy.tags && policy.tags.length > 0) { %>
|
|
191
|
-
<div class="mb-4">
|
|
192
|
-
<span class="text-sm text-gray-500 mr-2">Tags:</span>
|
|
193
|
-
<% policy.tags.forEach(function(tag) { %>
|
|
194
|
-
<span class="inline-block bg-gray-100 text-gray-700 text-xs px-2 py-0.5 rounded mr-1"><%= tag %></span>
|
|
195
|
-
<% }); %>
|
|
196
|
-
</div>
|
|
197
|
-
<% } %>
|
|
198
|
-
|
|
199
|
-
<% if (policy.rules && policy.rules.length > 0) { %>
|
|
200
|
-
<h2 class="text-xl font-semibold mb-3">Rules</h2>
|
|
201
|
-
<div class="overflow-x-auto">
|
|
202
|
-
<table class="min-w-full bg-white rounded-lg shadow text-sm">
|
|
203
|
-
<thead>
|
|
204
|
-
<tr class="bg-gray-50 text-left">
|
|
205
|
-
<th class="px-4 py-2">Priority</th>
|
|
206
|
-
<th class="px-4 py-2">When</th>
|
|
207
|
-
<th class="px-4 py-2">Then</th>
|
|
208
|
-
</tr>
|
|
209
|
-
</thead>
|
|
210
|
-
<tbody>
|
|
211
|
-
<% policy.rules.forEach(function(rule) { %>
|
|
212
|
-
<tr class="border-t">
|
|
213
|
-
<td class="px-4 py-2"><%= rule.priority %></td>
|
|
214
|
-
<td class="px-4 py-2">
|
|
215
|
-
<% if (rule.when.tagsAny) { %>tags: <%= rule.when.tagsAny.join(', ') %><% } %>
|
|
216
|
-
<% if (rule.when.conceptIds) { %>concepts: <%= rule.when.conceptIds.join(', ') %><% } %>
|
|
217
|
-
<% if (rule.when.status) { %>status: <%= rule.when.status %><% } %>
|
|
218
|
-
</td>
|
|
219
|
-
<td class="px-4 py-2">
|
|
220
|
-
<% if (rule.then.requireRole) { %>require role: <%= rule.then.requireRole %><% } %>
|
|
221
|
-
<% if (rule.then.deny) { %>deny<% } %>
|
|
222
|
-
<% if (rule.then.warn) { %>warn: <%= rule.then.warn %><% } %>
|
|
223
|
-
</td>
|
|
224
|
-
</tr>
|
|
225
|
-
<% }); %>
|
|
226
|
-
</tbody>
|
|
227
|
-
</table>
|
|
228
|
-
</div>
|
|
229
|
-
<% } %>`;
|
|
230
|
-
var ownerTemplate = `<nav class="text-sm text-gray-500 mb-4">
|
|
231
|
-
<a href="<%= basePath %>/" class="hover:text-blue-600">Home</a> › Owners › <%= owner.id %>
|
|
232
|
-
</nav>
|
|
233
|
-
|
|
234
|
-
<h1 class="text-3xl font-bold mb-2"><%= owner.displayName %></h1>
|
|
235
|
-
|
|
236
|
-
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
|
|
237
|
-
<div class="bg-white rounded-lg shadow p-4">
|
|
238
|
-
<div class="text-sm text-gray-500">ID</div>
|
|
239
|
-
<div><%= owner.id %></div>
|
|
240
|
-
</div>
|
|
241
|
-
<% if (owner.email) { %>
|
|
242
|
-
<div class="bg-white rounded-lg shadow p-4">
|
|
243
|
-
<div class="text-sm text-gray-500">Email</div>
|
|
244
|
-
<div><a href="mailto:<%= owner.email %>" class="text-blue-600 hover:underline"><%= owner.email %></a></div>
|
|
245
|
-
</div>
|
|
279
|
+
|
|
280
|
+
<% if (rules && rules.business_rules && rules.business_rules.length > 0) { %>
|
|
281
|
+
<section class="mb-8">
|
|
282
|
+
<h2 class="text-xl font-semibold mb-3">Business Rules</h2>
|
|
283
|
+
<div class="space-y-3">
|
|
284
|
+
<% for (var br of rules.business_rules) { %>
|
|
285
|
+
<div class="border rounded p-4">
|
|
286
|
+
<div class="font-medium"><%= br.name %></div>
|
|
287
|
+
<p class="text-sm text-gray-600 mt-1"><%= br.definition %></p>
|
|
288
|
+
<% if (br.enforcement && br.enforcement.length > 0) { %>
|
|
289
|
+
<div class="text-xs text-gray-500 mt-1">Enforcement: <%= br.enforcement.join(', ') %></div>
|
|
290
|
+
<% } %>
|
|
291
|
+
<% if (br.avoid && br.avoid.length > 0) { %>
|
|
292
|
+
<div class="text-xs text-red-500 mt-1">Avoid: <%= br.avoid.join(', ') %></div>
|
|
293
|
+
<% } %>
|
|
294
|
+
</div>
|
|
295
|
+
<% } %>
|
|
296
|
+
</div>
|
|
297
|
+
</section>
|
|
298
|
+
<% } %>
|
|
299
|
+
|
|
300
|
+
<% if (rules && rules.guardrail_filters && rules.guardrail_filters.length > 0) { %>
|
|
301
|
+
<section class="mb-8">
|
|
302
|
+
<h2 class="text-xl font-semibold mb-3">Guardrail Filters</h2>
|
|
303
|
+
<div class="space-y-3">
|
|
304
|
+
<% for (var gf of rules.guardrail_filters) { %>
|
|
305
|
+
<div class="border rounded p-4">
|
|
306
|
+
<div class="font-medium"><%= gf.name %></div>
|
|
307
|
+
<pre class="bg-gray-100 rounded p-2 text-sm mt-1"><code><%= gf.filter %></code></pre>
|
|
308
|
+
<p class="text-sm text-gray-600 mt-1"><%= gf.reason %></p>
|
|
309
|
+
</div>
|
|
310
|
+
<% } %>
|
|
311
|
+
</div>
|
|
312
|
+
</section>
|
|
313
|
+
<% } %>
|
|
314
|
+
|
|
315
|
+
<% if (rules && rules.hierarchies && rules.hierarchies.length > 0) { %>
|
|
316
|
+
<section class="mb-8">
|
|
317
|
+
<h2 class="text-xl font-semibold mb-3">Hierarchies</h2>
|
|
318
|
+
<div class="space-y-3">
|
|
319
|
+
<% for (var h of rules.hierarchies) { %>
|
|
320
|
+
<div class="border rounded p-4">
|
|
321
|
+
<div class="font-medium"><%= h.name %></div>
|
|
322
|
+
<div class="text-sm text-gray-600 mt-1">Dataset: <%= h.dataset %></div>
|
|
323
|
+
<div class="text-sm text-gray-600">Levels: <%= h.levels.join(' → ') %></div>
|
|
324
|
+
</div>
|
|
325
|
+
<% } %>
|
|
326
|
+
</div>
|
|
327
|
+
</section>
|
|
328
|
+
<% } %>
|
|
329
|
+
|
|
330
|
+
<% if (!rules || ((!rules.golden_queries || rules.golden_queries.length === 0) && (!rules.business_rules || rules.business_rules.length === 0) && (!rules.guardrail_filters || rules.guardrail_filters.length === 0) && (!rules.hierarchies || rules.hierarchies.length === 0))) { %>
|
|
331
|
+
<p class="text-gray-500">No rules or queries defined for this model.</p>
|
|
246
332
|
<% } %>
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
333
|
+
</main>
|
|
334
|
+
${FOOTER}
|
|
335
|
+
</body>
|
|
336
|
+
</html>`;
|
|
337
|
+
var glossaryTemplate = `${HEAD}
|
|
338
|
+
<body class="bg-white text-gray-900 min-h-screen">
|
|
339
|
+
${NAV}
|
|
340
|
+
<main class="max-w-5xl mx-auto p-6">
|
|
341
|
+
<h1 class="text-3xl font-bold mb-6">Glossary</h1>
|
|
342
|
+
|
|
343
|
+
<% var termIds = Object.keys(terms).sort(); %>
|
|
344
|
+
<% if (termIds.length === 0) { %>
|
|
345
|
+
<p class="text-gray-500">No terms defined.</p>
|
|
346
|
+
<% } else { %>
|
|
347
|
+
<div class="space-y-4">
|
|
348
|
+
<% for (var tid of termIds) { %>
|
|
349
|
+
<% var term = terms[tid]; %>
|
|
350
|
+
<div class="border rounded-lg p-4" id="term-<%= tid %>">
|
|
351
|
+
<h2 class="text-lg font-semibold"><%= tid %></h2>
|
|
352
|
+
<p class="text-gray-700 mt-1"><%= term.definition %></p>
|
|
353
|
+
<% if (term.synonyms && term.synonyms.length > 0) { %>
|
|
354
|
+
<div class="text-sm text-gray-500 mt-1">Synonyms: <%= term.synonyms.join(', ') %></div>
|
|
355
|
+
<% } %>
|
|
356
|
+
<% if (term.maps_to && term.maps_to.length > 0) { %>
|
|
357
|
+
<div class="text-sm text-gray-500 mt-1">Maps to: <%= term.maps_to.join(', ') %></div>
|
|
358
|
+
<% } %>
|
|
359
|
+
<% if (term.owner) { %>
|
|
360
|
+
<div class="text-sm text-gray-500 mt-1">Owner: <a href="<%= basePath %>/owners/<%= term.owner %>.html" class="text-blue-600 underline"><%= term.owner %></a></div>
|
|
361
|
+
<% } %>
|
|
362
|
+
</div>
|
|
363
|
+
<% } %>
|
|
364
|
+
</div>
|
|
365
|
+
<% } %>
|
|
366
|
+
</main>
|
|
367
|
+
${FOOTER}
|
|
368
|
+
</body>
|
|
369
|
+
</html>`;
|
|
370
|
+
var ownerTemplate = `${HEAD}
|
|
371
|
+
<body class="bg-white text-gray-900 min-h-screen">
|
|
372
|
+
${NAV}
|
|
373
|
+
${TIER_BADGE}
|
|
374
|
+
<main class="max-w-5xl mx-auto p-6">
|
|
375
|
+
<h1 class="text-3xl font-bold mb-2"><%= owner.display_name %></h1>
|
|
376
|
+
<div class="text-sm text-gray-500 mb-4">
|
|
377
|
+
<% if (owner.email) { %><span>Email: <%= owner.email %></span><% } %>
|
|
378
|
+
<% if (owner.team) { %><span class="ml-4">Team: <%= owner.team %></span><% } %>
|
|
251
379
|
</div>
|
|
380
|
+
<% if (owner.description) { %>
|
|
381
|
+
<p class="text-gray-600 mb-4"><%= owner.description %></p>
|
|
252
382
|
<% } %>
|
|
253
|
-
</div>
|
|
254
|
-
|
|
255
|
-
<% if (ownedNodes.length > 0) { %>
|
|
256
|
-
<h2 class="text-xl font-semibold mb-3">Owned Nodes</h2>
|
|
257
|
-
<ul class="space-y-1">
|
|
258
|
-
<% ownedNodes.forEach(function(node) { %>
|
|
259
|
-
<li>
|
|
260
|
-
<span class="inline-block bg-gray-100 text-xs px-1.5 py-0.5 rounded mr-1"><%= node.kind %></span>
|
|
261
|
-
<a href="<%= node.href %>" class="text-blue-600 hover:underline"><%= node.id %></a>
|
|
262
|
-
</li>
|
|
263
|
-
<% }); %>
|
|
264
|
-
</ul>
|
|
265
|
-
<% } %>`;
|
|
266
|
-
var glossaryTemplate = `<nav class="text-sm text-gray-500 mb-4">
|
|
267
|
-
<a href="<%= basePath %>/" class="hover:text-blue-600">Home</a> › Glossary
|
|
268
|
-
</nav>
|
|
269
|
-
|
|
270
|
-
<h1 class="text-3xl font-bold mb-6">Glossary</h1>
|
|
271
|
-
|
|
272
|
-
<% if (terms.length === 0) { %>
|
|
273
|
-
<p class="text-gray-500">No terms defined.</p>
|
|
274
|
-
<% } else { %>
|
|
275
|
-
<div class="overflow-x-auto">
|
|
276
|
-
<table class="min-w-full bg-white rounded-lg shadow text-sm">
|
|
277
|
-
<thead>
|
|
278
|
-
<tr class="bg-gray-50 text-left">
|
|
279
|
-
<th class="px-4 py-2">Term</th>
|
|
280
|
-
<th class="px-4 py-2">Definition</th>
|
|
281
|
-
<th class="px-4 py-2">Synonyms</th>
|
|
282
|
-
<th class="px-4 py-2">Maps To</th>
|
|
283
|
-
</tr>
|
|
284
|
-
</thead>
|
|
285
|
-
<tbody>
|
|
286
|
-
<% terms.forEach(function(term) { %>
|
|
287
|
-
<tr class="border-t">
|
|
288
|
-
<td class="px-4 py-2 font-medium"><%= term.id %></td>
|
|
289
|
-
<td class="px-4 py-2"><%= term.definition %></td>
|
|
290
|
-
<td class="px-4 py-2"><%= (term.synonyms || []).join(', ') %></td>
|
|
291
|
-
<td class="px-4 py-2">
|
|
292
|
-
<% (term.mapsTo || []).forEach(function(target) { %>
|
|
293
|
-
<a href="<%= basePath %>/concepts/<%= target %>.html" class="text-blue-600 hover:underline"><%= target %></a><%= ' ' %>
|
|
294
|
-
<% }); %>
|
|
295
|
-
</td>
|
|
296
|
-
</tr>
|
|
297
|
-
<% }); %>
|
|
298
|
-
</tbody>
|
|
299
|
-
</table>
|
|
300
|
-
</div>
|
|
301
|
-
<% } %>`;
|
|
302
|
-
var searchTemplate = `<nav class="text-sm text-gray-500 mb-4">
|
|
303
|
-
<a href="<%= basePath %>/" class="hover:text-blue-600">Home</a> › Search
|
|
304
|
-
</nav>
|
|
305
|
-
|
|
306
|
-
<h1 class="text-3xl font-bold mb-6">Search</h1>
|
|
307
|
-
|
|
308
|
-
<input
|
|
309
|
-
id="search-input"
|
|
310
|
-
type="text"
|
|
311
|
-
placeholder="Search concepts, products, policies, terms..."
|
|
312
|
-
class="w-full border border-gray-300 rounded-lg px-4 py-2 mb-6 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
313
|
-
>
|
|
314
|
-
|
|
315
|
-
<div id="search-results" class="space-y-3"></div>
|
|
316
|
-
|
|
317
|
-
<script>window.__CONTEXTKIT_BASE_PATH__ = '<%= basePath %>';</script>
|
|
318
|
-
<script src="<%= basePath %>/search.js"></script>`;
|
|
319
383
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
384
|
+
<% if (governedModels.length > 0) { %>
|
|
385
|
+
<section>
|
|
386
|
+
<h2 class="text-xl font-semibold mb-3">Governed Models</h2>
|
|
387
|
+
<div class="space-y-2">
|
|
388
|
+
<% for (var gm of governedModels) { %>
|
|
389
|
+
<div class="flex items-center gap-3">
|
|
390
|
+
<a href="<%= basePath %>/models/<%= gm.name %>.html" class="text-blue-600 hover:underline"><%= gm.name %></a>
|
|
391
|
+
<% if (gm.tier) { %><%- tierBadge(gm.tier) %><% } %>
|
|
392
|
+
</div>
|
|
393
|
+
<% } %>
|
|
394
|
+
</div>
|
|
395
|
+
</section>
|
|
396
|
+
<% } else { %>
|
|
397
|
+
<p class="text-gray-500">No models governed by this owner.</p>
|
|
398
|
+
<% } %>
|
|
399
|
+
</main>
|
|
400
|
+
${FOOTER}
|
|
401
|
+
</body>
|
|
402
|
+
</html>`;
|
|
403
|
+
var searchTemplate = `${HEAD}
|
|
404
|
+
<body class="bg-white text-gray-900 min-h-screen">
|
|
405
|
+
${NAV}
|
|
406
|
+
<main class="max-w-5xl mx-auto p-6">
|
|
407
|
+
<h1 class="text-3xl font-bold mb-6">Search</h1>
|
|
408
|
+
|
|
409
|
+
<input type="text" id="search-input"
|
|
410
|
+
placeholder="Search models, datasets, terms..."
|
|
411
|
+
class="w-full border rounded-lg px-4 py-2 mb-4 focus:outline-none focus:ring-2 focus:ring-blue-500" />
|
|
412
|
+
|
|
413
|
+
<div id="search-results" class="space-y-2"></div>
|
|
414
|
+
</main>
|
|
415
|
+
${FOOTER}
|
|
416
|
+
|
|
417
|
+
<script src="https://cdn.jsdelivr.net/npm/minisearch@7.1.0/dist/umd/index.min.js"></script>
|
|
418
|
+
<script>
|
|
419
|
+
(function() {
|
|
420
|
+
var indexData = <%- searchIndexJson %>;
|
|
421
|
+
var miniSearch = MiniSearch.loadJSON(JSON.stringify(indexData.index), indexData.options);
|
|
422
|
+
|
|
423
|
+
var input = document.getElementById('search-input');
|
|
424
|
+
var resultsContainer = document.getElementById('search-results');
|
|
425
|
+
var docs = indexData.documents;
|
|
426
|
+
|
|
427
|
+
function clearResults() {
|
|
428
|
+
while (resultsContainer.firstChild) {
|
|
429
|
+
resultsContainer.removeChild(resultsContainer.firstChild);
|
|
330
430
|
}
|
|
331
|
-
});
|
|
332
|
-
const documents = [];
|
|
333
|
-
for (const concept of manifest.concepts) {
|
|
334
|
-
documents.push({
|
|
335
|
-
id: concept.id,
|
|
336
|
-
kind: "concept",
|
|
337
|
-
text: concept.definition,
|
|
338
|
-
tags: (concept.tags ?? []).join(" ")
|
|
339
|
-
});
|
|
340
431
|
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
432
|
+
|
|
433
|
+
function createResultElement(doc) {
|
|
434
|
+
var wrapper = document.createElement('div');
|
|
435
|
+
wrapper.className = 'border rounded p-3';
|
|
436
|
+
|
|
437
|
+
var link = document.createElement('a');
|
|
438
|
+
link.href = doc.url;
|
|
439
|
+
link.className = 'text-blue-600 font-medium hover:underline';
|
|
440
|
+
link.textContent = doc.title;
|
|
441
|
+
wrapper.appendChild(link);
|
|
442
|
+
|
|
443
|
+
var badge = document.createElement('span');
|
|
444
|
+
badge.className = 'ml-2 text-xs text-gray-400 uppercase';
|
|
445
|
+
badge.textContent = doc.type;
|
|
446
|
+
wrapper.appendChild(badge);
|
|
447
|
+
|
|
448
|
+
if (doc.description) {
|
|
449
|
+
var desc = document.createElement('p');
|
|
450
|
+
desc.className = 'text-sm text-gray-600 mt-1';
|
|
451
|
+
desc.textContent = doc.description;
|
|
452
|
+
wrapper.appendChild(desc);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
return wrapper;
|
|
348
456
|
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
457
|
+
|
|
458
|
+
input.addEventListener('input', function() {
|
|
459
|
+
var query = input.value.trim();
|
|
460
|
+
clearResults();
|
|
461
|
+
if (!query) return;
|
|
462
|
+
var hits = miniSearch.search(query, { prefix: true, fuzzy: 0.2 });
|
|
463
|
+
if (hits.length === 0) {
|
|
464
|
+
var noResults = document.createElement('p');
|
|
465
|
+
noResults.className = 'text-gray-500';
|
|
466
|
+
noResults.textContent = 'No results found.';
|
|
467
|
+
resultsContainer.appendChild(noResults);
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
hits.slice(0, 20).forEach(function(hit) {
|
|
471
|
+
var doc = docs[hit.id];
|
|
472
|
+
if (doc) {
|
|
473
|
+
resultsContainer.appendChild(createResultElement(doc));
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
});
|
|
477
|
+
})();
|
|
478
|
+
</script>
|
|
479
|
+
</body>
|
|
480
|
+
</html>`;
|
|
481
|
+
|
|
482
|
+
// src/search/build-index.ts
|
|
483
|
+
import MiniSearch from "minisearch";
|
|
484
|
+
var MINISEARCH_OPTIONS = {
|
|
485
|
+
fields: ["title", "description", "type"],
|
|
486
|
+
storeFields: ["title", "description", "type", "url"],
|
|
487
|
+
idField: "id"
|
|
488
|
+
};
|
|
489
|
+
function buildSearchIndex(manifest, basePath) {
|
|
490
|
+
const docs = [];
|
|
491
|
+
let idCounter = 0;
|
|
492
|
+
for (const [name, model] of Object.entries(manifest.models)) {
|
|
493
|
+
docs.push({
|
|
494
|
+
id: String(idCounter++),
|
|
495
|
+
type: "model",
|
|
496
|
+
title: name,
|
|
497
|
+
description: model.description ?? "",
|
|
498
|
+
url: `${basePath}/models/${name}.html`
|
|
355
499
|
});
|
|
500
|
+
if (model.datasets) {
|
|
501
|
+
for (const ds of model.datasets) {
|
|
502
|
+
docs.push({
|
|
503
|
+
id: String(idCounter++),
|
|
504
|
+
type: "dataset",
|
|
505
|
+
title: `${name} / ${ds.name}`,
|
|
506
|
+
description: ds.description ?? "",
|
|
507
|
+
url: `${basePath}/models/${name}/schema.html`
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
}
|
|
356
511
|
}
|
|
357
|
-
for (const
|
|
358
|
-
|
|
359
|
-
id:
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
512
|
+
for (const [termId, term] of Object.entries(manifest.terms)) {
|
|
513
|
+
docs.push({
|
|
514
|
+
id: String(idCounter++),
|
|
515
|
+
type: "term",
|
|
516
|
+
title: termId,
|
|
517
|
+
description: term.definition,
|
|
518
|
+
url: `${basePath}/glossary.html#term-${termId}`
|
|
363
519
|
});
|
|
364
520
|
}
|
|
365
|
-
for (const
|
|
366
|
-
|
|
367
|
-
id:
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
521
|
+
for (const [oid, owner] of Object.entries(manifest.owners)) {
|
|
522
|
+
docs.push({
|
|
523
|
+
id: String(idCounter++),
|
|
524
|
+
type: "owner",
|
|
525
|
+
title: owner.display_name,
|
|
526
|
+
description: owner.description ?? "",
|
|
527
|
+
url: `${basePath}/owners/${oid}.html`
|
|
371
528
|
});
|
|
372
529
|
}
|
|
373
|
-
miniSearch
|
|
374
|
-
|
|
530
|
+
const miniSearch = new MiniSearch(MINISEARCH_OPTIONS);
|
|
531
|
+
miniSearch.addAll(docs);
|
|
532
|
+
const documentsMap = {};
|
|
533
|
+
for (const doc of docs) {
|
|
534
|
+
documentsMap[doc.id] = doc;
|
|
535
|
+
}
|
|
536
|
+
return {
|
|
537
|
+
index: JSON.parse(JSON.stringify(miniSearch)),
|
|
538
|
+
options: MINISEARCH_OPTIONS,
|
|
539
|
+
documents: documentsMap
|
|
540
|
+
};
|
|
375
541
|
}
|
|
376
542
|
|
|
377
543
|
// src/generator.ts
|
|
378
|
-
function
|
|
379
|
-
const
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
build: manifest.build,
|
|
544
|
+
function generateSite(manifest, config) {
|
|
545
|
+
const files = /* @__PURE__ */ new Map();
|
|
546
|
+
const siteTitle = config?.title ?? "ContextKit";
|
|
547
|
+
const basePath = (config?.base_path ?? "").replace(/\/+$/, "");
|
|
548
|
+
const commonData = {
|
|
549
|
+
siteTitle,
|
|
385
550
|
basePath
|
|
386
|
-
});
|
|
387
|
-
}
|
|
388
|
-
async function writeOutputFile(filePath, content) {
|
|
389
|
-
await mkdir(dirname(filePath), { recursive: true });
|
|
390
|
-
await writeFile(filePath, content, "utf-8");
|
|
391
|
-
}
|
|
392
|
-
function resolveAssetPath(filename) {
|
|
393
|
-
const currentDir = dirname(fileURLToPath(import.meta.url));
|
|
394
|
-
return join(currentDir, "assets", filename);
|
|
395
|
-
}
|
|
396
|
-
async function generateSite(options) {
|
|
397
|
-
const { manifest, outputDir, title, basePath = "" } = options;
|
|
398
|
-
const projectData = {
|
|
399
|
-
...manifest.project,
|
|
400
|
-
displayName: title ?? manifest.project.displayName
|
|
401
551
|
};
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
project: manifestWithTitle.project,
|
|
411
|
-
concepts: manifest.concepts,
|
|
412
|
-
products: manifest.products,
|
|
413
|
-
policies: manifest.policies,
|
|
414
|
-
entities: manifest.entities,
|
|
415
|
-
terms: manifest.terms,
|
|
552
|
+
files.set(
|
|
553
|
+
"index.html",
|
|
554
|
+
ejs.render(indexTemplate, {
|
|
555
|
+
...commonData,
|
|
556
|
+
pageTitle: "Home",
|
|
557
|
+
models: manifest.models,
|
|
558
|
+
governance: manifest.governance,
|
|
559
|
+
tiers: manifest.tiers,
|
|
416
560
|
owners: manifest.owners
|
|
417
|
-
}
|
|
418
|
-
"Home",
|
|
419
|
-
manifestWithTitle,
|
|
420
|
-
basePath
|
|
561
|
+
})
|
|
421
562
|
);
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
const
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
563
|
+
for (const [name, model] of Object.entries(manifest.models)) {
|
|
564
|
+
const gov = manifest.governance[name] ?? null;
|
|
565
|
+
const tier = manifest.tiers[name] ?? null;
|
|
566
|
+
const rules = manifest.rules[name] ?? null;
|
|
567
|
+
files.set(
|
|
568
|
+
`models/${name}.html`,
|
|
569
|
+
ejs.render(modelTemplate, {
|
|
570
|
+
...commonData,
|
|
571
|
+
pageTitle: name,
|
|
572
|
+
model,
|
|
573
|
+
gov,
|
|
574
|
+
tier,
|
|
575
|
+
rules
|
|
576
|
+
})
|
|
436
577
|
);
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
578
|
+
files.set(
|
|
579
|
+
`models/${name}/schema.html`,
|
|
580
|
+
ejs.render(schemaTemplate, {
|
|
581
|
+
...commonData,
|
|
582
|
+
pageTitle: `${name} \u2014 Schema`,
|
|
583
|
+
model,
|
|
584
|
+
gov,
|
|
585
|
+
tier
|
|
586
|
+
})
|
|
443
587
|
);
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
basePath
|
|
588
|
+
files.set(
|
|
589
|
+
`models/${name}/rules.html`,
|
|
590
|
+
ejs.render(rulesTemplate, {
|
|
591
|
+
...commonData,
|
|
592
|
+
pageTitle: `${name} \u2014 Rules`,
|
|
593
|
+
modelName: name,
|
|
594
|
+
rules
|
|
595
|
+
})
|
|
453
596
|
);
|
|
454
|
-
await writeOutputFile(join(outputDir, "policies", `${policy.id}.html`), html);
|
|
455
597
|
}
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
href: `${basePath}/products/${product.id}.html`
|
|
473
|
-
});
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
for (const policy of manifest.policies) {
|
|
477
|
-
if (policy.owner === owner.id) {
|
|
478
|
-
ownedNodes.push({
|
|
479
|
-
id: policy.id,
|
|
480
|
-
kind: "policy",
|
|
481
|
-
href: `${basePath}/policies/${policy.id}.html`
|
|
482
|
-
});
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
for (const entity of manifest.entities) {
|
|
486
|
-
if (entity.owner === owner.id) {
|
|
487
|
-
ownedNodes.push({
|
|
488
|
-
id: entity.id,
|
|
489
|
-
kind: "entity",
|
|
490
|
-
href: `${basePath}/`
|
|
491
|
-
});
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
for (const term of manifest.terms) {
|
|
495
|
-
if (term.owner === owner.id) {
|
|
496
|
-
ownedNodes.push({
|
|
497
|
-
id: term.id,
|
|
498
|
-
kind: "term",
|
|
499
|
-
href: `${basePath}/glossary.html`
|
|
598
|
+
files.set(
|
|
599
|
+
"glossary.html",
|
|
600
|
+
ejs.render(glossaryTemplate, {
|
|
601
|
+
...commonData,
|
|
602
|
+
pageTitle: "Glossary",
|
|
603
|
+
terms: manifest.terms
|
|
604
|
+
})
|
|
605
|
+
);
|
|
606
|
+
for (const [oid, owner] of Object.entries(manifest.owners)) {
|
|
607
|
+
const governedModels = [];
|
|
608
|
+
for (const [modelName, gov] of Object.entries(manifest.governance)) {
|
|
609
|
+
if (gov.owner === oid) {
|
|
610
|
+
const tierScore = manifest.tiers[modelName];
|
|
611
|
+
governedModels.push({
|
|
612
|
+
name: modelName,
|
|
613
|
+
tier: tierScore?.tier ?? null
|
|
500
614
|
});
|
|
501
615
|
}
|
|
502
616
|
}
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
617
|
+
files.set(
|
|
618
|
+
`owners/${oid}.html`,
|
|
619
|
+
ejs.render(ownerTemplate, {
|
|
620
|
+
...commonData,
|
|
621
|
+
pageTitle: owner.display_name,
|
|
622
|
+
owner,
|
|
623
|
+
governedModels
|
|
624
|
+
})
|
|
509
625
|
);
|
|
510
|
-
await writeOutputFile(join(outputDir, "owners", `${owner.id}.html`), html);
|
|
511
626
|
}
|
|
512
|
-
const
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
const searchHtml = renderPage(
|
|
521
|
-
searchTemplate,
|
|
522
|
-
{},
|
|
523
|
-
"Search",
|
|
524
|
-
manifestWithTitle,
|
|
525
|
-
basePath
|
|
627
|
+
const searchIndex = buildSearchIndex(manifest, basePath);
|
|
628
|
+
files.set(
|
|
629
|
+
"search.html",
|
|
630
|
+
ejs.render(searchTemplate, {
|
|
631
|
+
...commonData,
|
|
632
|
+
pageTitle: "Search",
|
|
633
|
+
searchIndexJson: JSON.stringify(searchIndex)
|
|
634
|
+
})
|
|
526
635
|
);
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
} catch {
|
|
541
|
-
styleCss = "/* ContextKit Site Styles */\n";
|
|
636
|
+
files.set("search-index.json", JSON.stringify(searchIndex, null, 2));
|
|
637
|
+
return files;
|
|
638
|
+
}
|
|
639
|
+
async function buildSite(manifest, config, outputDir) {
|
|
640
|
+
const siteConfig = config.site;
|
|
641
|
+
const files = generateSite(manifest, siteConfig);
|
|
642
|
+
for (const [filePath, content] of files) {
|
|
643
|
+
const fullPath = path.join(outputDir, filePath);
|
|
644
|
+
const dir = path.dirname(fullPath);
|
|
645
|
+
if (!fs.existsSync(dir)) {
|
|
646
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
647
|
+
}
|
|
648
|
+
fs.writeFileSync(fullPath, content, "utf-8");
|
|
542
649
|
}
|
|
543
|
-
await writeOutputFile(join(outputDir, "style.css"), styleCss);
|
|
544
650
|
}
|
|
545
651
|
export {
|
|
546
652
|
buildSearchIndex,
|
|
547
|
-
|
|
653
|
+
buildSite,
|
|
654
|
+
generateSite,
|
|
655
|
+
glossaryTemplate,
|
|
656
|
+
indexTemplate,
|
|
657
|
+
modelTemplate,
|
|
658
|
+
ownerTemplate,
|
|
659
|
+
rulesTemplate,
|
|
660
|
+
schemaTemplate,
|
|
661
|
+
searchTemplate
|
|
548
662
|
};
|
|
549
663
|
//# sourceMappingURL=index.mjs.map
|