@runcontext/site 0.4.2 → 0.4.4
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/astro/astro.config.mjs +13 -0
- package/astro/node_modules/.astro/data-store.json +1 -0
- package/astro/node_modules/.vite/deps/_metadata.json +31 -0
- package/astro/node_modules/.vite/deps/astro___aria-query.js +6776 -0
- package/astro/node_modules/.vite/deps/astro___aria-query.js.map +7 -0
- package/astro/node_modules/.vite/deps/astro___axobject-query.js +3754 -0
- package/astro/node_modules/.vite/deps/astro___axobject-query.js.map +7 -0
- package/astro/node_modules/.vite/deps/astro___cssesc.js +99 -0
- package/astro/node_modules/.vite/deps/astro___cssesc.js.map +7 -0
- package/astro/node_modules/.vite/deps/chunk-BUSYA2B4.js +8 -0
- package/astro/node_modules/.vite/deps/chunk-BUSYA2B4.js.map +7 -0
- package/astro/node_modules/.vite/deps/package.json +3 -0
- package/astro/src/components/Sidebar.astro +71 -0
- package/astro/src/components/TierBadge.astro +9 -0
- package/astro/src/components/Topbar.astro +42 -0
- package/astro/src/data/manifest.json +10839 -0
- package/astro/src/data/site-config.json +1 -0
- package/astro/src/layouts/Base.astro +461 -0
- package/astro/src/pages/glossary.astro +37 -0
- package/astro/src/pages/index.astro +98 -0
- package/astro/src/pages/models/[name]/rules.astro +87 -0
- package/astro/src/pages/models/[name]/schema.astro +82 -0
- package/astro/src/pages/models/[name].astro +181 -0
- package/astro/src/pages/owners/[id].astro +65 -0
- package/astro/src/pages/search.astro +108 -0
- package/astro/tsconfig.json +3 -0
- package/dist/build-index-MSTAYUC3.cjs +7 -0
- package/dist/build-index-MSTAYUC3.cjs.map +1 -0
- package/dist/build-index-UCDMZJZP.mjs +7 -0
- package/dist/build-index-UCDMZJZP.mjs.map +1 -0
- package/dist/chunk-BBC5HGNQ.cjs +65 -0
- package/dist/chunk-BBC5HGNQ.cjs.map +1 -0
- package/dist/chunk-WM73L4RO.mjs +65 -0
- package/dist/chunk-WM73L4RO.mjs.map +1 -0
- package/dist/index.cjs +84 -69
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +20 -8
- package/dist/index.d.ts +20 -8
- package/dist/index.mjs +81 -66
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -4
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
---
|
|
2
|
+
import Base from '../../../layouts/Base.astro';
|
|
3
|
+
import manifest from '../../../data/manifest.json';
|
|
4
|
+
|
|
5
|
+
export function getStaticPaths() {
|
|
6
|
+
return Object.keys(manifest.models).map((name) => ({
|
|
7
|
+
params: { name },
|
|
8
|
+
}));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const { name } = Astro.params;
|
|
12
|
+
const rules = manifest.rules[name!] ?? null;
|
|
13
|
+
const goldenQueries = rules?.golden_queries ?? [];
|
|
14
|
+
const businessRules = rules?.business_rules ?? [];
|
|
15
|
+
const guardrails = rules?.guardrail_filters ?? [];
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
<Base title={`${name} — Rules`} activeModel={name}>
|
|
19
|
+
<div class="breadcrumb">
|
|
20
|
+
<a href="/">Home</a>
|
|
21
|
+
<span class="breadcrumb-sep">/</span>
|
|
22
|
+
<a href={`/models/${name}.html`}>{name}</a>
|
|
23
|
+
<span class="breadcrumb-sep">/</span>
|
|
24
|
+
<span>Rules</span>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<div class="page-header">
|
|
28
|
+
<h1>Rules & Queries</h1>
|
|
29
|
+
<p class="subtitle">{goldenQueries.length} golden queries, {guardrails.length} guardrails, {businessRules.length} business rules</p>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
{goldenQueries.length > 0 && (
|
|
33
|
+
<div class="section">
|
|
34
|
+
<div class="section-label">Validated SQL</div>
|
|
35
|
+
<h2 class="section-title">Golden Queries</h2>
|
|
36
|
+
{goldenQueries.map((gq: any) => (
|
|
37
|
+
<div class="query-card">
|
|
38
|
+
<div class="query-q">{gq.question}</div>
|
|
39
|
+
{gq.sql && <pre class="query-sql sql-highlight">{gq.sql}</pre>}
|
|
40
|
+
{(gq.intent || gq.dialect || gq.caveats) && (
|
|
41
|
+
<div style="padding:0.5rem 1.25rem;display:flex;gap:0.75rem;font-size:0.65rem;color:var(--text-dim);border-top:1px solid var(--border);">
|
|
42
|
+
{gq.intent && <span>Intent: {gq.intent}</span>}
|
|
43
|
+
{gq.dialect && <span>Dialect: {gq.dialect}</span>}
|
|
44
|
+
{gq.caveats && <span>Caveats: {gq.caveats}</span>}
|
|
45
|
+
</div>
|
|
46
|
+
)}
|
|
47
|
+
</div>
|
|
48
|
+
))}
|
|
49
|
+
</div>
|
|
50
|
+
)}
|
|
51
|
+
|
|
52
|
+
{guardrails.length > 0 && (
|
|
53
|
+
<div class="section">
|
|
54
|
+
<div class="section-label">Safety</div>
|
|
55
|
+
<h2 class="section-title">Guardrail Filters</h2>
|
|
56
|
+
{guardrails.map((gf: any) => (
|
|
57
|
+
<div class="guardrail">
|
|
58
|
+
<div class="guardrail-name">{gf.name}</div>
|
|
59
|
+
{gf.filter && <div class="guardrail-filter">{gf.filter}</div>}
|
|
60
|
+
{gf.reason && <div class="guardrail-reason">{gf.reason}</div>}
|
|
61
|
+
{gf.tables && gf.tables.length > 0 && (
|
|
62
|
+
<div style="font-size:0.65rem;color:var(--text-dim);margin-top:0.35rem;">
|
|
63
|
+
Tables: {gf.tables.join(', ')}
|
|
64
|
+
</div>
|
|
65
|
+
)}
|
|
66
|
+
</div>
|
|
67
|
+
))}
|
|
68
|
+
</div>
|
|
69
|
+
)}
|
|
70
|
+
|
|
71
|
+
{businessRules.length > 0 && (
|
|
72
|
+
<div class="section">
|
|
73
|
+
<div class="section-label">Logic</div>
|
|
74
|
+
<h2 class="section-title">Business Rules</h2>
|
|
75
|
+
{businessRules.map((br: any) => (
|
|
76
|
+
<div class="card" style="margin-bottom:0.75rem;">
|
|
77
|
+
<div style="font-size:0.88rem;font-weight:500;color:var(--text);margin-bottom:0.25rem;">{br.name}</div>
|
|
78
|
+
{br.definition && <div style="font-size:0.82rem;color:var(--text-secondary);">{br.definition}</div>}
|
|
79
|
+
</div>
|
|
80
|
+
))}
|
|
81
|
+
</div>
|
|
82
|
+
)}
|
|
83
|
+
|
|
84
|
+
{!rules && (
|
|
85
|
+
<p style="color:var(--text-secondary);">No rules defined for this model. Add a <code class="mono" style="background:var(--bg-card);padding:0.15rem 0.4rem;border-radius:3px;">{name}.rules.yaml</code> file.</p>
|
|
86
|
+
)}
|
|
87
|
+
</Base>
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
---
|
|
2
|
+
import Base from '../../../layouts/Base.astro';
|
|
3
|
+
import manifest from '../../../data/manifest.json';
|
|
4
|
+
|
|
5
|
+
export function getStaticPaths() {
|
|
6
|
+
return Object.keys(manifest.models).map((name) => ({
|
|
7
|
+
params: { name },
|
|
8
|
+
}));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const { name } = Astro.params;
|
|
12
|
+
const model = manifest.models[name!];
|
|
13
|
+
const gov = manifest.governance[name!] ?? null;
|
|
14
|
+
const datasets = model?.datasets ?? [];
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
<Base title={`${name} — Schema`} activeModel={name}>
|
|
18
|
+
<div class="breadcrumb">
|
|
19
|
+
<a href="/">Home</a>
|
|
20
|
+
<span class="breadcrumb-sep">/</span>
|
|
21
|
+
<a href={`/models/${name}.html`}>{name}</a>
|
|
22
|
+
<span class="breadcrumb-sep">/</span>
|
|
23
|
+
<span>Schema</span>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<div class="page-header">
|
|
27
|
+
<h1>Schema Browser</h1>
|
|
28
|
+
<p class="subtitle">{datasets.length} datasets in {name}</p>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
{datasets.map((ds: any) => {
|
|
32
|
+
const dsGov = gov?.datasets?.[ds.name];
|
|
33
|
+
const fields = ds.fields ?? [];
|
|
34
|
+
return (
|
|
35
|
+
<div class="section">
|
|
36
|
+
<div style="display:flex;align-items:center;gap:0.5rem;margin-bottom:0.75rem;">
|
|
37
|
+
<h2 class="section-title" style="margin-bottom:0;">{ds.name}</h2>
|
|
38
|
+
{dsGov?.table_type && <span class={`tag ds-${dsGov.table_type}`}>{dsGov.table_type}</span>}
|
|
39
|
+
</div>
|
|
40
|
+
{ds.description && <p style="color:var(--text-secondary);font-size:0.85rem;margin-bottom:0.5rem;">{ds.description}</p>}
|
|
41
|
+
{dsGov && (
|
|
42
|
+
<div style="display:flex;gap:1rem;font-size:0.72rem;color:var(--text-dim);margin-bottom:0.75rem;">
|
|
43
|
+
{dsGov.grain && <span>Grain: {dsGov.grain}</span>}
|
|
44
|
+
{dsGov.refresh && <span>Refresh: {dsGov.refresh}</span>}
|
|
45
|
+
</div>
|
|
46
|
+
)}
|
|
47
|
+
<table class="data-table">
|
|
48
|
+
<thead>
|
|
49
|
+
<tr><th>Field</th><th>Description</th><th>Role</th><th>Aggregation</th></tr>
|
|
50
|
+
</thead>
|
|
51
|
+
<tbody>
|
|
52
|
+
{fields.map((f: any) => {
|
|
53
|
+
const fGov = gov?.fields?.[`${ds.name}.${f.name}`] ?? gov?.fields?.[f.name];
|
|
54
|
+
return (
|
|
55
|
+
<tr>
|
|
56
|
+
<td class="mono">{f.name}</td>
|
|
57
|
+
<td style="color:var(--text-secondary);">{f.description || '\u2014'}</td>
|
|
58
|
+
<td>
|
|
59
|
+
{fGov?.semantic_role && (
|
|
60
|
+
<span class={`tag role-${fGov.semantic_role}`}>{fGov.semantic_role}</span>
|
|
61
|
+
)}
|
|
62
|
+
</td>
|
|
63
|
+
<td class="mono" style="font-size:0.72rem;color:var(--text-dim);">
|
|
64
|
+
{fGov?.default_aggregation || '\u2014'}
|
|
65
|
+
{fGov?.additive && <span style="color:var(--green);margin-left:0.25rem;" title="Additive metric">(+)</span>}
|
|
66
|
+
</td>
|
|
67
|
+
</tr>
|
|
68
|
+
);
|
|
69
|
+
})}
|
|
70
|
+
</tbody>
|
|
71
|
+
</table>
|
|
72
|
+
</div>
|
|
73
|
+
);
|
|
74
|
+
})}
|
|
75
|
+
</Base>
|
|
76
|
+
|
|
77
|
+
<style>
|
|
78
|
+
.ds-fact { color: var(--purple); background: rgba(167, 139, 250, 0.08); }
|
|
79
|
+
.ds-dimension { color: var(--green); background: var(--green-dim); }
|
|
80
|
+
.ds-event { color: var(--orange); background: rgba(245, 158, 11, 0.08); }
|
|
81
|
+
.ds-view { color: var(--blue); background: var(--blue-dim); }
|
|
82
|
+
</style>
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
---
|
|
2
|
+
import Base from '../../layouts/Base.astro';
|
|
3
|
+
import TierBadge from '../../components/TierBadge.astro';
|
|
4
|
+
import manifest from '../../data/manifest.json';
|
|
5
|
+
import siteConfig from '../../data/site-config.json';
|
|
6
|
+
|
|
7
|
+
export function getStaticPaths() {
|
|
8
|
+
return Object.keys(manifest.models).map((name) => ({
|
|
9
|
+
params: { name },
|
|
10
|
+
}));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const { name } = Astro.params;
|
|
14
|
+
const model = manifest.models[name!];
|
|
15
|
+
const gov = manifest.governance[name!] ?? null;
|
|
16
|
+
const tier = manifest.tiers[name!] ?? null;
|
|
17
|
+
const rules = manifest.rules[name!] ?? null;
|
|
18
|
+
const lineage = manifest.lineage[name!] ?? null;
|
|
19
|
+
const studioMode = (siteConfig as any).studioMode ?? false;
|
|
20
|
+
|
|
21
|
+
const datasets = model?.datasets ?? [];
|
|
22
|
+
const relationships = model?.relationships ?? [];
|
|
23
|
+
const metrics = model?.metrics ?? [];
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
<Base title={name!} activeModel={name}>
|
|
27
|
+
<div class="breadcrumb">
|
|
28
|
+
<a href="/">Home</a>
|
|
29
|
+
<span class="breadcrumb-sep">/</span>
|
|
30
|
+
<span>{name}</span>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<div class="page-header">
|
|
34
|
+
<div style="display:flex;align-items:center;gap:0.75rem;">
|
|
35
|
+
<h1 id="model-name">{name}</h1>
|
|
36
|
+
{tier && <TierBadge tier={tier.tier} />}
|
|
37
|
+
{studioMode && (
|
|
38
|
+
<button class="rename-btn" data-model-name={name} title="Rename this model">✎ Rename</button>
|
|
39
|
+
)}
|
|
40
|
+
</div>
|
|
41
|
+
{studioMode ? (
|
|
42
|
+
<p class="subtitle">
|
|
43
|
+
<span class="editable" data-file={`context/models/${name}.osi.yaml`} data-path="semantic_model.0.description" data-label="Model description">
|
|
44
|
+
{model?.description || 'Add description...'}
|
|
45
|
+
</span>
|
|
46
|
+
</p>
|
|
47
|
+
) : (
|
|
48
|
+
model?.description && <p class="subtitle">{model.description}</p>
|
|
49
|
+
)}
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<!-- Governance Grid -->
|
|
53
|
+
{gov && (
|
|
54
|
+
<div class="section">
|
|
55
|
+
<div class="section-label">Governance</div>
|
|
56
|
+
<div class="gov-grid">
|
|
57
|
+
{gov.owner && <div class="gov-cell"><div class="gov-label">Owner</div><div class="gov-value"><a href={`/owners/${gov.owner}.html`}>{gov.owner}</a></div></div>}
|
|
58
|
+
{gov.trust && <div class="gov-cell"><div class="gov-label">Trust</div><div class="gov-value">{gov.trust}</div></div>}
|
|
59
|
+
{gov.security && <div class="gov-cell"><div class="gov-label">Security</div><div class="gov-value">{gov.security}</div></div>}
|
|
60
|
+
{gov.version && <div class="gov-cell"><div class="gov-label">Version</div><div class="gov-value">{gov.version}</div></div>}
|
|
61
|
+
</div>
|
|
62
|
+
{gov.tags && gov.tags.length > 0 && (
|
|
63
|
+
<div style="margin-top:0.75rem;display:flex;gap:0.35rem;flex-wrap:wrap;">
|
|
64
|
+
{gov.tags.map((t: string) => <span class="tag">{t}</span>)}
|
|
65
|
+
</div>
|
|
66
|
+
)}
|
|
67
|
+
</div>
|
|
68
|
+
)}
|
|
69
|
+
|
|
70
|
+
<!-- Navigation links -->
|
|
71
|
+
<div class="section">
|
|
72
|
+
<div class="card-grid">
|
|
73
|
+
<a href={`/models/${name}/schema.html`} class="card-link">
|
|
74
|
+
<div class="card">
|
|
75
|
+
<div style="font-size:0.88rem;font-weight:500;color:var(--accent-light);margin-bottom:0.25rem;">Schema Browser</div>
|
|
76
|
+
<div style="font-size:0.78rem;color:var(--text-secondary);">{datasets.length} datasets, {datasets.reduce((n: number, ds: any) => n + (ds.fields?.length ?? 0), 0)} fields</div>
|
|
77
|
+
</div>
|
|
78
|
+
</a>
|
|
79
|
+
<a href={`/models/${name}/rules.html`} class="card-link">
|
|
80
|
+
<div class="card">
|
|
81
|
+
<div style="font-size:0.88rem;font-weight:500;color:var(--accent-light);margin-bottom:0.25rem;">Rules & Queries</div>
|
|
82
|
+
<div style="font-size:0.78rem;color:var(--text-secondary);">{rules?.golden_queries?.length ?? 0} golden queries, {rules?.guardrail_filters?.length ?? 0} guardrails</div>
|
|
83
|
+
</div>
|
|
84
|
+
</a>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
<!-- Tier Scorecard -->
|
|
89
|
+
{tier && (
|
|
90
|
+
<div class="section">
|
|
91
|
+
<div class="section-label">Quality</div>
|
|
92
|
+
<h2 class="section-title">Tier Scorecard</h2>
|
|
93
|
+
<div class="scorecard">
|
|
94
|
+
{['bronze', 'silver', 'gold'].map((t) => {
|
|
95
|
+
const tierData = (tier as any)[t];
|
|
96
|
+
if (!tierData) return null;
|
|
97
|
+
return (
|
|
98
|
+
<div class="sc-tier">
|
|
99
|
+
<div class="sc-tier-head">
|
|
100
|
+
<span class={`sc-tier-name ${t}`}>{t}</span>
|
|
101
|
+
<span class={`sc-status ${tierData.passed ? 'pass' : 'fail'}`}>{tierData.passed ? 'PASS' : 'FAIL'}</span>
|
|
102
|
+
</div>
|
|
103
|
+
<ul class="check-list">
|
|
104
|
+
{tierData.checks?.map((c: any) => (
|
|
105
|
+
<li class="check-item">
|
|
106
|
+
<span class={`check-icon ${c.passed ? 'pass' : 'fail'}`}>{c.passed ? '\u2713' : '\u2717'}</span>
|
|
107
|
+
<span>{c.label}{c.detail ? ` — ${c.detail}` : ''}</span>
|
|
108
|
+
</li>
|
|
109
|
+
))}
|
|
110
|
+
</ul>
|
|
111
|
+
</div>
|
|
112
|
+
);
|
|
113
|
+
})}
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
)}
|
|
117
|
+
|
|
118
|
+
<!-- Metrics -->
|
|
119
|
+
{metrics.length > 0 && (
|
|
120
|
+
<div class="section">
|
|
121
|
+
<div class="section-label">Analytics</div>
|
|
122
|
+
<h2 class="section-title">Metrics</h2>
|
|
123
|
+
{metrics.map((met: any) => (
|
|
124
|
+
<div class="card" style="margin-bottom:0.75rem;">
|
|
125
|
+
<div class="metric-name">{met.name}</div>
|
|
126
|
+
{met.description && <div class="metric-desc">{met.description}</div>}
|
|
127
|
+
{met.expression && (
|
|
128
|
+
<div class="metric-formula">
|
|
129
|
+
{typeof met.expression === 'string' ? met.expression : JSON.stringify(met.expression)}
|
|
130
|
+
</div>
|
|
131
|
+
)}
|
|
132
|
+
</div>
|
|
133
|
+
))}
|
|
134
|
+
</div>
|
|
135
|
+
)}
|
|
136
|
+
|
|
137
|
+
<!-- Relationships -->
|
|
138
|
+
{relationships.length > 0 && (
|
|
139
|
+
<div class="section">
|
|
140
|
+
<div class="section-label">Connections</div>
|
|
141
|
+
<h2 class="section-title">Relationships</h2>
|
|
142
|
+
<table class="data-table">
|
|
143
|
+
<thead>
|
|
144
|
+
<tr><th>Name</th><th>From</th><th>To</th><th>Cardinality</th></tr>
|
|
145
|
+
</thead>
|
|
146
|
+
<tbody>
|
|
147
|
+
{relationships.map((rel: any) => (
|
|
148
|
+
<tr>
|
|
149
|
+
<td class="mono">{rel.name}</td>
|
|
150
|
+
<td><span class="mono">{rel.from}</span>.{rel.from_columns?.join(', ')}</td>
|
|
151
|
+
<td><span class="mono">{rel.to}</span>.{rel.to_columns?.join(', ')}</td>
|
|
152
|
+
<td>{rel.cardinality || '\u2014'}</td>
|
|
153
|
+
</tr>
|
|
154
|
+
))}
|
|
155
|
+
</tbody>
|
|
156
|
+
</table>
|
|
157
|
+
</div>
|
|
158
|
+
)}
|
|
159
|
+
|
|
160
|
+
<!-- Lineage -->
|
|
161
|
+
{lineage && (lineage.upstream?.length > 0 || lineage.downstream?.length > 0) && (
|
|
162
|
+
<div class="section">
|
|
163
|
+
<div class="section-label">Data Flow</div>
|
|
164
|
+
<h2 class="section-title">Lineage</h2>
|
|
165
|
+
{lineage.upstream?.map((u: any) => (
|
|
166
|
+
<div class="card" style="margin-bottom:0.5rem;">
|
|
167
|
+
<div style="font-size:0.75rem;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.1em;margin-bottom:0.25rem;">Upstream</div>
|
|
168
|
+
<div class="mono" style="font-size:0.85rem;">{u.source}</div>
|
|
169
|
+
{u.type && <div style="font-size:0.72rem;color:var(--text-dim);margin-top:0.15rem;">{u.type}{u.tool ? ` via ${u.tool}` : ''}</div>}
|
|
170
|
+
</div>
|
|
171
|
+
))}
|
|
172
|
+
{lineage.downstream?.map((d: any) => (
|
|
173
|
+
<div class="card" style="margin-bottom:0.5rem;">
|
|
174
|
+
<div style="font-size:0.75rem;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.1em;margin-bottom:0.25rem;">Downstream</div>
|
|
175
|
+
<div class="mono" style="font-size:0.85rem;">{d.target}</div>
|
|
176
|
+
{d.type && <div style="font-size:0.72rem;color:var(--text-dim);margin-top:0.15rem;">{d.type}{d.tool ? ` via ${d.tool}` : ''}</div>}
|
|
177
|
+
</div>
|
|
178
|
+
))}
|
|
179
|
+
</div>
|
|
180
|
+
)}
|
|
181
|
+
</Base>
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
import Base from '../../layouts/Base.astro';
|
|
3
|
+
import TierBadge from '../../components/TierBadge.astro';
|
|
4
|
+
import manifest from '../../data/manifest.json';
|
|
5
|
+
|
|
6
|
+
export function getStaticPaths() {
|
|
7
|
+
return Object.keys(manifest.owners).map((id) => ({
|
|
8
|
+
params: { id },
|
|
9
|
+
}));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const { id } = Astro.params;
|
|
13
|
+
const owner = (manifest.owners as any)[id!];
|
|
14
|
+
|
|
15
|
+
// Find models governed by this owner
|
|
16
|
+
const governedModels = Object.entries(manifest.governance)
|
|
17
|
+
.filter(([, gov]: [string, any]) => gov.owner === id)
|
|
18
|
+
.map(([modelName]) => ({
|
|
19
|
+
name: modelName,
|
|
20
|
+
tier: (manifest.tiers as any)[modelName]?.tier ?? null,
|
|
21
|
+
}));
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
<Base title={owner?.display_name || id!}>
|
|
25
|
+
<div class="breadcrumb">
|
|
26
|
+
<a href="/">Home</a>
|
|
27
|
+
<span class="breadcrumb-sep">/</span>
|
|
28
|
+
<span>Owners</span>
|
|
29
|
+
<span class="breadcrumb-sep">/</span>
|
|
30
|
+
<span>{owner?.display_name || id}</span>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<div class="page-header">
|
|
34
|
+
<h1>{owner?.display_name || id}</h1>
|
|
35
|
+
{owner?.team && <p class="subtitle">{owner.team}</p>}
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
<div class="section">
|
|
39
|
+
<div class="gov-grid">
|
|
40
|
+
{owner?.email && <div class="gov-cell"><div class="gov-label">Email</div><div class="gov-value">{owner.email}</div></div>}
|
|
41
|
+
{owner?.team && <div class="gov-cell"><div class="gov-label">Team</div><div class="gov-value">{owner.team}</div></div>}
|
|
42
|
+
{owner?.slack && <div class="gov-cell"><div class="gov-label">Slack</div><div class="gov-value">{owner.slack}</div></div>}
|
|
43
|
+
</div>
|
|
44
|
+
{owner?.description && <p style="color:var(--text-secondary);font-size:0.85rem;margin-top:0.75rem;">{owner.description}</p>}
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
{governedModels.length > 0 && (
|
|
48
|
+
<div class="section">
|
|
49
|
+
<div class="section-label">Ownership</div>
|
|
50
|
+
<h2 class="section-title">Governed Models</h2>
|
|
51
|
+
<div class="card-grid">
|
|
52
|
+
{governedModels.map((m) => (
|
|
53
|
+
<a href={`/models/${m.name}.html`} class="card-link">
|
|
54
|
+
<div class="card">
|
|
55
|
+
<div style="display:flex;align-items:center;gap:0.5rem;">
|
|
56
|
+
<span class="mono" style="font-size:0.85rem;color:var(--accent-light);">{m.name}</span>
|
|
57
|
+
{m.tier && <TierBadge tier={m.tier} />}
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
</a>
|
|
61
|
+
))}
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
)}
|
|
65
|
+
</Base>
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
---
|
|
2
|
+
import Base from '../layouts/Base.astro';
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
<Base title="Search">
|
|
6
|
+
<div class="page-header">
|
|
7
|
+
<h1>Search</h1>
|
|
8
|
+
<p class="subtitle">Search across models, datasets, fields, terms, and owners.</p>
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
<input
|
|
12
|
+
type="text"
|
|
13
|
+
id="search-input"
|
|
14
|
+
class="search-input"
|
|
15
|
+
placeholder="Search models, fields, terms..."
|
|
16
|
+
autofocus
|
|
17
|
+
/>
|
|
18
|
+
|
|
19
|
+
<div id="search-results" style="margin-top:1.5rem;"></div>
|
|
20
|
+
</Base>
|
|
21
|
+
|
|
22
|
+
<script>
|
|
23
|
+
// Search index is loaded from search-index.json (built alongside the site)
|
|
24
|
+
async function initSearch() {
|
|
25
|
+
const input = document.getElementById('search-input') as HTMLInputElement;
|
|
26
|
+
const results = document.getElementById('search-results')!;
|
|
27
|
+
if (!input) return;
|
|
28
|
+
|
|
29
|
+
let index: any = null;
|
|
30
|
+
try {
|
|
31
|
+
const res = await fetch('/search-index.json');
|
|
32
|
+
index = await res.json();
|
|
33
|
+
} catch {
|
|
34
|
+
results.textContent = 'Search index not available.';
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const docs = index.documents || {};
|
|
39
|
+
|
|
40
|
+
input.addEventListener('input', () => {
|
|
41
|
+
const q = input.value.trim().toLowerCase();
|
|
42
|
+
if (!q) { results.textContent = ''; return; }
|
|
43
|
+
|
|
44
|
+
const matches = Object.values(docs).filter((d: any) =>
|
|
45
|
+
d.title?.toLowerCase().includes(q) ||
|
|
46
|
+
d.description?.toLowerCase().includes(q) ||
|
|
47
|
+
d.type?.toLowerCase().includes(q)
|
|
48
|
+
).slice(0, 20);
|
|
49
|
+
|
|
50
|
+
if (matches.length === 0) {
|
|
51
|
+
results.textContent = `No results for "${input.value}"`;
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Safe: all content comes from the manifest (server-generated data), not user input
|
|
56
|
+
results.textContent = '';
|
|
57
|
+
for (const m of matches as any[]) {
|
|
58
|
+
const link = document.createElement('a');
|
|
59
|
+
link.href = m.url;
|
|
60
|
+
link.className = 'card-link';
|
|
61
|
+
link.style.marginBottom = '0.5rem';
|
|
62
|
+
link.style.display = 'block';
|
|
63
|
+
|
|
64
|
+
const card = document.createElement('div');
|
|
65
|
+
card.className = 'card';
|
|
66
|
+
|
|
67
|
+
const title = document.createElement('div');
|
|
68
|
+
title.style.cssText = 'font-size:0.88rem;font-weight:500;color:var(--accent-light);margin-bottom:0.15rem;';
|
|
69
|
+
title.textContent = m.title;
|
|
70
|
+
card.appendChild(title);
|
|
71
|
+
|
|
72
|
+
const typeTag = document.createElement('span');
|
|
73
|
+
typeTag.className = 'tag';
|
|
74
|
+
typeTag.style.marginRight = '0.35rem';
|
|
75
|
+
typeTag.textContent = m.type;
|
|
76
|
+
card.appendChild(typeTag);
|
|
77
|
+
|
|
78
|
+
if (m.description) {
|
|
79
|
+
const desc = document.createElement('span');
|
|
80
|
+
desc.style.cssText = 'font-size:0.78rem;color:var(--text-secondary);';
|
|
81
|
+
desc.textContent = m.description;
|
|
82
|
+
card.appendChild(desc);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
link.appendChild(card);
|
|
86
|
+
results.appendChild(link);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
initSearch();
|
|
91
|
+
</script>
|
|
92
|
+
|
|
93
|
+
<style>
|
|
94
|
+
.search-input {
|
|
95
|
+
width: 100%;
|
|
96
|
+
background: var(--bg-card);
|
|
97
|
+
border: 1px solid var(--border);
|
|
98
|
+
border-radius: 6px;
|
|
99
|
+
padding: 0.6rem 1rem;
|
|
100
|
+
font-family: var(--sans);
|
|
101
|
+
font-size: 0.9rem;
|
|
102
|
+
color: var(--text);
|
|
103
|
+
outline: none;
|
|
104
|
+
transition: border-color 0.15s, box-shadow 0.15s;
|
|
105
|
+
}
|
|
106
|
+
.search-input::placeholder { color: var(--text-dim); }
|
|
107
|
+
.search-input:focus { border-color: var(--accent-border); box-shadow: 0 0 0 3px var(--accent-dim); }
|
|
108
|
+
</style>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/Users/erickittelson/Desktop/ContextKit/packages/site/dist/build-index-MSTAYUC3.cjs"],"names":[],"mappings":"AAAA;AACE;AACF,wDAA6B;AAC7B;AACE;AACF,8DAAC","file":"/Users/erickittelson/Desktop/ContextKit/packages/site/dist/build-index-MSTAYUC3.cjs"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } }// src/search/build-index.ts
|
|
2
|
+
var _minisearch = require('minisearch'); var _minisearch2 = _interopRequireDefault(_minisearch);
|
|
3
|
+
var MINISEARCH_OPTIONS = {
|
|
4
|
+
fields: ["title", "description", "type"],
|
|
5
|
+
storeFields: ["title", "description", "type", "url"],
|
|
6
|
+
idField: "id"
|
|
7
|
+
};
|
|
8
|
+
function buildSearchIndex(manifest, basePath) {
|
|
9
|
+
const docs = [];
|
|
10
|
+
let idCounter = 0;
|
|
11
|
+
for (const [name, model] of Object.entries(manifest.models)) {
|
|
12
|
+
docs.push({
|
|
13
|
+
id: String(idCounter++),
|
|
14
|
+
type: "model",
|
|
15
|
+
title: name,
|
|
16
|
+
description: _nullishCoalesce(model.description, () => ( "")),
|
|
17
|
+
url: `${basePath}/models/${name}.html`
|
|
18
|
+
});
|
|
19
|
+
if (model.datasets) {
|
|
20
|
+
for (const ds of model.datasets) {
|
|
21
|
+
docs.push({
|
|
22
|
+
id: String(idCounter++),
|
|
23
|
+
type: "dataset",
|
|
24
|
+
title: `${name} / ${ds.name}`,
|
|
25
|
+
description: _nullishCoalesce(ds.description, () => ( "")),
|
|
26
|
+
url: `${basePath}/models/${name}/schema.html`
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
for (const [termId, term] of Object.entries(manifest.terms)) {
|
|
32
|
+
docs.push({
|
|
33
|
+
id: String(idCounter++),
|
|
34
|
+
type: "term",
|
|
35
|
+
title: termId,
|
|
36
|
+
description: term.definition,
|
|
37
|
+
url: `${basePath}/glossary.html#term-${termId}`
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
for (const [oid, owner] of Object.entries(manifest.owners)) {
|
|
41
|
+
docs.push({
|
|
42
|
+
id: String(idCounter++),
|
|
43
|
+
type: "owner",
|
|
44
|
+
title: owner.display_name,
|
|
45
|
+
description: _nullishCoalesce(owner.description, () => ( "")),
|
|
46
|
+
url: `${basePath}/owners/${oid}.html`
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
const miniSearch = new (0, _minisearch2.default)(MINISEARCH_OPTIONS);
|
|
50
|
+
miniSearch.addAll(docs);
|
|
51
|
+
const documentsMap = {};
|
|
52
|
+
for (const doc of docs) {
|
|
53
|
+
documentsMap[doc.id] = doc;
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
index: JSON.parse(JSON.stringify(miniSearch)),
|
|
57
|
+
options: MINISEARCH_OPTIONS,
|
|
58
|
+
documents: documentsMap
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
exports.buildSearchIndex = buildSearchIndex;
|
|
65
|
+
//# sourceMappingURL=chunk-BBC5HGNQ.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/Users/erickittelson/Desktop/ContextKit/packages/site/dist/chunk-BBC5HGNQ.cjs","../src/search/build-index.ts"],"names":[],"mappings":"AAAA;ACOA,gGAAuB;AAwBvB,IAAM,mBAAA,EAAqB;AAAA,EACzB,MAAA,EAAQ,CAAC,OAAA,EAAS,aAAA,EAAe,MAAM,CAAA;AAAA,EACvC,WAAA,EAAa,CAAC,OAAA,EAAS,aAAA,EAAe,MAAA,EAAQ,KAAK,CAAA;AAAA,EACnD,OAAA,EAAS;AACX,CAAA;AASO,SAAS,gBAAA,CAAiB,QAAA,EAAoB,QAAA,EAA+B;AAClF,EAAA,MAAM,KAAA,EAAyB,CAAC,CAAA;AAChC,EAAA,IAAI,UAAA,EAAY,CAAA;AAGhB,EAAA,IAAA,CAAA,MAAW,CAAC,IAAA,EAAM,KAAK,EAAA,GAAK,MAAA,CAAO,OAAA,CAAQ,QAAA,CAAS,MAAM,CAAA,EAAG;AAC3D,IAAA,IAAA,CAAK,IAAA,CAAK;AAAA,MACR,EAAA,EAAI,MAAA,CAAO,SAAA,EAAW,CAAA;AAAA,MACtB,IAAA,EAAM,OAAA;AAAA,MACN,KAAA,EAAO,IAAA;AAAA,MACP,WAAA,mBAAa,KAAA,CAAM,WAAA,UAAe,IAAA;AAAA,MAClC,GAAA,EAAK,CAAA,EAAA;AACN,IAAA;AAGG,IAAA;AACF,MAAA;AACE,QAAA;AACE,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACD,QAAA;AACH,MAAA;AACF,IAAA;AACF,EAAA;AAGA,EAAA;AACO,IAAA;AACC,MAAA;AACE,MAAA;AACN,MAAA;AACA,MAAA;AACK,MAAA;AACN,IAAA;AACH,EAAA;AAGA,EAAA;AACO,IAAA;AACC,MAAA;AACE,MAAA;AACN,MAAA;AACA,MAAA;AACK,MAAA;AACN,IAAA;AACH,EAAA;AAGM,EAAA;AACN,EAAA;AAGM,EAAA;AACN,EAAA;AACE,IAAA;AACF,EAAA;AAEO,EAAA;AACE,IAAA;AACP,IAAA;AACA,IAAA;AACF,EAAA;AACF;ADjDY;AACA;AACA;AACA","file":"/Users/erickittelson/Desktop/ContextKit/packages/site/dist/chunk-BBC5HGNQ.cjs","sourcesContent":[null,"/**\n * Builds a MiniSearch index from a Manifest for client-side search.\n *\n * The index is serialized to JSON so it can be embedded in the search page\n * and loaded by MiniSearch on the client side.\n */\n\nimport MiniSearch from 'minisearch';\nimport type { Manifest } from '@runcontext/core';\n\nexport interface SearchDocument {\n id: string;\n type: string;\n title: string;\n description: string;\n url: string;\n}\n\nexport interface SearchIndex {\n /** Serialized MiniSearch index (JSON-parsed object). */\n index: unknown;\n /** MiniSearch constructor options needed to reload the index. */\n options: {\n fields: string[];\n storeFields: string[];\n idField: string;\n };\n /** Map of document ID to document metadata for rendering results. */\n documents: Record<string, SearchDocument>;\n}\n\nconst MINISEARCH_OPTIONS = {\n fields: ['title', 'description', 'type'],\n storeFields: ['title', 'description', 'type', 'url'],\n idField: 'id',\n};\n\n/**\n * Build a search index from a manifest.\n *\n * @param manifest - The compiled ContextKit manifest\n * @param basePath - The base URL path for links (e.g. '' or '/docs')\n * @returns A SearchIndex object ready for JSON serialization\n */\nexport function buildSearchIndex(manifest: Manifest, basePath: string): SearchIndex {\n const docs: SearchDocument[] = [];\n let idCounter = 0;\n\n // Index models\n for (const [name, model] of Object.entries(manifest.models)) {\n docs.push({\n id: String(idCounter++),\n type: 'model',\n title: name,\n description: model.description ?? '',\n url: `${basePath}/models/${name}.html`,\n });\n\n // Index datasets within each model\n if (model.datasets) {\n for (const ds of model.datasets) {\n docs.push({\n id: String(idCounter++),\n type: 'dataset',\n title: `${name} / ${ds.name}`,\n description: ds.description ?? '',\n url: `${basePath}/models/${name}/schema.html`,\n });\n }\n }\n }\n\n // Index glossary terms\n for (const [termId, term] of Object.entries(manifest.terms)) {\n docs.push({\n id: String(idCounter++),\n type: 'term',\n title: termId,\n description: term.definition,\n url: `${basePath}/glossary.html#term-${termId}`,\n });\n }\n\n // Index owners\n for (const [oid, owner] of Object.entries(manifest.owners)) {\n docs.push({\n id: String(idCounter++),\n type: 'owner',\n title: owner.display_name,\n description: owner.description ?? '',\n url: `${basePath}/owners/${oid}.html`,\n });\n }\n\n // Build MiniSearch index\n const miniSearch = new MiniSearch(MINISEARCH_OPTIONS);\n miniSearch.addAll(docs);\n\n // Create document lookup map by id\n const documentsMap: Record<string, SearchDocument> = {};\n for (const doc of docs) {\n documentsMap[doc.id] = doc;\n }\n\n return {\n index: JSON.parse(JSON.stringify(miniSearch)),\n options: MINISEARCH_OPTIONS,\n documents: documentsMap,\n };\n}\n"]}
|