@eventcatalog/core 5.0.0-beta.29 → 5.0.0-beta.30
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/dist/analytics/analytics.cjs +1 -1
- package/dist/analytics/analytics.js +2 -2
- package/dist/analytics/log-build.cjs +1 -1
- package/dist/analytics/log-build.js +3 -3
- package/dist/{chunk-4IREUUHE.js → chunk-55QETQZ3.js} +1 -1
- package/dist/{chunk-3RDV4HXA.js → chunk-FLAZ2JVN.js} +1 -1
- package/dist/{chunk-34VU2X7H.js → chunk-GQI5UYXZ.js} +1 -1
- package/dist/{chunk-ZK7Q7XDV.js → chunk-NA7OTBWW.js} +1 -1
- package/dist/{chunk-B4OOUG5I.js → chunk-T3OROMAU.js} +1 -1
- package/dist/constants.cjs +1 -1
- package/dist/constants.js +1 -1
- package/dist/eventcatalog.cjs +1 -1
- package/dist/eventcatalog.js +5 -5
- package/dist/generate.cjs +1 -1
- package/dist/generate.js +3 -3
- package/dist/utils/cli-logger.cjs +1 -1
- package/dist/utils/cli-logger.js +2 -2
- package/eventcatalog/src/components/Homepage/CatalogDashboard.astro +342 -0
- package/eventcatalog/src/features/mcp/apps/generated/canvas.html +73 -73
- package/eventcatalog/src/features/mcp/apps/generated/viewer.html +68 -68
- package/eventcatalog/src/features/studio/components/LeftPanel.tsx +3 -0
- package/eventcatalog/src/pages/_index.astro +4 -341
- package/eventcatalog/src/pages/index.astro +3 -1
- package/eventcatalog/src/utils/git.ts +11 -0
- package/eventcatalog/src/utils/homepage-dashboard.ts +143 -0
- package/package.json +3 -3
|
@@ -126,6 +126,9 @@ function CatalogList({
|
|
|
126
126
|
<div className="relative">
|
|
127
127
|
<Search size={14} className="absolute left-2.5 top-1/2 -translate-y-1/2 text-[rgb(var(--ec-icon-color))]" />
|
|
128
128
|
<input
|
|
129
|
+
// The list shows when its tab is chosen: typing then searches it, rather than reaching the canvas's shortcuts
|
|
130
|
+
// (from the tab button, typing "Order Service" picked the Comment tool)
|
|
131
|
+
autoFocus
|
|
129
132
|
value={search}
|
|
130
133
|
onChange={(e) => setSearch(e.target.value)}
|
|
131
134
|
placeholder="Search the catalog"
|
|
@@ -1,358 +1,21 @@
|
|
|
1
1
|
---
|
|
2
|
-
import { buildUrl } from '@utils/url-builder';
|
|
3
|
-
import config from '@config';
|
|
4
|
-
|
|
5
|
-
import { getCollection } from 'astro:content';
|
|
6
2
|
import VerticalSideBarLayout from '@layouts/VerticalSideBarLayout.astro';
|
|
7
|
-
import
|
|
3
|
+
import CatalogDashboard from '@components/Homepage/CatalogDashboard.astro';
|
|
8
4
|
|
|
9
5
|
const { showNestedSideBar = true } = Astro.props;
|
|
10
|
-
|
|
11
|
-
// Lightweight data fetch: only raw collections, no hydration.
|
|
12
|
-
const [allDomains, allServices, allEvents, allCommands, allQueries, allFlows] = await Promise.all([
|
|
13
|
-
getCollection('domains'),
|
|
14
|
-
getCollection('services'),
|
|
15
|
-
getCollection('events'),
|
|
16
|
-
getCollection('commands'),
|
|
17
|
-
getCollection('queries'),
|
|
18
|
-
getCollection('flows'),
|
|
19
|
-
]);
|
|
20
|
-
|
|
21
|
-
const isCurrentVersion = (item: { data: { hidden?: boolean }; filePath?: string }) =>
|
|
22
|
-
item.data.hidden !== true && !item.filePath?.includes('versioned');
|
|
23
|
-
|
|
24
|
-
const domains = allDomains.filter(isCurrentVersion);
|
|
25
|
-
const services = allServices.filter(isCurrentVersion);
|
|
26
|
-
const events = allEvents.filter(isCurrentVersion);
|
|
27
|
-
const commands = allCommands.filter(isCurrentVersion);
|
|
28
|
-
const queries = allQueries.filter(isCurrentVersion);
|
|
29
|
-
const messages = [...events, ...commands, ...queries];
|
|
30
|
-
const flows = allFlows.filter(isCurrentVersion);
|
|
31
|
-
|
|
32
|
-
const getDefaultUrl = (route: string, defaultValue: string) => {
|
|
33
|
-
if (domains.length > 0) return buildUrl(`/${route}/domains/${domains[0].data.id}/${domains[0].data.version}`);
|
|
34
|
-
if (services.length > 0) return buildUrl(`/${route}/services/${services[0].data.id}/${services[0].data.version}`);
|
|
35
|
-
if (flows.length > 0) return buildUrl(`/${route}/flows/${flows[0].data.id}/${flows[0].data.version}`);
|
|
36
|
-
return buildUrl(defaultValue);
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
interface StatTile {
|
|
40
|
-
label: string;
|
|
41
|
-
count: number;
|
|
42
|
-
href: string;
|
|
43
|
-
emptyHref: string;
|
|
44
|
-
tone: string; // CSS color expression — drives accent rail + count gradient
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const statTiles: StatTile[] = [
|
|
48
|
-
{
|
|
49
|
-
label: 'Domains',
|
|
50
|
-
count: domains.length,
|
|
51
|
-
href: buildUrl('/discover/domains'),
|
|
52
|
-
emptyHref: 'https://www.eventcatalog.dev/docs/domains',
|
|
53
|
-
tone: '250 204 21', // amber-400
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
label: 'Services',
|
|
57
|
-
count: services.length,
|
|
58
|
-
href: buildUrl('/discover/services'),
|
|
59
|
-
emptyHref: 'https://www.eventcatalog.dev/docs/services',
|
|
60
|
-
tone: '244 114 182', // pink-400
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
label: 'Messages',
|
|
64
|
-
count: messages.length,
|
|
65
|
-
href: buildUrl('/discover/events'),
|
|
66
|
-
emptyHref: 'https://www.eventcatalog.dev/docs/messages',
|
|
67
|
-
tone: '167 139 250', // violet-400
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
label: 'Flows',
|
|
71
|
-
count: flows.length,
|
|
72
|
-
href: buildUrl('/discover/flows'),
|
|
73
|
-
emptyHref: 'https://www.eventcatalog.dev/docs/flows',
|
|
74
|
-
tone: '94 234 212', // teal-300
|
|
75
|
-
},
|
|
76
|
-
];
|
|
77
|
-
|
|
78
|
-
interface ExploreCard {
|
|
79
|
-
title: string;
|
|
80
|
-
description: string;
|
|
81
|
-
icon: any;
|
|
82
|
-
href: string;
|
|
83
|
-
external?: boolean;
|
|
84
|
-
/** When true, renders as the lead "feature" card with extra width and CTA styling. */
|
|
85
|
-
feature?: boolean;
|
|
86
|
-
tone: string; // RGB triple for the icon tile
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const exploreCards: ExploreCard[] = [
|
|
90
|
-
{
|
|
91
|
-
title: 'Documentation',
|
|
92
|
-
description: 'Browse every domain, service, message, and flow your teams have published.',
|
|
93
|
-
icon: BookOpenText,
|
|
94
|
-
href: getDefaultUrl('docs', 'domains'),
|
|
95
|
-
feature: true,
|
|
96
|
-
tone: '96 165 250', // blue-400
|
|
97
|
-
},
|
|
98
|
-
{
|
|
99
|
-
title: 'Visualizer',
|
|
100
|
-
description: 'See how services connect — message flows across your architecture.',
|
|
101
|
-
icon: Workflow,
|
|
102
|
-
href: getDefaultUrl('visualiser', 'domains'),
|
|
103
|
-
tone: '167 139 250', // violet-400
|
|
104
|
-
},
|
|
105
|
-
{
|
|
106
|
-
title: 'Discover',
|
|
107
|
-
description: 'Search every event, service, agent, and team across the catalog.',
|
|
108
|
-
icon: TableProperties,
|
|
109
|
-
href: buildUrl('/discover/events'),
|
|
110
|
-
tone: '94 234 212', // teal-300
|
|
111
|
-
},
|
|
112
|
-
{
|
|
113
|
-
title: 'Schema Explorer',
|
|
114
|
-
description: 'Track schema evolution, compare versions, and catch breaking changes.',
|
|
115
|
-
icon: FileJson,
|
|
116
|
-
href: buildUrl('/schemas/explorer'),
|
|
117
|
-
tone: '250 204 21', // amber-400
|
|
118
|
-
},
|
|
119
|
-
{
|
|
120
|
-
title: 'Team Directory',
|
|
121
|
-
description: 'Find owners, on-call contacts, and the team behind every service.',
|
|
122
|
-
icon: BookUser,
|
|
123
|
-
href: buildUrl('/directory/users'),
|
|
124
|
-
tone: '251 146 60', // orange-400
|
|
125
|
-
},
|
|
126
|
-
{
|
|
127
|
-
title: 'API & SDK',
|
|
128
|
-
description: 'Programmatic access — query and manage your catalog with a typed SDK.',
|
|
129
|
-
icon: Code2,
|
|
130
|
-
href: 'https://www.eventcatalog.dev/docs/sdk',
|
|
131
|
-
external: true,
|
|
132
|
-
tone: '129 140 248', // indigo-400
|
|
133
|
-
},
|
|
134
|
-
];
|
|
135
6
|
---
|
|
136
7
|
|
|
137
8
|
<VerticalSideBarLayout showNestedSideBar={showNestedSideBar}>
|
|
138
|
-
<div class="ec-home
|
|
139
|
-
|
|
140
|
-
<header class="ec-hero">
|
|
141
|
-
<div class="text-[11px] font-semibold uppercase tracking-[0.18em] text-[rgb(var(--ec-accent))]">Catalog</div>
|
|
142
|
-
<div class="mt-3 flex flex-col gap-3 md:flex-row md:items-end md:justify-between">
|
|
143
|
-
<div>
|
|
144
|
-
<h1 class="text-[28px] font-semibold leading-[1.1] tracking-tight text-[rgb(var(--ec-page-text))] md:text-[32px]">
|
|
145
|
-
{config?.organizationName || 'EventCatalog'}
|
|
146
|
-
</h1>
|
|
147
|
-
<p class="mt-2 max-w-2xl text-[14px] leading-relaxed text-[rgb(var(--ec-page-text-muted))]">
|
|
148
|
-
{config.tagline || 'Explore and understand your event-driven architecture.'}
|
|
149
|
-
</p>
|
|
150
|
-
</div>
|
|
151
|
-
<div class="flex items-center gap-2">
|
|
152
|
-
<a
|
|
153
|
-
href={getDefaultUrl('docs', 'domains')}
|
|
154
|
-
class="ec-btn-primary inline-flex items-center gap-1.5 rounded-md bg-[rgb(var(--ec-page-bg))] border border-[rgb(var(--ec-page-border))] px-3.5 py-2 text-[12.5px] font-semibold text-[rgb(var(--ec-page-text-muted))] transition-colors hover:border-[rgb(var(--ec-accent)/0.5)] hover:text-[rgb(var(--ec-accent))]"
|
|
155
|
-
>
|
|
156
|
-
Explore Catalog
|
|
157
|
-
<ArrowRight className="h-3.5 w-3.5" />
|
|
158
|
-
</a>
|
|
159
|
-
</div>
|
|
160
|
-
</div>
|
|
161
|
-
</header>
|
|
162
|
-
|
|
163
|
-
<div class="ec-divider"></div>
|
|
164
|
-
|
|
165
|
-
<!-- Stat strip -->
|
|
166
|
-
<section class="ec-stats">
|
|
167
|
-
{
|
|
168
|
-
statTiles.map((tile) => (
|
|
169
|
-
<a
|
|
170
|
-
href={tile.count > 0 ? tile.href : tile.emptyHref}
|
|
171
|
-
target={tile.count === 0 ? '_blank' : undefined}
|
|
172
|
-
rel={tile.count === 0 ? 'noreferrer' : undefined}
|
|
173
|
-
class="ec-stat group"
|
|
174
|
-
style={`--tone: ${tile.tone};`}
|
|
175
|
-
>
|
|
176
|
-
<div class="flex items-baseline justify-between gap-2">
|
|
177
|
-
<span class="text-[11px] font-medium uppercase tracking-[0.14em] text-[rgb(var(--tone))]">{tile.label}</span>
|
|
178
|
-
{tile.count > 0 ? (
|
|
179
|
-
<ArrowUpRight className="h-3.5 w-3.5 text-[rgb(var(--ec-page-text-muted))] transition-transform duration-200 group-hover:-translate-y-0.5 group-hover:translate-x-0.5 group-hover:text-[rgb(var(--ec-accent))]" />
|
|
180
|
-
) : (
|
|
181
|
-
<Plus className="h-3.5 w-3.5 text-[rgb(var(--ec-page-text-muted))] transition-colors group-hover:text-[rgb(var(--ec-accent))]" />
|
|
182
|
-
)}
|
|
183
|
-
</div>
|
|
184
|
-
<div class="mt-3 flex items-baseline gap-2">
|
|
185
|
-
<span class="font-mono text-[34px] font-semibold leading-none tracking-tight text-[rgb(var(--ec-page-text))]">
|
|
186
|
-
{tile.count}
|
|
187
|
-
</span>
|
|
188
|
-
{tile.count === 0 && <span class="text-[12px] text-[rgb(var(--ec-page-text-muted))]">— add some</span>}
|
|
189
|
-
</div>
|
|
190
|
-
</a>
|
|
191
|
-
))
|
|
192
|
-
}
|
|
193
|
-
</section>
|
|
194
|
-
|
|
195
|
-
<!-- Eyebrow + Explore grid -->
|
|
196
|
-
<section class="mt-12">
|
|
197
|
-
<div class="ec-section-eyebrow">
|
|
198
|
-
<span class="text-[11px] font-semibold uppercase tracking-[0.18em] text-[rgb(var(--ec-accent))]">Explore</span>
|
|
199
|
-
<span class="ec-section-rule"></span>
|
|
200
|
-
<span class="font-mono text-[11px] text-[rgb(var(--ec-page-text-muted)/0.7)]">{exploreCards.length} sections</span>
|
|
201
|
-
</div>
|
|
202
|
-
|
|
203
|
-
<div class="ec-explore-grid mt-4">
|
|
204
|
-
{
|
|
205
|
-
exploreCards.map((card) => (
|
|
206
|
-
<a
|
|
207
|
-
href={card.href}
|
|
208
|
-
target={card.external ? '_blank' : undefined}
|
|
209
|
-
rel={card.external ? 'noreferrer' : undefined}
|
|
210
|
-
class:list={['ec-card group', card.feature ? 'ec-card--feature' : '']}
|
|
211
|
-
style={`--tone: ${card.tone};`}
|
|
212
|
-
>
|
|
213
|
-
<div class="flex items-start justify-between gap-3">
|
|
214
|
-
<div class="ec-card-icon">
|
|
215
|
-
<card.icon className="h-4 w-4" />
|
|
216
|
-
</div>
|
|
217
|
-
<ArrowUpRight className="h-3.5 w-3.5 text-[rgb(var(--ec-page-text-muted))] transition-transform duration-200 group-hover:-translate-y-0.5 group-hover:translate-x-0.5 group-hover:text-[rgb(var(--ec-accent))]" />
|
|
218
|
-
</div>
|
|
219
|
-
<h3 class="mt-4 text-[15px] font-semibold tracking-tight text-[rgb(var(--ec-page-text))]">{card.title}</h3>
|
|
220
|
-
<p class="mt-1.5 text-[13px] leading-relaxed text-[rgb(var(--ec-page-text-muted))]">{card.description}</p>
|
|
221
|
-
</a>
|
|
222
|
-
))
|
|
223
|
-
}
|
|
224
|
-
</div>
|
|
225
|
-
</section>
|
|
9
|
+
<div class="ec-home">
|
|
10
|
+
<CatalogDashboard />
|
|
226
11
|
</div>
|
|
227
12
|
</VerticalSideBarLayout>
|
|
228
13
|
|
|
229
14
|
<style is:global>
|
|
230
15
|
.ec-home {
|
|
231
|
-
/*
|
|
16
|
+
/* The layout pads its content: the homepage sets its own, wider spacing */
|
|
232
17
|
margin-left: calc(var(--ec-app-content-padding-left, 5rem) * -1);
|
|
233
18
|
margin-right: calc(var(--ec-app-content-padding-right, 5rem) * -1);
|
|
234
19
|
padding: 2rem 2rem 0;
|
|
235
20
|
}
|
|
236
|
-
.ec-hero {
|
|
237
|
-
padding-bottom: 1.5rem;
|
|
238
|
-
}
|
|
239
|
-
.ec-divider {
|
|
240
|
-
height: 1px;
|
|
241
|
-
background: linear-gradient(
|
|
242
|
-
to right,
|
|
243
|
-
transparent,
|
|
244
|
-
rgb(var(--ec-page-border)) 12%,
|
|
245
|
-
rgb(var(--ec-page-border)) 88%,
|
|
246
|
-
transparent
|
|
247
|
-
);
|
|
248
|
-
margin: 0.25rem 0 1.5rem;
|
|
249
|
-
}
|
|
250
|
-
.ec-stats {
|
|
251
|
-
display: grid;
|
|
252
|
-
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
253
|
-
gap: 0.75rem;
|
|
254
|
-
}
|
|
255
|
-
@media (min-width: 768px) {
|
|
256
|
-
.ec-stats {
|
|
257
|
-
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
.ec-stat {
|
|
261
|
-
position: relative;
|
|
262
|
-
display: flex;
|
|
263
|
-
flex-direction: column;
|
|
264
|
-
overflow: hidden;
|
|
265
|
-
border: 1px solid rgb(var(--ec-page-border));
|
|
266
|
-
border-radius: 0.75rem;
|
|
267
|
-
background:
|
|
268
|
-
radial-gradient(80% 100% at 0% 0%, rgb(var(--tone) / 0.05), transparent 60%), rgb(var(--ec-card-bg, var(--ec-page-bg)));
|
|
269
|
-
padding: 1rem 1.1rem 1.1rem;
|
|
270
|
-
transition:
|
|
271
|
-
border-color 200ms,
|
|
272
|
-
transform 200ms,
|
|
273
|
-
box-shadow 200ms;
|
|
274
|
-
text-decoration: none;
|
|
275
|
-
}
|
|
276
|
-
.ec-stat:hover {
|
|
277
|
-
border-color: rgb(var(--ec-accent) / 0.5);
|
|
278
|
-
transform: translateY(-1px);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
.ec-section-eyebrow {
|
|
282
|
-
display: flex;
|
|
283
|
-
align-items: center;
|
|
284
|
-
gap: 0.75rem;
|
|
285
|
-
}
|
|
286
|
-
.ec-section-rule {
|
|
287
|
-
flex: 1;
|
|
288
|
-
height: 1px;
|
|
289
|
-
background: linear-gradient(to right, rgb(var(--ec-accent) / 0.4), rgb(var(--ec-page-border)) 30%);
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
/* Explore grid: bordered cells with breathing room between them. */
|
|
293
|
-
.ec-explore-grid {
|
|
294
|
-
display: grid;
|
|
295
|
-
grid-template-columns: repeat(1, minmax(0, 1fr));
|
|
296
|
-
gap: 1rem;
|
|
297
|
-
}
|
|
298
|
-
@media (min-width: 640px) {
|
|
299
|
-
.ec-explore-grid {
|
|
300
|
-
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
@media (min-width: 1024px) {
|
|
304
|
-
.ec-explore-grid {
|
|
305
|
-
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
.ec-card {
|
|
310
|
-
position: relative;
|
|
311
|
-
display: flex;
|
|
312
|
-
flex-direction: column;
|
|
313
|
-
padding: 1.5rem 1.5rem 1.4rem;
|
|
314
|
-
text-decoration: none;
|
|
315
|
-
border: 1px solid rgb(var(--ec-page-border));
|
|
316
|
-
border-radius: 0.875rem;
|
|
317
|
-
background: rgb(var(--ec-card-bg, var(--ec-page-bg)));
|
|
318
|
-
transition:
|
|
319
|
-
background 200ms,
|
|
320
|
-
border-color 200ms;
|
|
321
|
-
min-height: 9.5rem;
|
|
322
|
-
}
|
|
323
|
-
.ec-card:hover {
|
|
324
|
-
background: rgb(var(--ec-page-bg) / 0.4);
|
|
325
|
-
border-color: rgb(var(--ec-accent) / 0.5);
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
.ec-card--feature {
|
|
329
|
-
background:
|
|
330
|
-
radial-gradient(60% 90% at 0% 0%, rgb(var(--tone) / 0.07), transparent 60%), rgb(var(--ec-card-bg, var(--ec-page-bg)));
|
|
331
|
-
}
|
|
332
|
-
.ec-card--feature:hover {
|
|
333
|
-
background: radial-gradient(60% 90% at 0% 0%, rgb(var(--ec-accent) / 0.12), transparent 60%), rgb(var(--ec-page-bg) / 0.4);
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
.ec-card-icon {
|
|
337
|
-
display: inline-flex;
|
|
338
|
-
height: 2.25rem;
|
|
339
|
-
width: 2.25rem;
|
|
340
|
-
align-items: center;
|
|
341
|
-
justify-content: center;
|
|
342
|
-
border-radius: 0.5rem;
|
|
343
|
-
background: rgb(var(--tone) / 0.12);
|
|
344
|
-
color: rgb(var(--tone));
|
|
345
|
-
transition:
|
|
346
|
-
background 200ms,
|
|
347
|
-
color 200ms;
|
|
348
|
-
}
|
|
349
|
-
.ec-card:hover .ec-card-icon {
|
|
350
|
-
background: rgb(var(--ec-accent) / 0.2);
|
|
351
|
-
color: rgb(var(--ec-accent));
|
|
352
|
-
}
|
|
353
|
-
.ec-card--feature .ec-card-icon {
|
|
354
|
-
height: 2.5rem;
|
|
355
|
-
width: 2.5rem;
|
|
356
|
-
border-radius: 0.625rem;
|
|
357
|
-
}
|
|
358
21
|
</style>
|
|
@@ -30,7 +30,9 @@ if (existsSync(pathToUserDefinedLandingPage)) {
|
|
|
30
30
|
import('@components/MDX/page-components'),
|
|
31
31
|
import('@components/MDX/components'),
|
|
32
32
|
]);
|
|
33
|
-
|
|
33
|
+
const { default: CatalogDashboard } = await import('@components/Homepage/CatalogDashboard.astro');
|
|
34
|
+
// A custom homepage can show the catalog dashboard too: <CatalogDashboard /> from Astro.props.components
|
|
35
|
+
customHomepageComponents = { ...pageComponents, ...mdxComponents(props), CatalogDashboard };
|
|
34
36
|
|
|
35
37
|
const rawContent = readFileSync(pathToUserDefinedLandingPage, 'utf-8');
|
|
36
38
|
nodeGraphs = getMDXComponentsByName(rawContent, 'NodeGraph') || [];
|
|
@@ -87,6 +87,17 @@ const readCommit = (filePath: string, age: 'newest' | 'oldest'): Commit => {
|
|
|
87
87
|
return parseCommit(match.groups.header);
|
|
88
88
|
};
|
|
89
89
|
|
|
90
|
+
/**
|
|
91
|
+
* When a file was last committed, from the history read once for the whole repository: never runs git for one file,
|
|
92
|
+
* so it's cheap to ask for many (e.g. every resource on the homepage). Undefined without git, or for a file that isn't
|
|
93
|
+
* committed.
|
|
94
|
+
*/
|
|
95
|
+
export function getLastCommitDate(filePath: string): Date | undefined {
|
|
96
|
+
if (!hasGit()) return undefined;
|
|
97
|
+
const commit = getNewestCommits()?.get(toRealPath(filePath));
|
|
98
|
+
return commit ? new Date(commit.timestamp) : undefined;
|
|
99
|
+
}
|
|
100
|
+
|
|
90
101
|
export function getGitHistory(
|
|
91
102
|
filePath: string,
|
|
92
103
|
{
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { statSync } from 'node:fs';
|
|
3
|
+
import { getCollection } from 'astro:content';
|
|
4
|
+
import { getLastCommitDate } from '@utils/git';
|
|
5
|
+
import { buildUrl } from '@utils/url-builder';
|
|
6
|
+
|
|
7
|
+
/** The resources the dashboard shows, by collection */
|
|
8
|
+
export type DashboardCollection = 'domains' | 'services' | 'events' | 'commands' | 'queries' | 'flows';
|
|
9
|
+
|
|
10
|
+
type Owner = { id: string };
|
|
11
|
+
|
|
12
|
+
/** The fields the dashboard reads from a collection entry */
|
|
13
|
+
export type DashboardEntry = {
|
|
14
|
+
filePath?: string;
|
|
15
|
+
data: {
|
|
16
|
+
id: string;
|
|
17
|
+
name?: string;
|
|
18
|
+
version: string;
|
|
19
|
+
summary?: string;
|
|
20
|
+
hidden?: boolean;
|
|
21
|
+
owners?: Owner[];
|
|
22
|
+
schemaPath?: string;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type DashboardResource = {
|
|
27
|
+
collection: DashboardCollection;
|
|
28
|
+
id: string;
|
|
29
|
+
version: string;
|
|
30
|
+
name: string;
|
|
31
|
+
summary?: string;
|
|
32
|
+
href: string;
|
|
33
|
+
owners: { id: string; name: string }[];
|
|
34
|
+
updatedAt?: number;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export type DashboardData = {
|
|
38
|
+
counts: Record<DashboardCollection | 'messages' | 'schemas' | 'teams' | 'users', number>;
|
|
39
|
+
recentlyUpdated: DashboardResource[];
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const MESSAGE_COLLECTIONS = ['events', 'commands', 'queries'] as const;
|
|
43
|
+
|
|
44
|
+
// The latest version of a resource that isn't hidden (older versions live in a `versioned` folder)
|
|
45
|
+
const isCurrent = (entry: DashboardEntry) => entry.data.hidden !== true && !entry.filePath?.includes('versioned');
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Everything the homepage dashboard shows, from the catalog's collections: counts, and what changed recently.
|
|
49
|
+
*/
|
|
50
|
+
export const buildDashboard = ({
|
|
51
|
+
collections,
|
|
52
|
+
teams,
|
|
53
|
+
users,
|
|
54
|
+
getUpdatedAt,
|
|
55
|
+
}: {
|
|
56
|
+
collections: Record<DashboardCollection, DashboardEntry[]>;
|
|
57
|
+
teams: DashboardEntry[];
|
|
58
|
+
users: DashboardEntry[];
|
|
59
|
+
/** When a resource's file last changed (unix ms), if known */
|
|
60
|
+
getUpdatedAt: (entry: DashboardEntry) => number | undefined;
|
|
61
|
+
}): DashboardData => {
|
|
62
|
+
const ownerNames = new Map<string, string>();
|
|
63
|
+
for (const owner of [...teams, ...users]) ownerNames.set(owner.data.id, owner.data.name ?? owner.data.id);
|
|
64
|
+
|
|
65
|
+
const toResource = (collection: DashboardCollection, entry: DashboardEntry): DashboardResource => ({
|
|
66
|
+
collection,
|
|
67
|
+
id: entry.data.id,
|
|
68
|
+
version: entry.data.version,
|
|
69
|
+
name: entry.data.name ?? entry.data.id,
|
|
70
|
+
summary: entry.data.summary?.trim() || undefined,
|
|
71
|
+
href: buildUrl(`/docs/${collection}/${entry.data.id}/${entry.data.version}`),
|
|
72
|
+
owners: (entry.data.owners ?? []).map(({ id }) => ({ id, name: ownerNames.get(id) ?? id })),
|
|
73
|
+
updatedAt: getUpdatedAt(entry),
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const current = Object.fromEntries(
|
|
77
|
+
Object.entries(collections).map(([collection, entries]) => [collection, entries.filter(isCurrent)])
|
|
78
|
+
) as Record<DashboardCollection, DashboardEntry[]>;
|
|
79
|
+
const messages = MESSAGE_COLLECTIONS.flatMap((collection) => current[collection]);
|
|
80
|
+
const all = (Object.keys(current) as DashboardCollection[]).flatMap((collection) =>
|
|
81
|
+
current[collection].map((entry) => ({ entry, resource: toResource(collection, entry) }))
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
const recentlyUpdated = all
|
|
85
|
+
.map(({ resource }) => resource)
|
|
86
|
+
.filter((resource) => resource.updatedAt !== undefined)
|
|
87
|
+
.sort((a, b) => b.updatedAt! - a.updatedAt!)
|
|
88
|
+
.slice(0, 8);
|
|
89
|
+
|
|
90
|
+
const withSchema = messages.filter((entry) => entry.data.schemaPath).length;
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
counts: {
|
|
94
|
+
domains: current.domains.length,
|
|
95
|
+
services: current.services.length,
|
|
96
|
+
events: current.events.length,
|
|
97
|
+
commands: current.commands.length,
|
|
98
|
+
queries: current.queries.length,
|
|
99
|
+
flows: current.flows.length,
|
|
100
|
+
messages: messages.length,
|
|
101
|
+
schemas: withSchema,
|
|
102
|
+
teams: teams.filter(isCurrent).length,
|
|
103
|
+
users: users.filter(isCurrent).length,
|
|
104
|
+
},
|
|
105
|
+
recentlyUpdated,
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// When a resource's file last changed: its last commit, or (without git, or before it's committed) the file's date
|
|
110
|
+
const getUpdatedAt = (entry: DashboardEntry) => {
|
|
111
|
+
if (!entry.filePath) return undefined;
|
|
112
|
+
const file = path.resolve(process.env.PROJECT_DIR || process.cwd(), entry.filePath);
|
|
113
|
+
const committed = getLastCommitDate(file);
|
|
114
|
+
if (committed) return committed.getTime();
|
|
115
|
+
try {
|
|
116
|
+
return statSync(file).mtimeMs;
|
|
117
|
+
} catch {
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
/** The homepage dashboard for this catalog */
|
|
123
|
+
export const getDashboard = async (): Promise<DashboardData> => {
|
|
124
|
+
const [domains, services, events, commands, queries, flows, teams, users] = await Promise.all([
|
|
125
|
+
getCollection('domains'),
|
|
126
|
+
getCollection('services'),
|
|
127
|
+
getCollection('events'),
|
|
128
|
+
getCollection('commands'),
|
|
129
|
+
getCollection('queries'),
|
|
130
|
+
getCollection('flows'),
|
|
131
|
+
getCollection('teams'),
|
|
132
|
+
getCollection('users'),
|
|
133
|
+
]);
|
|
134
|
+
return buildDashboard({
|
|
135
|
+
collections: { domains, services, events, commands, queries, flows } as unknown as Record<
|
|
136
|
+
DashboardCollection,
|
|
137
|
+
DashboardEntry[]
|
|
138
|
+
>,
|
|
139
|
+
teams: teams as unknown as DashboardEntry[],
|
|
140
|
+
users: users as unknown as DashboardEntry[],
|
|
141
|
+
getUpdatedAt,
|
|
142
|
+
});
|
|
143
|
+
};
|
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
},
|
|
8
8
|
"license": "BUSL-1.1",
|
|
9
9
|
"type": "module",
|
|
10
|
-
"version": "5.0.0-beta.
|
|
10
|
+
"version": "5.0.0-beta.30",
|
|
11
11
|
"publishConfig": {
|
|
12
12
|
"access": "public"
|
|
13
13
|
},
|
|
@@ -142,8 +142,8 @@
|
|
|
142
142
|
"yjs": "^13.6.33",
|
|
143
143
|
"zod": "^4.3.6",
|
|
144
144
|
"@eventcatalog/linter": "1.2.0-beta.4",
|
|
145
|
-
"@eventcatalog/
|
|
146
|
-
"@eventcatalog/
|
|
145
|
+
"@eventcatalog/sdk": "2.30.0-beta.1",
|
|
146
|
+
"@eventcatalog/visualiser": "^5.0.0-beta.7"
|
|
147
147
|
},
|
|
148
148
|
"devDependencies": {
|
|
149
149
|
"@astrojs/check": "^0.9.10",
|