@eventcatalog/core 5.7.2 → 5.7.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/analytics/analytics.cjs +1 -1
  2. package/dist/analytics/analytics.js +2 -2
  3. package/dist/analytics/log-build.cjs +1 -1
  4. package/dist/analytics/log-build.js +3 -3
  5. package/dist/{chunk-OR4VBY3F.js → chunk-3I4MO4G6.js} +1 -1
  6. package/dist/{chunk-B3FUBDJN.js → chunk-NNR5RP47.js} +1 -1
  7. package/dist/{chunk-EPI5BHEU.js → chunk-Q6ZCTZM3.js} +1 -1
  8. package/dist/{chunk-TYZENGGJ.js → chunk-SAB3WVWU.js} +1 -1
  9. package/dist/{chunk-NNTWQNQ5.js → chunk-SR6NZ4QL.js} +1 -1
  10. package/dist/constants.cjs +1 -1
  11. package/dist/constants.js +1 -1
  12. package/dist/docs/development/ask-your-architecture/02-eventcatalog-assistant/01-what-is-eventcatalog-assistant.md +8 -0
  13. package/dist/docs/development/ask-your-architecture/02-eventcatalog-assistant/02-configuration.md +24 -0
  14. package/dist/docs/development/guides/domains/06-visualize-domains/01-domain-diagram.md +2 -1
  15. package/dist/docs/development/studio/01-introduction.md +2 -1
  16. package/dist/docs/development/studio/04-collaborate-on-a-canvas.md +10 -2
  17. package/dist/docs/development/studio/05-connect-your-agent.md +4 -0
  18. package/dist/docs/development/studio/07-reference.md +40 -7
  19. package/dist/docs/development/studio/draw-with-ai.md +96 -0
  20. package/dist/eventcatalog.cjs +1 -1
  21. package/dist/eventcatalog.js +5 -5
  22. package/dist/generate.cjs +1 -1
  23. package/dist/generate.js +3 -3
  24. package/dist/utils/cli-logger.cjs +1 -1
  25. package/dist/utils/cli-logger.js +2 -2
  26. package/eventcatalog/src/components/MDX/Steps/Step.astro +4 -2
  27. package/eventcatalog/src/components/SchemaExplorer/DiffViewer.tsx +2 -103
  28. package/eventcatalog/src/components/SchemaExplorer/diff-dark-styles.ts +104 -0
  29. package/eventcatalog/src/features/mcp/apps/generated/viewer.html +68 -68
  30. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/changelog/_ChangelogPage.astro +297 -0
  31. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/changelog/_index.data.spec.ts +101 -0
  32. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/changelog/_index.data.ts +76 -47
  33. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/changelog/index.astro +4 -168
  34. package/eventcatalog/src/pages/docs/services/[id]/[version]/changelog/index.astro +14 -0
  35. package/eventcatalog/src/pages/docs/services/[id]/changelog/index.astro +3 -3
  36. package/eventcatalog/src/utils/node-graphs/channel-node-graph.ts +2 -2
  37. package/eventcatalog/src/utils/node-graphs/domain-levels-node-graph.ts +248 -71
  38. package/eventcatalog/src/utils/node-graphs/message-node-graph.ts +0 -87
  39. package/package.json +3 -3
@@ -0,0 +1,297 @@
1
+ ---
2
+ import Footer from '@layouts/Footer.astro';
3
+ import { getChangeLogs } from '@utils/collections/changelogs';
4
+ import { render, getEntry } from 'astro:content';
5
+ import mdxComponents from '@components/MDX/components';
6
+ import 'diff2html/bundles/css/diff2html.min.css';
7
+ import { getDiffDarkStyles } from '@components/SchemaExplorer/diff-dark-styles';
8
+
9
+ import { buildUrl } from '@utils/url-builder';
10
+ import { getPreviousVersion, getResourceNoun } from '@utils/collections/util';
11
+ import { getDiffsForCurrentAndPreviousVersion } from '@utils/file-diffs';
12
+ import { isIconPath, resolveIconUrl } from '@utils/icon';
13
+ import VerticalSideBarLayout from '@layouts/VerticalSideBarLayout.astro';
14
+ import Badge from '@components/Badge.astro';
15
+ import { ClientRouter } from 'astro:transitions';
16
+ import { HistoryIcon } from 'lucide-react';
17
+
18
+ interface Props {
19
+ changelogOwner: any;
20
+ }
21
+
22
+ const props = Astro.props.changelogOwner;
23
+
24
+ let collectionItem = props;
25
+ const logs = await getChangeLogs(props);
26
+
27
+ const { data } = props;
28
+ const latestVersion = data.latestVersion;
29
+
30
+ const renderedLogs = logs.map(async (log) => {
31
+ const logEntry = await getEntry('changelogs', log.id);
32
+ const { Content } = await render(logEntry as any);
33
+ return {
34
+ Content,
35
+ ...log,
36
+ };
37
+ });
38
+
39
+ const logsToRender = await Promise.all(renderedLogs);
40
+
41
+ const logListPromise = logsToRender.map(async (log) => {
42
+ const currentLogVersion = log.data.version;
43
+ const previousLogVersion = log.data.version ? getPreviousVersion(log.data.version, data.versions || []) : '';
44
+ return {
45
+ id: log.id,
46
+ // Anchor for the version, linked from the "Versions" list on the right
47
+ anchor: `v${log.data.version}`,
48
+ url: buildUrl(`/docs/${props.collection}/${props.data.id}/${log.data.version}`),
49
+ isLatest: log.data.version === latestVersion,
50
+ version: log.data.version,
51
+ previousVersion: previousLogVersion,
52
+ createdAt: log.data.createdAt,
53
+ badges: log.data.badges || [],
54
+ Content: log.Content,
55
+ diffs:
56
+ currentLogVersion && previousLogVersion
57
+ ? await getDiffsForCurrentAndPreviousVersion(
58
+ currentLogVersion,
59
+ previousLogVersion,
60
+ collectionItem.data.id,
61
+ props.allCollectionItems
62
+ )
63
+ : [],
64
+ };
65
+ });
66
+
67
+ const logList = await Promise.all(logListPromise);
68
+
69
+ const friendlyCollectionName = getResourceNoun(props.collection);
70
+ const headerIconSrc = isIconPath(props.data.styles?.icon) ? resolveIconUrl(props.data.styles!.icon!) : null;
71
+
72
+ const formatDate = (date?: Date) =>
73
+ date?.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric', timeZone: 'UTC' });
74
+
75
+ const releaseCount = `${logList.length} ${logList.length === 1 ? 'release' : 'releases'}`;
76
+ ---
77
+
78
+ <VerticalSideBarLayout title={`${props.data.name} | Changelog`} description={props.data.summary}>
79
+ <main class="flex docs-layout min-h-full bg-[rgb(var(--ec-page-bg))]">
80
+ <div class="flex docs-layout w-full">
81
+ <div class="w-full min-w-0 lg:mr-2 xl:pr-12 py-8 bg-[rgb(var(--ec-page-bg))]">
82
+ <div class="border-b border-[rgb(var(--ec-page-border))] md:pb-3 pt-4">
83
+ <div class="flex flex-col gap-4">
84
+ <span class="text-xs md:text-sm font-semibold text-[rgb(var(--ec-accent))]">Changelog</span>
85
+ <div class="flex items-center gap-3">
86
+ {
87
+ headerIconSrc && (
88
+ <img src={headerIconSrc} alt={props.data.name} class="w-8 h-8 md:w-10 md:h-10 object-contain rounded-md shrink-0" />
89
+ )
90
+ }
91
+ <h2 id="doc-page-header" class="text-2xl md:text-4xl font-bold text-[rgb(var(--ec-page-text))]">
92
+ {props.data.name}
93
+ </h2>
94
+ </div>
95
+ </div>
96
+ <p class="text-base pt-4 text-[rgb(var(--ec-page-text-muted))] font-light">
97
+ What changed in each version of {props.data.name}, newest first.
98
+ </p>
99
+ <div class="flex flex-wrap items-center gap-x-3 gap-y-2 pt-5 text-xs text-[rgb(var(--ec-page-text-muted))]">
100
+ <span>{releaseCount}</span>
101
+ {
102
+ latestVersion && (
103
+ <>
104
+ <span aria-hidden="true" class="h-3 w-px bg-[rgb(var(--ec-page-border))]" />
105
+ <span>Latest v{latestVersion}</span>
106
+ </>
107
+ )
108
+ }
109
+ </div>
110
+ </div>
111
+
112
+ {
113
+ logList.length === 0 && (
114
+ <div class="mt-8 rounded-lg border border-dashed border-[rgb(var(--ec-page-border))] px-6 py-10 text-center">
115
+ <HistoryIcon className="mx-auto h-6 w-6 text-[rgb(var(--ec-icon-color))]" strokeWidth={1.5} />
116
+ <p class="mt-3 text-sm font-semibold text-[rgb(var(--ec-page-text))]">No changelogs yet</p>
117
+ <p class="mt-1 text-sm text-[rgb(var(--ec-page-text-muted))]">
118
+ Add a <code>changelog.mdx</code> file next to this {friendlyCollectionName} to record what changed.
119
+ </p>
120
+ </div>
121
+ )
122
+ }
123
+
124
+ <ol class="changelog-timeline pt-4">
125
+ {
126
+ logList.map((log, index) => (
127
+ <li id={log.anchor} class="changelog-release relative scroll-mt-20 pl-8 pt-8">
128
+ {index !== logList.length - 1 && (
129
+ <span aria-hidden="true" class="absolute left-[5px] top-[2.9rem] -bottom-[2.6rem] w-px bg-[rgb(var(--ec-page-border))]" />
130
+ )}
131
+ <span
132
+ aria-hidden="true"
133
+ class={`absolute left-0 top-[2.6rem] h-[11px] w-[11px] rounded-full ring-4 ring-[rgb(var(--ec-page-bg))] ${
134
+ log.isLatest ? 'bg-[rgb(var(--ec-accent))]' : 'bg-[rgb(var(--ec-page-border))]'
135
+ }`}
136
+ />
137
+ <div class="flex flex-wrap items-center gap-x-3 gap-y-2">
138
+ <a
139
+ href={log.url}
140
+ class="text-xl font-bold text-[rgb(var(--ec-page-text))] hover:text-[rgb(var(--ec-accent))] transition-colors"
141
+ >
142
+ v{log.version}
143
+ </a>
144
+ {log.isLatest && (
145
+ <span class="rounded-full bg-[rgb(var(--ec-accent-subtle))] px-2 py-0.5 text-xs font-medium text-[rgb(var(--ec-accent))]">
146
+ Latest
147
+ </span>
148
+ )}
149
+ {log.createdAt && (
150
+ <time datetime={log.createdAt.toISOString()} class="text-sm text-[rgb(var(--ec-page-text-muted))]">
151
+ {formatDate(log.createdAt)}
152
+ </time>
153
+ )}
154
+ </div>
155
+ {log.badges.filter(Boolean).length > 0 && (
156
+ <div class="flex flex-wrap gap-1.5 pt-3">
157
+ {log.badges.filter(Boolean).map((badge: any) => (
158
+ <Badge badge={badge} />
159
+ ))}
160
+ </div>
161
+ )}
162
+ <div class="prose prose-md pt-2 pb-4 w-full text-[15px]">
163
+ <log.Content components={mdxComponents(props)} />
164
+ </div>
165
+ {log.diffs && log.diffs.length > 0 && (
166
+ <details class="changelog-diffs group mb-4 rounded-lg border border-[rgb(var(--ec-page-border))] overflow-hidden">
167
+ <summary class="flex cursor-pointer list-none items-center justify-between gap-3 bg-[rgb(var(--ec-content-hover))] px-4 py-2.5 text-sm">
168
+ <span class="text-[rgb(var(--ec-page-text))]">
169
+ <span class="font-semibold">v{log.previousVersion}</span>
170
+ <span class="mx-2 text-[rgb(var(--ec-page-text-muted))]">&rarr;</span>
171
+ <span class="font-semibold">v{log.version}</span>
172
+ </span>
173
+ <span class="text-xs text-[rgb(var(--ec-page-text-muted))]">
174
+ {log.diffs.length} {log.diffs.length === 1 ? 'file changed' : 'files changed'}
175
+ <span class="ml-1 inline-block transition-transform group-open:rotate-90">&rsaquo;</span>
176
+ </span>
177
+ </summary>
178
+ <div class="space-y-4 border-t border-[rgb(var(--ec-page-border))] p-2">
179
+ {log.diffs.map((diff) => (
180
+ <div set:html={diff} />
181
+ ))}
182
+ </div>
183
+ </details>
184
+ )}
185
+ </li>
186
+ ))
187
+ }
188
+ </ol>
189
+ <Footer />
190
+ </div>
191
+ {
192
+ logList.length > 0 && (
193
+ <aside class="hidden xl:block sticky top-[4rem] self-start w-[280px] max-h-[calc(100vh-4rem)] overflow-y-auto py-2 flex-shrink-0 pr-4 bg-[rgb(var(--ec-page-bg))]">
194
+ <div class="mt-8">
195
+ <h3 class="text-xs text-[rgb(var(--ec-page-text))] font-semibold capitalize flex items-center gap-2 mb-4">
196
+ <HistoryIcon className="w-4 h-4" />
197
+ Versions
198
+ </h3>
199
+ <nav id="changelog-versions" class="text-xs border-l border-[rgb(var(--ec-page-border))]">
200
+ {logList.map((log) => (
201
+ <a
202
+ href={`#${log.anchor}`}
203
+ data-version-anchor={log.anchor}
204
+ class="flex items-center justify-between gap-2 py-1.5 pl-3 pr-2.5 leading-5 text-[rgb(var(--ec-page-text-muted))] hover:text-[rgb(var(--ec-page-text))] border-l-2 border-transparent -ml-px transition-all duration-200"
205
+ >
206
+ <span>
207
+ v{log.version}
208
+ {log.isLatest && <span class="ml-1 text-[rgb(var(--ec-page-text-muted))]">(latest)</span>}
209
+ </span>
210
+ {log.createdAt && (
211
+ <span class="shrink-0 text-[11px] text-[rgb(var(--ec-page-text-muted))]">
212
+ {log.createdAt.toISOString().split('T')[0]}
213
+ </span>
214
+ )}
215
+ </a>
216
+ ))}
217
+ </nav>
218
+ </div>
219
+ </aside>
220
+ )
221
+ }
222
+ </div>
223
+
224
+ <style is:global set:html={getDiffDarkStyles(":root[data-theme='dark'] .changelog-diffs")} />
225
+ <style is:global>
226
+ .docs-layout .prose {
227
+ max-width: none;
228
+ overflow: auto;
229
+ }
230
+ .changelog-diffs summary::-webkit-details-marker {
231
+ display: none;
232
+ }
233
+ #changelog-versions a.is-active {
234
+ color: rgb(var(--ec-accent));
235
+ border-color: rgb(var(--ec-accent));
236
+ font-weight: 500;
237
+ }
238
+ </style>
239
+
240
+ <script>
241
+ declare global {
242
+ interface Window {
243
+ __changelogVersionsScroll?: (event: Event) => void;
244
+ }
245
+ }
246
+
247
+ // Highlight the version in view in the "Versions" list, like "On this page" does for headings.
248
+ function setupVersionList() {
249
+ if (window.__changelogVersionsScroll) {
250
+ document.removeEventListener('scroll', window.__changelogVersionsScroll, true);
251
+ window.__changelogVersionsScroll = undefined;
252
+ }
253
+
254
+ const nav = document.getElementById('changelog-versions');
255
+ if (!nav) return;
256
+
257
+ const links = Array.from(nav.querySelectorAll<HTMLAnchorElement>('a[data-version-anchor]'));
258
+ const setActive = (anchor: string) => {
259
+ links.forEach((link) => link.classList.toggle('is-active', link.dataset.versionAnchor === anchor));
260
+ };
261
+
262
+ let paused = false;
263
+ links.forEach((link) =>
264
+ link.addEventListener('click', () => {
265
+ setActive(link.dataset.versionAnchor!);
266
+ paused = true;
267
+ setTimeout(() => (paused = false), 800);
268
+ })
269
+ );
270
+
271
+ // Each release is one tall block, so pick the last one whose top has scrolled past the
272
+ // top third of the screen, or the last release once the page is scrolled to the bottom.
273
+ const releases = Array.from(document.querySelectorAll<HTMLElement>('.changelog-release'));
274
+ const onScroll = (event: Event) => {
275
+ // Ignore other scrolling areas, like the sidebar
276
+ if (paused || (event.target instanceof HTMLElement && !event.target.contains(releases[0]))) return;
277
+ const scroller =
278
+ event.target instanceof HTMLElement ? event.target : (document.scrollingElement as HTMLElement | null);
279
+ const atBottom = scroller && scroller.scrollTop + scroller.clientHeight >= scroller.scrollHeight - 4;
280
+ const current = atBottom
281
+ ? releases[releases.length - 1]
282
+ : releases.filter((release) => release.getBoundingClientRect().top <= window.innerHeight / 3).pop();
283
+ setActive((current ?? releases[0])?.id ?? '');
284
+ };
285
+
286
+ window.__changelogVersionsScroll = onScroll;
287
+ document.addEventListener('scroll', onScroll, { capture: true, passive: true });
288
+
289
+ setActive(decodeURIComponent(location.hash.slice(1)) || links[0]?.dataset.versionAnchor || '');
290
+ }
291
+
292
+ // The page uses the ClientRouter, so this fires on the first load and after every navigation.
293
+ document.addEventListener('astro:page-load', setupVersionList);
294
+ </script>
295
+ </main>
296
+ <ClientRouter />
297
+ </VerticalSideBarLayout>
@@ -0,0 +1,101 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import { getLatestServiceChangelogPaths, Page, ServicePage } from './_index.data';
3
+
4
+ const COLLECTIONS = ['agents', 'events', 'commands', 'queries', 'services', 'domains', 'systems', 'flows', 'containers'];
5
+
6
+ const mocks = vi.hoisted(() => ({
7
+ isSSR: vi.fn(),
8
+ isChangelogEnabled: vi.fn(),
9
+ loaders: {} as Record<string, ReturnType<typeof vi.fn>>,
10
+ getServices: vi.fn(),
11
+ }));
12
+
13
+ vi.mock('@utils/feature', () => ({
14
+ isSSR: () => mocks.isSSR(),
15
+ isChangelogEnabled: () => mocks.isChangelogEnabled(),
16
+ }));
17
+ vi.mock('@utils/changelog-resource-badge', () => ({
18
+ changelogResourceCollections: ['agents', 'events', 'commands', 'queries', 'services', 'domains', 'systems', 'flows', 'containers'],
19
+ }));
20
+ vi.mock('@utils/collections/services', () => ({
21
+ getServices: () => mocks.getServices(),
22
+ }));
23
+ vi.mock('@utils/page-loaders/page-data-loader', () => ({
24
+ pageDataLoader: new Proxy({}, { get: (_target, type: string) => mocks.loaders[type] }),
25
+ }));
26
+
27
+ const item = (id: string, version: string) => ({ collection: 'any', data: { id, version } });
28
+
29
+ describe('Changelog routes', () => {
30
+ beforeEach(() => {
31
+ mocks.isSSR.mockReset().mockReturnValue(false);
32
+ mocks.isChangelogEnabled.mockReset().mockReturnValue(true);
33
+ mocks.loaders = Object.fromEntries(COLLECTIONS.map((type) => [type, vi.fn().mockResolvedValue([item(`${type}-1`, '1.0.0')])]));
34
+ });
35
+
36
+ it('gives every type but services a path on the generic route', async () => {
37
+ const paths = await Page.getStaticPaths();
38
+
39
+ expect(paths.map(({ params }) => params.type)).toEqual(COLLECTIONS.filter((type) => type !== 'services'));
40
+ expect(paths[0].params).toEqual({ type: 'agents', id: 'agents-1', version: '1.0.0' });
41
+ expect(mocks.loaders.services).not.toHaveBeenCalled();
42
+ });
43
+
44
+ it('gives one path per service id and version on the service route', async () => {
45
+ const orders1 = item('Orders', '1.0.0');
46
+ mocks.loaders.services.mockResolvedValue([orders1, item('Orders', '2.0.0'), item('Billing', '1.0.0')]);
47
+
48
+ const paths = await ServicePage.getStaticPaths();
49
+
50
+ expect(paths.map(({ params }) => params)).toEqual([
51
+ { id: 'Orders', version: '1.0.0' },
52
+ { id: 'Orders', version: '2.0.0' },
53
+ { id: 'Billing', version: '1.0.0' },
54
+ ]);
55
+ expect(paths[0].props).toMatchObject({ type: 'services', ...orders1 });
56
+ });
57
+
58
+ it('has no static paths on a server', async () => {
59
+ mocks.isSSR.mockReturnValue(true);
60
+
61
+ expect(await Page.getStaticPaths()).toEqual([]);
62
+ expect(await ServicePage.getStaticPaths()).toEqual([]);
63
+ });
64
+
65
+ it('has no static paths when the changelog is disabled', async () => {
66
+ mocks.isChangelogEnabled.mockReturnValue(false);
67
+
68
+ expect(await Page.getStaticPaths()).toEqual([]);
69
+ expect(await ServicePage.getStaticPaths()).toEqual([]);
70
+ });
71
+
72
+ it('forces the services type when fetching on the service route', async () => {
73
+ mocks.loaders.services.mockResolvedValue([item('Orders', '1.0.0')]);
74
+
75
+ const data = await ServicePage.getData({ params: { id: 'Orders', version: '1.0.0', type: 'events' }, props: {} } as any);
76
+
77
+ expect(data).toMatchObject({ type: 'services', data: { id: 'Orders', version: '1.0.0' } });
78
+ expect(mocks.loaders.events).not.toHaveBeenCalled();
79
+ });
80
+
81
+ it('returns nothing for an unknown version or a disabled changelog', async () => {
82
+ mocks.loaders.services.mockResolvedValue([item('Orders', '1.0.0')]);
83
+
84
+ await expect(ServicePage.getData({ params: { id: 'Orders', version: '9.9.9' }, props: {} } as any)).resolves.toBeUndefined();
85
+
86
+ mocks.isChangelogEnabled.mockReturnValue(false);
87
+ await expect(ServicePage.getData({ params: { id: 'Orders', version: '1.0.0' }, props: {} } as any)).resolves.toBeUndefined();
88
+ });
89
+
90
+ it('gives the version-less service changelog alias a path for the latest version of each service', async () => {
91
+ mocks.loaders.services.mockResolvedValue([item('Orders', '1.0.0'), item('Orders', '2.0.0'), item('Billing', '1.0.0')]);
92
+ mocks.getServices.mockResolvedValue([item('Orders', '2.0.0'), item('Billing', '1.0.0')]);
93
+
94
+ const paths = await getLatestServiceChangelogPaths();
95
+
96
+ expect(paths.map(({ params, props }) => ({ params, redirectVersion: props?.redirectVersion }))).toEqual([
97
+ { params: { id: 'Orders' }, redirectVersion: '2.0.0' },
98
+ { params: { id: 'Billing' }, redirectVersion: '1.0.0' },
99
+ ]);
100
+ });
101
+ });
@@ -2,61 +2,90 @@ import { isSSR, isChangelogEnabled } from '@utils/feature';
2
2
  import { changelogResourceCollections } from '@utils/changelog-resource-badge';
3
3
  import { HybridPage } from '@utils/page-loaders/hybrid-page';
4
4
  import type { PageTypes } from '@types';
5
+ import { getLatestServicePaths } from '../../../../services/_latest-version-route';
5
6
 
6
- export class Page extends HybridPage {
7
- static get prerender(): boolean {
8
- return !isSSR();
9
- }
7
+ // Services have their own route (pages/docs/services/[id]/[version]/changelog), because the
8
+ // version-less alias /docs/services/[id]/[docType]/[docId] outranks this generic route and
9
+ // would otherwise catch /docs/services/{id}/{version}/changelog.
10
+ const GENERIC_ROUTE_TYPES = changelogResourceCollections.filter((type) => type !== 'services');
10
11
 
11
- static async getStaticPaths(): Promise<Array<{ params: any; props: any }>> {
12
- if (isSSR() || !isChangelogEnabled()) {
13
- return [];
12
+ type RouteParams = { type?: string; id?: string; version?: string };
13
+
14
+ const createChangelogPage = ({
15
+ types,
16
+ toParams,
17
+ typeOf,
18
+ }: {
19
+ types: PageTypes[];
20
+ toParams: (type: PageTypes, item: any) => RouteParams;
21
+ typeOf: (params: RouteParams) => string | undefined;
22
+ }) =>
23
+ class extends HybridPage {
24
+ static get prerender(): boolean {
25
+ return !isSSR();
14
26
  }
15
27
 
16
- const { pageDataLoader } = await import('@utils/page-loaders/page-data-loader');
17
-
18
- const itemTypes: PageTypes[] = [...changelogResourceCollections];
19
- const allItems = await Promise.all(itemTypes.map((type) => pageDataLoader[type]()));
20
-
21
- return allItems.flatMap((items, index) =>
22
- items.map((item) => ({
23
- params: {
24
- type: itemTypes[index],
25
- id: item.data.id,
26
- version: item.data.version,
27
- },
28
- props: {
29
- type: itemTypes[index],
30
- allCollectionItems: items,
31
- ...item,
32
- },
33
- }))
34
- );
35
- }
36
-
37
- protected static async fetchData(params: any) {
38
- const { type, id, version } = params;
39
-
40
- if (!type || !id || !version || !isChangelogEnabled()) {
41
- return null;
28
+ static async getStaticPaths(): Promise<Array<{ params: any; props: any }>> {
29
+ if (isSSR() || !isChangelogEnabled()) {
30
+ return [];
31
+ }
32
+
33
+ const { pageDataLoader } = await import('@utils/page-loaders/page-data-loader');
34
+
35
+ const allItems = await Promise.all(types.map((type) => pageDataLoader[type]()));
36
+
37
+ return allItems.flatMap((items, index) =>
38
+ items.map((item) => ({
39
+ params: toParams(types[index], item),
40
+ props: {
41
+ type: types[index],
42
+ allCollectionItems: items,
43
+ ...item,
44
+ },
45
+ }))
46
+ );
42
47
  }
43
48
 
44
- const { pageDataLoader } = await import('@utils/page-loaders/page-data-loader');
49
+ protected static async fetchData(params: RouteParams) {
50
+ const type = typeOf(params);
51
+ const { id, version } = params;
45
52
 
46
- // Get all items of the specified type
47
- const items = await pageDataLoader[type as PageTypes]();
53
+ if (!type || !id || !version || !isChangelogEnabled()) {
54
+ return null;
55
+ }
48
56
 
49
- // Find the specific item by id and version
50
- const item = items.find((i) => i.data.id === id && i.data.version === version);
57
+ const { pageDataLoader } = await import('@utils/page-loaders/page-data-loader');
51
58
 
52
- if (!item) {
53
- return null;
59
+ const items = await pageDataLoader[type as PageTypes]();
60
+ const item = items.find((i) => i.data.id === id && i.data.version === version);
61
+
62
+ if (!item) {
63
+ return null;
64
+ }
65
+
66
+ return {
67
+ type,
68
+ allCollectionItems: items,
69
+ ...item,
70
+ };
54
71
  }
72
+ };
73
+
74
+ export const Page = createChangelogPage({
75
+ types: GENERIC_ROUTE_TYPES as PageTypes[],
76
+ toParams: (type, item) => ({ type, id: item.data.id, version: item.data.version }),
77
+ typeOf: (params) => params.type,
78
+ });
79
+
80
+ export const ServicePage = createChangelogPage({
81
+ types: ['services'],
82
+ toParams: (_type, item) => ({ id: item.data.id, version: item.data.version }),
83
+ typeOf: () => 'services',
84
+ });
55
85
 
56
- return {
57
- type,
58
- allCollectionItems: items,
59
- ...item,
60
- };
61
- }
62
- }
86
+ // Static paths for the version-less /docs/services/[id]/changelog alias. The service route has no
87
+ // [type] segment, so put it back for getLatestServicePaths, which only keeps services.
88
+ export const getLatestServiceChangelogPaths = async () => {
89
+ const paths = await ServicePage.getStaticPaths();
90
+ return getLatestServicePaths(paths.map(({ params, props }) => ({ params: { ...params, type: 'services' }, props })));
91
+ };