@eventcatalog/core 2.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @eventcatalog/core
2
2
 
3
+ ## 2.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 9b92648: feat(core): added changelog support for domains, services and messages
8
+
3
9
  ## 2.1.0
4
10
 
5
11
  ### Minor Changes
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@eventcatalog/core",
3
3
  "type": "module",
4
- "version": "2.1.0",
4
+ "version": "2.2.0",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -44,7 +44,13 @@ const copyFiles = async ({ source, target, catalogFilesDir, pathToMarkdownFiles,
44
44
 
45
45
  //ensure the directory exists
46
46
  ensureDirSync(path.dirname(targetPath));
47
- fs.cpSync(file, targetPath.replace('index.md', 'index.mdx'));
47
+
48
+ if (file.includes('changelog.md')) {
49
+ const target = targetPath.replace('/content', '/content/changelogs');
50
+ fs.cpSync(file, target.replace('changelog.md', 'changelog.mdx'));
51
+ } else {
52
+ fs.cpSync(file, targetPath.replace('index.md', 'index.mdx').replace('changelog.md', 'changelog.mdx'));
53
+ }
48
54
  }
49
55
 
50
56
  // Copy all other files (non markdown) files into catalog-files directory (non collection)
@@ -121,6 +127,7 @@ export const catalogToAstro = async (source, astroContentDir, catalogFilesDir) =
121
127
  path.join(source, 'events/**/**/index.md'),
122
128
  path.join(source, 'services/**/events/**/index.md'),
123
129
  path.join(source, 'domains/**/events/**/index.md'),
130
+ path.join(source, 'events/**/**/changelog.md'),
124
131
  ],
125
132
  pathToAllFiles: [
126
133
  path.join(source, 'events/**'),
@@ -139,6 +146,7 @@ export const catalogToAstro = async (source, astroContentDir, catalogFilesDir) =
139
146
  path.join(source, 'commands/**/**/index.md'),
140
147
  path.join(source, 'services/**/commands/**/index.md'),
141
148
  path.join(source, 'domains/**/commands/**/index.md'),
149
+ path.join(source, 'commands/**/**/changelog.md'),
142
150
  ],
143
151
  pathToAllFiles: [
144
152
  path.join(source, 'commands/**'),
@@ -153,7 +161,11 @@ export const catalogToAstro = async (source, astroContentDir, catalogFilesDir) =
153
161
  source,
154
162
  target: astroContentDir,
155
163
  catalogFilesDir,
156
- pathToMarkdownFiles: [path.join(source, 'services/**/**/index.md'), path.join(source, 'domains/**/services/**/index.md')],
164
+ pathToMarkdownFiles: [
165
+ path.join(source, 'services/**/**/index.md'),
166
+ path.join(source, 'domains/**/services/**/index.md'),
167
+ path.join(source, 'services/**/**/changelog.md'),
168
+ ],
157
169
  pathToAllFiles: [path.join(source, 'services/**'), path.join(source, 'domains/**/services/**')],
158
170
  type: 'services',
159
171
  });
@@ -163,7 +175,7 @@ export const catalogToAstro = async (source, astroContentDir, catalogFilesDir) =
163
175
  source,
164
176
  target: astroContentDir,
165
177
  catalogFilesDir,
166
- pathToMarkdownFiles: [path.join(source, 'domains/**/**/index.md')],
178
+ pathToMarkdownFiles: [path.join(source, 'domains/**/**/index.md'), path.join(source, 'domains/**/**/changelog.md')],
167
179
  pathToAllFiles: [path.join(source, 'domains/**')],
168
180
  type: 'domains',
169
181
  });
@@ -0,0 +1,5 @@
1
+ ---
2
+ id: empty
3
+ ---
4
+
5
+ <!-- Do not delete this file, required for EC, you an ignore this file -->
@@ -30,7 +30,12 @@ for (let item of [...verifiedWatchList]) {
30
30
  for (let event of events) {
31
31
  const { path: eventPath, type } = event;
32
32
  const file = eventPath.split(item)[1];
33
- const newPath = path.join(contentPath, item, extensionReplacer(item, file));
33
+ let newPath = path.join(contentPath, item, extensionReplacer(item, file));
34
+
35
+ // Check if changlogs, they need to go into their own content folder
36
+ if (file.includes('changelog.md')) {
37
+ newPath = newPath.replace('src/content', 'src/content/changelogs');
38
+ }
34
39
 
35
40
  // If config files have changes
36
41
  if (eventPath.includes('eventcatalog.config.js') || eventPath.includes('eventcatalog.styles.css')) {
@@ -26,6 +26,7 @@ const currentPath = Astro.url.pathname;
26
26
  </option>
27
27
  })}
28
28
  </select>
29
+ <a href={buildUrl(`/docs/${collectionItem.collection}/${collectionItem.data.id}/${collectionItem.data.latestVersion}/changelog`)} class="text-[10px] text-gray-500">View changelogs</a>
29
30
  </div>
30
31
 
31
32
  <script>
@@ -38,6 +38,9 @@ const ownersList = owners.map((o) => ({
38
38
  <PillList title={`Services (${services.length})`} pills={serviceList} emptyMessage={`This domain does not contain any services.`} color="pink" client:load />
39
39
  <OwnersList title={`Service owners (${ownersList.length})`} owners={ownersList} emptyMessage={`This domain does not have any documented owners.`} client:load />
40
40
  {domain.data.versions && <VersionList versions={domain.data.versions} collectionItem={domain} />}
41
- <a href={buildUrl(`/visualiser/${domain.collection}/${domain.data.id}/${domain.data.version}`)} class="block text-center rounded-md w-full bg-white px-3.5 py-2.5 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-100/60 hover:text-purple-500">View in Visualiser</a>
41
+ <div class="space-y-2">
42
+ <a href={buildUrl(`/visualiser/${domain.collection}/${domain.data.id}/${domain.data.version}`)} class="block text-center rounded-md w-full bg-white px-3.5 py-2.5 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-100/60 hover:text-purple-500">View in Visualiser</a>
43
+ <a href={buildUrl(`${domain.data.version}/changelog`)} class="block text-center rounded-md w-full bg-white px-3.5 py-2.5 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-100/60 hover:text-purple-500">Changelog</a>
44
+ </div>
42
45
  </div>
43
46
  </aside>
@@ -47,7 +47,7 @@ const schemaURL = path.join(publicPath, schemaFilePath || '')
47
47
 
48
48
  ---
49
49
 
50
- <aside class="sticky top-28 left-0 space-y-8 h-full overflow-y-auto">
50
+ <aside class="sticky top-28 left-0 space-y-8 h-full overflow-y-auto pb-20 ">
51
51
  <div class="">
52
52
  <PillList color="pink" title={`${type} Producers (${producerList.length})`} pills={producerList} emptyMessage={`This ${type} does not get produced by any services.`} client:load />
53
53
  <PillList color="pink" title={`${type} Consumers (${consumerList.length})`} pills={consumerList} emptyMessage={`This ${type} does not get consumed by any services.`} client:load />
@@ -77,6 +77,9 @@ const schemaURL = path.join(publicPath, schemaFilePath || '')
77
77
  class="block text-center rounded-md w-full bg-white px-3.5 py-2.5 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-100/60 hover:text-purple-500"
78
78
  >View in Visualiser</a
79
79
  >
80
+
81
+ <a href={buildUrl(`${message.data.version}/changelog`)} class="block text-center rounded-md w-full bg-white px-3.5 py-2.5 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-100/60 hover:text-purple-500">Changelog</a>
82
+
80
83
  </div>
81
84
  </div>
82
85
  </aside>
@@ -46,8 +46,6 @@ const ownersList = owners.map((o) => ({
46
46
  const publicPath = service?.catalog?.publicPath;
47
47
  const schemaFilePath = service?.data?.schemaPath;
48
48
  const schemaURL = join(publicPath, schemaFilePath || '')
49
-
50
-
51
49
  ---
52
50
 
53
51
  <aside class="sticky top-28 left-0 space-y-8 h-full overflow-y-auto">
@@ -77,6 +75,7 @@ const schemaURL = join(publicPath, schemaFilePath || '')
77
75
  }
78
76
  <a href={buildUrl(`/visualiser/${service.collection}/${service.data.id}/${service.data.version}`)} class="block text-center rounded-md w-full bg-white px-3.5 py-2.5 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-100/60 hover:text-purple-500">View in Visualiser</a>
79
77
  <a id="open-api-button" href={buildUrl(`/docs/${service.collection}/${service.data.id}/${service.data.version}/spec`)} class="hidden text-center rounded-md w-full bg-white px-3.5 py-2.5 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-100/60 hover:text-purple-500">View API spec</a>
78
+ <a href={buildUrl(`${service.data.version}/changelog`)} class="block text-center rounded-md w-full bg-white px-3.5 py-2.5 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-100/60 hover:text-purple-500">Changelog</a>
80
79
  </div>
81
80
  </div>
82
81
  </aside>
@@ -13,6 +13,26 @@ const pages = defineCollection({
13
13
  }),
14
14
  });
15
15
 
16
+ const changelogs = defineCollection({
17
+ type: 'content',
18
+ schema: z.object({
19
+ createdAt: z.date().optional(),
20
+ badges: z.array(badge).optional(),
21
+ // Used by eventcatalog
22
+ version: z.string().optional(),
23
+ versions: z.array(z.string()).optional(),
24
+ latestVersion: z.string().optional(),
25
+ catalog: z
26
+ .object({
27
+ path: z.string(),
28
+ filePath: z.string(),
29
+ publicPath: z.string(),
30
+ type: z.string(),
31
+ })
32
+ .optional(),
33
+ }),
34
+ });
35
+
16
36
  // Create a union type for owners
17
37
  const ownerReference = z.union([reference('users'), reference('teams')]);
18
38
 
@@ -123,4 +143,5 @@ export const collections = {
123
143
  teams,
124
144
  domains,
125
145
  pages,
146
+ changelogs,
126
147
  };
@@ -0,0 +1,172 @@
1
+ ---
2
+ import type { CollectionEntry } from 'astro:content';
3
+
4
+ import { getEvents } from '@utils/events';
5
+ import { getServices } from '@utils/services/services';
6
+ import { getCommands } from '@utils/commands';
7
+ import { getDomains } from '@utils/domains/domains';
8
+ import type { CollectionTypes } from '@types';
9
+ import Layout from '@layouts/DocsLayout.astro';
10
+ import { getChangeLogs } from '@utils/changelogs/changelogs';
11
+ import { EnvelopeIcon, RectangleGroupIcon, ServerIcon } from '@heroicons/react/24/outline';
12
+
13
+ import { buildUrl } from '@utils/url-builder';
14
+
15
+ export async function getStaticPaths() {
16
+ const events = await getEvents();
17
+ const commands = await getCommands();
18
+ const services = await getServices();
19
+ const domains = await getDomains();
20
+
21
+ const buildPages = (collection: CollectionEntry<CollectionTypes>[]) => {
22
+
23
+ return collection.map((item) => ({
24
+ params: {
25
+ type: item.collection,
26
+ id: item.data.id,
27
+ version: item.data.version,
28
+ },
29
+ props: {
30
+ type: item.collection,
31
+ ...item,
32
+ },
33
+ }));
34
+ };
35
+
36
+ return [...buildPages(domains), ...buildPages(events), ...buildPages(services), ...buildPages(commands)];
37
+ }
38
+
39
+ const props = Astro.props;
40
+ const logs = await getChangeLogs(props);
41
+
42
+ const { data } = props;
43
+ const latestVersion = data.latestVersion;
44
+
45
+ const renderedLogs = await logs.map(async (log) => {
46
+ const { Content } = await log.render();
47
+ return {
48
+ Content,
49
+ ...log
50
+ }
51
+ });
52
+
53
+ const logsToRender = await Promise.all(renderedLogs);
54
+
55
+ const logList = logsToRender.map((log, index) => ({
56
+ id: log.id,
57
+ url: buildUrl(`/docs/${props.collection}/${props.data.id}`),
58
+ isLatest: log.data.version === latestVersion,
59
+ version: log.data.version,
60
+ createdAt: log.data.createdAt,
61
+ badges: log.data.badges || [],
62
+ Content: log.Content,
63
+ }));
64
+
65
+ const getBadge = () => {
66
+ if (props.collection === 'services') {
67
+ return { backgroundColor: 'pink', textColor: 'pink', content: 'Service', icon: ServerIcon, class: "text-pink-400" };
68
+ }
69
+ if (props.collection === 'events') {
70
+ return { backgroundColor: 'orange', textColor: 'orange', content: 'Event', icon: EnvelopeIcon, class: "text-orange-400" };
71
+ }
72
+ if (props.collection === 'commands') {
73
+ return { backgroundColor: 'blue', textColor: 'blue', content: 'Command', icon: EnvelopeIcon, class: "text-blue-400" };
74
+ }
75
+ if (props.collection === 'domains') {
76
+ return { backgroundColor: 'yellow', textColor: 'yellow', content: 'Domain', icon: RectangleGroupIcon, class: "text-yellow-400" };
77
+ }
78
+ };
79
+
80
+ const badges = [
81
+ getBadge()
82
+ ];
83
+
84
+ ---
85
+
86
+ <Layout title="ChangeLog">
87
+ <main class="flex-1 w-full lg:pr-10 md:pt-4">
88
+ <div class="border-b border-gray-200 flex justify-between items-start py-4 w-full ">
89
+ <div>
90
+ <h2 class="text-2xl md:text-4xl font-bold">{props.data.name} (Changelog)</h2>
91
+ <h2 class="text-lg pt-2 text-gray-500 font-light">{props.data.summary}</h2>
92
+ {
93
+ badges && (
94
+ <div class="flex flex-wrap py-2 pt-4">
95
+ {badges.map((badge: any) => (
96
+ <span class={`text-sm font-light text-gray-500 px-2 py-1 rounded-md mr-2 bg-${badge.backgroundColor}-100 space-x-1 border border-${badge.backgroundColor}-200 text-${badge.textColor}-800 flex items-center ${badge.class ? badge.class : ''} `}>
97
+ {badge.icon && <badge.icon className="w-4 h-4 inline-block mr-1 " />}
98
+ <span>{badge.content}</span>
99
+ </span>
100
+ ))}
101
+ </div>
102
+ )
103
+ }
104
+ </div>
105
+ </div>
106
+ {logList.length === 0 &&
107
+ <div class="py-4 text-gray-400 prose prose-md">
108
+ <p>No changelogs found.</p>
109
+ </div>
110
+ }
111
+ <div class="flow-root py-8">
112
+ <ul role="list" class="-mb-8">
113
+ {logList.map((log, index) => (
114
+ <li>
115
+ <div class="relative pb-8">
116
+ {index !== logList.length - 1 ? (
117
+ <span aria-hidden="true" class="absolute left-6 top-4 -ml-px h-full w-0.5 bg-gray-200" />
118
+ ) : null}
119
+ <div class="relative flex space-x-3">
120
+ <div>
121
+ <a
122
+ href={log.isLatest ? `${log.url}` : `${log.url}/${log.version}`}
123
+ class={'bg-purple-500 hover:bg-purple-400 text-white flex h-8 w-14 items-center justify-center rounded-full ring-8 ring-white'}
124
+ >
125
+ {log.version}
126
+ {/* <DocumentTextIcon aria-hidden="true" className="h-5 w-5 text-white" /> */}
127
+ </a>
128
+ </div>
129
+
130
+ </div>
131
+ <div class="pl-[70px] py-2 -mt-10">
132
+ {log.createdAt &&
133
+ <div class="pb-2">
134
+ <h3 class="text-2xl text-gray-800 font-bold">
135
+ <span>{log.createdAt.toISOString().split('T')[0]} {`${log.isLatest ? '(latest)' : ''}`}</span>
136
+ </h3>
137
+ </div>
138
+ }
139
+ {
140
+ log.badges && (
141
+ <div class="flex flex-wrap">
142
+ {log.badges.map((badge: any) => (
143
+ <span class={`text-sm font-light text-gray-500 px-2 py-1 rounded-md mr-2 bg-${badge.backgroundColor}-100 space-x-1 border border-${badge.backgroundColor}-200 text-${badge.textColor}-800 flex items-center ${badge.class ? badge.class : ''} `}>
144
+ {badge.icon && <badge.icon className="w-4 h-4 inline-block mr-1 " />}
145
+ <span>{badge.content}</span>
146
+ </span>
147
+ ))}
148
+ </div>
149
+ )
150
+ }
151
+ <div class="prose prose-md !max-w-none py-2">
152
+ <log.Content />
153
+ </div>
154
+ </div>
155
+ </div>
156
+ </li>
157
+ ))}
158
+ </ul>
159
+ </div>
160
+ <footer class="py-4 space-y-8 border-t border-gray-300">
161
+ <div class="flex justify-between items-center py-8 text-gray-500 text-sm font-light">
162
+ <div class="flex space-x-5">
163
+ <a href="https://github.com/event-catalog/eventcatalog" target="_blank"><svg class="w-5 h-5 bg-gray-400 hover:bg-purple-500 dark:hover:bg-gray-400" style="mask-image: url(&quot;/icons/github.svg&quot;); mask-repeat: no-repeat; mask-position: center center;"></svg></a>
164
+ <a href="https://x.com/event_catalog" target="_blank"><span class="sr-only">x</span><svg class="w-5 h-5 bg-gray-400 hover:bg-purple-500 dark:hover:bg-gray-400" style="mask-image: url(&quot;/icons/x-twitter.svg&quot;); mask-repeat: no-repeat; mask-position: center center;"></svg></a>
165
+ </div>
166
+ <a target="_blank" class="hover:text-purple-500 hover:underline text-gray-400 font-light not-prose" href="https://eventcatalog.dev">Powered by EventCatalog</a>
167
+ </div>
168
+ </footer>
169
+ </main>
170
+
171
+ </Layout>
172
+
@@ -0,0 +1,34 @@
1
+ import type { CollectionTypes } from '@types';
2
+ import { getCollection, type CollectionEntry } from 'astro:content';
3
+
4
+ export type ChangeLog = CollectionEntry<'changelogs'>;
5
+
6
+ export const getChangeLogs = async (item: CollectionEntry<CollectionTypes>): Promise<ChangeLog[]> => {
7
+ const { collection, data } = item;
8
+
9
+ // Get all logs for collection type and filter by given collection
10
+ const logs = await getCollection('changelogs', (log) => {
11
+ return log.id.includes(`${collection}/`) && log.id.includes(`/${data.id}/`);
12
+ });
13
+
14
+ const hydratedLogs = logs.map((log) => {
15
+ // Check if there is a version in the url
16
+ const isVersioned = log.id.includes('versioned');
17
+
18
+ const parts = log.id.split('/');
19
+ // hack to get the version of the id (url)
20
+ const version = parts[parts.length - 2];
21
+ return {
22
+ ...log,
23
+ data: {
24
+ ...log.data,
25
+ version: isVersioned ? version : data.latestVersion || 'latest',
26
+ },
27
+ };
28
+ });
29
+
30
+ // Order by version string
31
+ return hydratedLogs.sort((a, b) => {
32
+ return b.data.version.localeCompare(a.data.version);
33
+ });
34
+ };