@eventcatalog/core 4.12.0-beta.9 → 4.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) 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-GNFKUOXD.js → chunk-DZZDBFUM.js} +1 -1
  6. package/dist/{chunk-K3DWGBGQ.js → chunk-EVXNBTRA.js} +1 -1
  7. package/dist/{chunk-YOIPWS7L.js → chunk-HH6GPY4H.js} +1 -1
  8. package/dist/{chunk-UIHG2TPV.js → chunk-HHZFKWX7.js} +1 -1
  9. package/dist/{chunk-4S47JELQ.js → chunk-UWSBZYF5.js} +3 -3
  10. package/dist/constants.cjs +1 -1
  11. package/dist/constants.js +1 -1
  12. package/dist/docs/development/01-getting-started/develop-and-build.md +18 -0
  13. package/dist/docs/development/ask-your-architecture/02-eventcatalog-assistant/02-configuration.md +4 -0
  14. package/dist/docs/development/components/components/27-code-group.md +95 -0
  15. package/dist/docs/development/components/components/28-columns.md +115 -0
  16. package/dist/docs/development/customization/05-custom-pages-and-api-routes/02-create-a-custom-page.md +21 -0
  17. package/dist/docs/development/customization/05-custom-pages-and-api-routes/04-reference.md +24 -0
  18. package/dist/docs/development/deployment/hosting-options.md +8 -0
  19. package/dist/docs/development/guides/resources/messages/09-add-usage-examples.md +103 -9
  20. package/dist/docs/development/guides/resources/schemas/04-explore-schemas/01-schema-explorer.md +79 -13
  21. package/dist/docs/development/upgrading/upgrading.md +8 -3
  22. package/dist/docs/development/upgrading/v4.md +1 -1
  23. package/dist/eventcatalog.cjs +1 -1
  24. package/dist/eventcatalog.js +12 -12
  25. package/dist/federation/federate.js +3 -3
  26. package/dist/generate.cjs +1 -1
  27. package/dist/generate.js +3 -3
  28. package/dist/runtime-dependencies/manifest.json +1 -3
  29. package/dist/runtime-dependencies/set-cookie-parser.mjs +3 -0
  30. package/dist/utils/cli-logger.cjs +1 -1
  31. package/dist/utils/cli-logger.js +2 -2
  32. package/eventcatalog/auth.config.ts +3 -4
  33. package/eventcatalog/src/components/Header.astro +101 -33
  34. package/eventcatalog/src/enterprise/auth/[...auth].ts +1 -1
  35. package/eventcatalog/src/enterprise/auth/login.astro +2 -2
  36. package/eventcatalog/src/enterprise/auth/middleware/middleware-auth.ts +1 -1
  37. package/eventcatalog/src/utils/auth-astro/client.ts +89 -0
  38. package/eventcatalog/src/utils/auth-astro/config.ts +24 -0
  39. package/eventcatalog/src/utils/auth-astro/server.ts +74 -0
  40. package/package.json +3 -2
  41. package/dist/runtime-dependencies/auth-astro.mjs +0 -3
  42. package/dist/runtime-dependencies/auth-astro__client.mjs +0 -3
  43. package/dist/runtime-dependencies/auth-astro__server.mjs +0 -3
  44. package/dist/{chunk-U4YJSTGV.js → chunk-WRLNLWPW.js} +7 -7
@@ -11,21 +11,22 @@ import AddedIn from '@site/src/components/MDX/AddedIn';
11
11
 
12
12
  The Schema Explorer lets your teams quickly find the schema, understand who owns it, and who is producing or consuming it (see [demo](https://demo.eventcatalog.dev/schemas)).
13
13
 
14
- ![Example](../img/schema-explorer.png)
14
+ ![Schema Explorer with a list of schemas on the left and the Review Submitted schema open on the right](../img/schema-explorer.png)
15
15
 
16
16
  Using the Schema Explorer, you can:
17
17
 
18
18
  - Quickly find schemas in your Architecture
19
- - View diffs between versions of your schemas
20
- - Quickly find who is consuming or producing your schemas
21
- - Schema ownership to query who owns a schema
19
+ - Read usage examples written by the team that owns the schema
20
+ - See who is producing or consuming a schema, and which flows it is part of
21
+ - Compare versions of your schemas
22
+ - See schema ownership and where the schema comes from
22
23
  - Get API (GET) access to your schemas for mocking or testing
23
24
 
24
25
  ### How to use the Schema Explorer?
25
26
 
26
27
  You can access the Schema Explorer from the sidebar, or by going to the `/schemas/explorer` page.
27
28
 
28
- The page will take all the schemas from your EventCatalog and render them in a searchable list.
29
+ The page will take all the schemas from your EventCatalog and render them in a searchable list. Select a schema to open it on the right.
29
30
 
30
31
  :::tip Schema Path
31
32
  You need to set the `schemaPath` in your schema frontmatter to the path to your schema file for Events, Queries and Commands.
@@ -33,19 +34,84 @@ You need to set the `schemaPath` in your schema frontmatter to the path to your
33
34
  For services you need to specify the path to your specification file in the `specifications` frontmatter.
34
35
  :::
35
36
 
36
- ![Example](../img/schema-explorer-2.png)
37
-
38
37
  ##### Filters
39
- You can use the filters to quickly find schemas in your Architecture. You can filter by name, message type and schema format.
40
38
 
41
- ##### Schema Preview
39
+ Use the search box to find schemas by name. Select the filter button next to it to filter by type (events, commands, queries, or specifications) and by schema format.
42
40
 
43
- The schema preview will show you a preview of the schema in a readable format, you can use the `Schema` button to switch between different views of your schema (if they are supported, JSON or Avro).
41
+ <img src={require('../img/schema-explorer-2.png').default} alt="Schema Explorer search box and filters for type and format" style={{ width: '100%', maxWidth: '360px' }} />
44
42
 
45
- ##### API Access
43
+ ##### Schema pages
44
+
45
+ Every message schema also has its own page at `/schemas/{type}/{id}/{version}`, for example `/schemas/events/OrderCreated/1.0.0`. Share this link when you want someone to look at one schema.
46
+
47
+ ### Schema tabs
48
+
49
+ <AddedIn version="4.11.0" />
50
+
51
+ Each schema is split into tabs. EventCatalog only shows a tab when there is something to show, for example the **Flows** tab only appears when the message is part of a flow.
52
+
53
+ | Tab | What it shows |
54
+ | --- | ------------- |
55
+ | **Schema** | The raw schema, with syntax highlighting and a copy button. |
56
+ | **Properties** | A readable view of the schema properties (JSON Schema, Avro and Protobuf). |
57
+ | **Examples** | [Usage examples](/docs/development/guides/resources/messages/add-usage-examples) stored in the message's `examples/` folder. |
58
+ | **Producers & Consumers** | A graph of the services that produce and consume the message. |
59
+ | **Flows** | The [flows](/docs/development/guides/resources/flows/introduction) the message is part of. |
60
+ | **Versions** | Every version of the schema, with a side-by-side comparison. |
61
+ | **Details** | Schema metadata: format, version, file, source, and owners. |
62
+ | **API** | API access to the schema (EventCatalog Scale). |
63
+
64
+ ##### Link to a tab
65
+
66
+ The selected tab is stored in the `tab` query parameter, so you can link straight to it.
67
+
68
+ ```txt
69
+ /schemas/events/OrderCreated/1.0.0?tab=examples
70
+ ```
71
+
72
+ | Tab | `tab` value |
73
+ | --- | ----------- |
74
+ | Schema | `schema` (default) |
75
+ | Properties | `properties` |
76
+ | Examples | `examples` |
77
+ | Producers & Consumers | `producers-consumers` |
78
+ | Flows | `flows` |
79
+ | Versions | `versions` |
80
+ | Details | `details` |
81
+ | API | `api` |
82
+
83
+ If the tab does not exist for that message, EventCatalog opens the **Schema** tab.
84
+
85
+ ##### Examples
86
+
87
+ The **Examples** tab shows the examples your team has written for the message. Examples written in Markdown or MDX can explain the scenario and show code in several languages.
46
88
 
47
- For EventCatalog Scale users, you can get API (GET) access to your schemas for mocking or testing.
89
+ ![Examples tab showing a written example next to a code group](../img/schema-explorer-examples.png)
90
+
91
+ To add examples, see [Add usage examples](/docs/development/guides/resources/messages/add-usage-examples).
48
92
 
49
93
  ##### Producers and Consumers
50
94
 
51
- The producers and consumers section will show you who is producing or consuming the schema. You can click on the producer or consumer to see more information about them.
95
+ The **Producers & Consumers** tab shows the services that produce and consume the message, using the same graph as the message page. Click on a producer or consumer to see more information about them.
96
+
97
+ ![Producers and Consumers tab showing the Review API publishing Review Submitted, which the Review Moderation Worker subscribes to](../img/schema-explorer-producers-consumers.png)
98
+
99
+ ##### Flows
100
+
101
+ The **Flows** tab shows the flows the message is part of. If the message is used in more than one flow, pick the flow from the dropdown. Use **Open flow** to go to the flow page.
102
+
103
+ ![Flows tab showing the Review Submission flow](../img/schema-explorer-flows.png)
104
+
105
+ ##### Versions
106
+
107
+ The **Versions** tab lists every version of the schema. Select a version to view it, or pick a **From** and **To** version to compare them side by side. Use **Expand** to open the comparison in a larger view.
108
+
109
+ You can also switch versions from the version dropdown next to the schema name.
110
+
111
+ ##### Details
112
+
113
+ The **Details** tab shows the schema name, format, message type, version, file, and owners. When a schema comes from an external source, the tab also shows where it came from.
114
+
115
+ ##### API Access
116
+
117
+ For EventCatalog Scale users, you can get API (GET) access to your schemas for mocking or testing. See [Schema API](/docs/development/guides/resources/schemas/schema-api).
@@ -38,7 +38,11 @@ To upgrade your EventCatalog you can find the packages `@eventcatalog/core` your
38
38
  Once you upgrade the version number, run `npm install` to install the latest updates.
39
39
 
40
40
  :::tip
41
- If you don't see the changes you expect, try removing the `.eventcatalog-core` folder, `node_modules` folder and install fresh again.
41
+ If you don't see the changes you expect, try removing the `.astro` folder, the `.eventcatalog-core` folder (if your project has one) and the `node_modules` folder, then install fresh again.
42
+ :::
43
+
44
+ :::info
45
+ From EventCatalog 4.12, EventCatalog no longer creates a `.eventcatalog-core` folder. If your project still has one from an older version, you can delete it once any older EventCatalog processes have stopped.
42
46
  :::
43
47
 
44
48
  ### Having problems?
@@ -46,9 +50,10 @@ If you don't see the changes you expect, try removing the `.eventcatalog-core` f
46
50
  Trying to upgrade and having issues? Try these steps
47
51
 
48
52
  1. Delete `node_modules` folder
49
- 1. Delete `.eventcatalog-core` folder
53
+ 1. Delete `.astro` folder
54
+ 1. Delete `.eventcatalog-core` folder (if you have one)
50
55
  1. Delete `package-lock.json` (if you have one).
51
- 1. Run `npm run i`
56
+ 1. Run `npm install`
52
57
  1. Verify if issues are fixed
53
58
 
54
59
  Still having issues?
@@ -43,7 +43,7 @@ Before upgrading, we recommend:
43
43
 
44
44
  1. Create a branch for the upgrade.
45
45
  1. Upgrade `@eventcatalog/core`.
46
- 1. Remove `.eventcatalog-core`, `node_modules`, and your lock file if the catalog does not pick up the new version.
46
+ 1. Remove `.astro`, `.eventcatalog-core` (if you have one), `node_modules`, and your lock file if the catalog does not pick up the new version.
47
47
  1. Run your catalog locally and check custom pages, custom navigation, and visualiser links.
48
48
 
49
49
  ## Removed
@@ -181,7 +181,7 @@ var verifyRequiredFieldsAreInCatalogConfigFile = async (projectDirectory) => {
181
181
  var import_picocolors = __toESM(require("picocolors"), 1);
182
182
 
183
183
  // package.json
184
- var version = "4.12.0-beta.9";
184
+ var version = "4.12.1";
185
185
 
186
186
  // src/constants.ts
187
187
  var VERSION = version;
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  log_build_default
3
- } from "./chunk-4S47JELQ.js";
3
+ } from "./chunk-UWSBZYF5.js";
4
+ import "./chunk-HH6GPY4H.js";
4
5
  import "./chunk-ZAZHAVKB.js";
5
6
  import {
6
7
  runMigrations
@@ -10,18 +11,17 @@ import {
10
11
  FederationConflictError,
11
12
  FederationDiagnosticError,
12
13
  federateCatalog
13
- } from "./chunk-U4YJSTGV.js";
14
- import "./chunk-WRNH5C3U.js";
15
- import "./chunk-5EPNFDT5.js";
16
- import "./chunk-A2RZR3U4.js";
17
- import "./chunk-SDZQJTJW.js";
18
- import "./chunk-352FGP6W.js";
19
- import "./chunk-R7Z5ALYI.js";
14
+ } from "./chunk-WRLNLWPW.js";
20
15
  import {
21
16
  getFederationDiagnosticCounts,
22
17
  getVisibleFederationDiagnostics
23
18
  } from "./chunk-JJPB6EOZ.js";
24
19
  import "./chunk-FV56YFM4.js";
20
+ import "./chunk-WRNH5C3U.js";
21
+ import "./chunk-5EPNFDT5.js";
22
+ import "./chunk-A2RZR3U4.js";
23
+ import "./chunk-SDZQJTJW.js";
24
+ import "./chunk-352FGP6W.js";
25
25
  import {
26
26
  getProjectOutDir,
27
27
  isAuthEnabled,
@@ -30,10 +30,10 @@ import {
30
30
  } from "./chunk-MDWB5HLO.js";
31
31
  import {
32
32
  generate
33
- } from "./chunk-K3DWGBGQ.js";
33
+ } from "./chunk-EVXNBTRA.js";
34
34
  import {
35
35
  logger
36
- } from "./chunk-GNFKUOXD.js";
36
+ } from "./chunk-DZZDBFUM.js";
37
37
  import {
38
38
  resolve_catalog_dependencies_default
39
39
  } from "./chunk-36RYTYT5.js";
@@ -47,7 +47,7 @@ import {
47
47
  import {
48
48
  watch
49
49
  } from "./chunk-U63GDPPY.js";
50
- import "./chunk-YOIPWS7L.js";
50
+ import "./chunk-R7Z5ALYI.js";
51
51
  import {
52
52
  getAstroConfigPath
53
53
  } from "./chunk-VIJJ7JKH.js";
@@ -63,7 +63,7 @@ import {
63
63
  } from "./chunk-LFQJXLHG.js";
64
64
  import {
65
65
  VERSION
66
- } from "./chunk-UIHG2TPV.js";
66
+ } from "./chunk-HHZFKWX7.js";
67
67
  import {
68
68
  getEventCatalogConfigFile,
69
69
  verifyRequiredFieldsAreInCatalogConfigFile
@@ -2,15 +2,15 @@ import {
2
2
  FederationConflictError,
3
3
  FederationDiagnosticError,
4
4
  federateCatalog
5
- } from "../chunk-U4YJSTGV.js";
5
+ } from "../chunk-WRLNLWPW.js";
6
+ import "../chunk-JJPB6EOZ.js";
7
+ import "../chunk-FV56YFM4.js";
6
8
  import "../chunk-WRNH5C3U.js";
7
9
  import "../chunk-5EPNFDT5.js";
8
10
  import "../chunk-A2RZR3U4.js";
9
11
  import "../chunk-SDZQJTJW.js";
10
12
  import "../chunk-352FGP6W.js";
11
13
  import "../chunk-R7Z5ALYI.js";
12
- import "../chunk-JJPB6EOZ.js";
13
- import "../chunk-FV56YFM4.js";
14
14
  import "../chunk-RFIXKATC.js";
15
15
  export {
16
16
  FederationConflictError,
package/dist/generate.cjs CHANGED
@@ -112,7 +112,7 @@ var getEventCatalogConfigFile = async (projectDirectory) => {
112
112
  var import_picocolors = __toESM(require("picocolors"), 1);
113
113
 
114
114
  // package.json
115
- var version = "4.12.0-beta.9";
115
+ var version = "4.12.1";
116
116
 
117
117
  // src/constants.ts
118
118
  var VERSION = version;
package/dist/generate.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  generate
3
- } from "./chunk-K3DWGBGQ.js";
4
- import "./chunk-GNFKUOXD.js";
5
- import "./chunk-UIHG2TPV.js";
3
+ } from "./chunk-EVXNBTRA.js";
4
+ import "./chunk-DZZDBFUM.js";
5
+ import "./chunk-HHZFKWX7.js";
6
6
  import "./chunk-RFIXKATC.js";
7
7
  export {
8
8
  generate
@@ -65,9 +65,6 @@
65
65
  "astro/logger/node": "astro__logger__node.mjs",
66
66
  "astro/types": "astro__types.mjs",
67
67
  "astro/zod": "astro__zod.mjs",
68
- "auth-astro": "auth-astro.mjs",
69
- "auth-astro/client": "auth-astro__client.mjs",
70
- "auth-astro/server": "auth-astro__server.mjs",
71
68
  "boxen": "boxen.mjs",
72
69
  "commander": "commander.mjs",
73
70
  "cross-env": "cross-env.mjs",
@@ -123,6 +120,7 @@
123
120
  "remark-gfm": "remark-gfm.mjs",
124
121
  "rimraf": "rimraf.mjs",
125
122
  "semver": "semver.mjs",
123
+ "set-cookie-parser": "set-cookie-parser.mjs",
126
124
  "shelljs": "shelljs.mjs",
127
125
  "sonner": "sonner.mjs",
128
126
  "sql.js": "sql.js.mjs",
@@ -0,0 +1,3 @@
1
+ export * from "set-cookie-parser";
2
+ import * as dependency from "set-cookie-parser";
3
+ export default dependency.default;
@@ -36,7 +36,7 @@ module.exports = __toCommonJS(cli_logger_exports);
36
36
  var import_picocolors = __toESM(require("picocolors"), 1);
37
37
 
38
38
  // package.json
39
- var version = "4.12.0-beta.9";
39
+ var version = "4.12.1";
40
40
 
41
41
  // src/constants.ts
42
42
  var VERSION = version;
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  logger
3
- } from "../chunk-GNFKUOXD.js";
4
- import "../chunk-UIHG2TPV.js";
3
+ } from "../chunk-DZZDBFUM.js";
4
+ import "../chunk-HHZFKWX7.js";
5
5
  export {
6
6
  logger
7
7
  };
@@ -1,4 +1,4 @@
1
- import { defineConfig } from 'auth-astro';
1
+ import { defineConfig } from '@utils/auth-astro/config';
2
2
  import { join } from 'node:path';
3
3
  import GitHub from '@auth/core/providers/github';
4
4
  import Okta from '@auth/core/providers/okta';
@@ -22,9 +22,8 @@ const getAuthProviders = async () => {
22
22
  // GitHub provider
23
23
  if (authConfig.providers?.github) {
24
24
  const githubConfig = authConfig.providers.github;
25
- // GitHub sends an RFC 9207 `iss` parameter on OAuth callbacks. Pin the issuer so
26
- // validation works even when the resolved @auth/core copy predates the upstream
27
- // default (auth-astro's peer range can hoist an older @auth/core in user projects).
25
+ // GitHub sends an RFC 9207 `iss` parameter on OAuth callbacks. Pin the issuer,
26
+ // including for GitHub Enterprise base URLs, so validation works.
28
27
  const githubBaseUrl = githubConfig?.enterprise?.baseUrl ?? 'https://github.com';
29
28
  providers.push(
30
29
  GitHub({
@@ -8,7 +8,7 @@ import {
8
8
  isEventCatalogChatVisible,
9
9
  isEventCatalogChatEnabled,
10
10
  } from '@utils/feature';
11
- import { getSession } from 'auth-astro/server';
11
+ import { getSession } from '@utils/auth-astro/server';
12
12
  import { isAuthEnabled, isSSR } from '@utils/feature';
13
13
  import { EnvironmentDropdown } from './EnvironmentDropdown';
14
14
  import ChatPanelButton from './ChatPanel/ChatPanelButton';
@@ -28,6 +28,27 @@ const logo = {
28
28
  };
29
29
 
30
30
  const repositoryUrl = catalog?.repositoryUrl || 'https://github.com/event-catalog/eventcatalog';
31
+
32
+ const user = session?.user as { name?: string; email?: string; image?: string; provider?: string } | undefined;
33
+ const userName = user?.name || user?.email || 'User';
34
+ const userInitials =
35
+ userName
36
+ .split(/[\s@.]+/)
37
+ .filter(Boolean)
38
+ .map((part) => part[0])
39
+ .join('')
40
+ .substring(0, 2)
41
+ .toUpperCase() || 'U';
42
+ // Google avatar URLs are blocked by some browsers' tracking protection, so fall back to initials
43
+ const userImage = user?.image && !user.image.includes('googleusercontent.com') ? user.image : undefined;
44
+ const providerNames: Record<string, string> = {
45
+ github: 'GitHub',
46
+ google: 'Google',
47
+ okta: 'Okta',
48
+ auth0: 'Auth0',
49
+ 'microsoft-entra-id': 'Microsoft Entra ID',
50
+ };
51
+ const providerName = user?.provider ? providerNames[user.provider] || user.provider : undefined;
31
52
  ---
32
53
 
33
54
  <nav
@@ -58,46 +79,90 @@ const repositoryUrl = catalog?.repositoryUrl || 'https://github.com/event-catalo
58
79
  <button
59
80
  id="profile-menu-button"
60
81
  type="button"
61
- class="flex items-center focus:outline-hidden focus:ring-2 focus:ring-offset-2 focus:ring-gray-400 rounded-full"
82
+ class="group flex items-center gap-1 rounded-full p-0.5 pr-1.5 text-[rgb(var(--ec-icon-color))] hover:text-[rgb(var(--ec-icon-hover))] hover:bg-[rgb(var(--ec-dropdown-hover))] aria-expanded:bg-[rgb(var(--ec-dropdown-hover))] transition-colors focus:outline-hidden focus-visible:ring-2 focus-visible:ring-[rgb(var(--ec-accent))]"
62
83
  aria-expanded="false"
63
- aria-haspopup="true"
64
- aria-label="User menu"
84
+ aria-haspopup="menu"
85
+ aria-controls="profile-dropdown"
86
+ aria-label={`Account menu for ${userName}`}
65
87
  >
66
- {session.user?.image && !session.user?.image?.includes('googleusercontent.com') ? (
88
+ {userImage ? (
67
89
  <img
68
- src={session.user.image}
69
- alt={session.user?.name || 'User'}
70
- class="h-9 w-9 rounded-full border-2 border-gray-200 hover:border-gray-400 transition-all shadow-xs"
90
+ src={userImage}
91
+ alt=""
92
+ class="h-8 w-8 rounded-full object-cover ring-1 ring-[rgb(var(--ec-page-border))]"
71
93
  />
72
94
  ) : (
73
- <div class="h-9 w-9 rounded-full border-2 border-gray-200 hover:border-gray-400 transition-all bg-gray-100 hover:bg-gray-200 flex items-center justify-center text-sm font-medium text-gray-600 shadow-xs">
74
- {session.user?.name
75
- ? session.user.name
76
- .split(' ')
77
- .map((n) => n[0])
78
- .join('')
79
- .substring(0, 2)
80
- .toUpperCase()
81
- : 'U'}
82
- </div>
95
+ <span class="h-8 w-8 rounded-full bg-[rgb(var(--ec-accent-subtle))] text-[rgb(var(--ec-accent))] ring-1 ring-[rgb(var(--ec-page-border))] flex items-center justify-center text-xs font-semibold">
96
+ {userInitials}
97
+ </span>
83
98
  )}
99
+ <svg
100
+ viewBox="0 0 24 24"
101
+ class="h-4 w-4 transition-transform group-aria-expanded:rotate-180"
102
+ fill="none"
103
+ stroke="currentColor"
104
+ stroke-width="2"
105
+ stroke-linecap="round"
106
+ stroke-linejoin="round"
107
+ aria-hidden="true"
108
+ >
109
+ <path d="m6 9 6 6 6-6" />
110
+ </svg>
84
111
  </button>
85
112
  <div
86
113
  id="profile-dropdown"
87
- class="ec-hidden absolute right-0 mt-2 w-48 bg-[rgb(var(--ec-dropdown-bg))] rounded-lg shadow-md py-1 z-50 border-2 border-[rgb(var(--ec-dropdown-border))] overflow-hidden"
114
+ role="menu"
115
+ aria-labelledby="profile-menu-button"
116
+ class="ec-hidden absolute right-0 mt-2 w-64 bg-[rgb(var(--ec-dropdown-bg))] rounded-xl shadow-lg z-50 border border-[rgb(var(--ec-dropdown-border))] overflow-hidden"
88
117
  >
89
- <div class="px-4 py-2 text-sm text-[rgb(var(--ec-dropdown-text))] border-b border-[rgb(var(--ec-dropdown-border))]">
90
- <div class="font-semibold">{session.user?.name || 'User'}</div>
91
- {session.user?.email && (
92
- <div class="text-[rgb(var(--ec-icon-color))] text-xs mt-0.5">{session.user.email}</div>
118
+ <div class="flex items-center gap-3 px-4 py-3 border-b border-[rgb(var(--ec-dropdown-border))]">
119
+ {userImage ? (
120
+ <img
121
+ src={userImage}
122
+ alt=""
123
+ class="h-10 w-10 shrink-0 rounded-full object-cover ring-1 ring-[rgb(var(--ec-page-border))]"
124
+ />
125
+ ) : (
126
+ <span class="h-10 w-10 shrink-0 rounded-full bg-[rgb(var(--ec-accent-subtle))] text-[rgb(var(--ec-accent))] flex items-center justify-center text-sm font-semibold">
127
+ {userInitials}
128
+ </span>
93
129
  )}
130
+ <div class="min-w-0">
131
+ <div class="truncate text-sm font-semibold text-[rgb(var(--ec-dropdown-text))]">{userName}</div>
132
+ {user?.email && user.email !== userName && (
133
+ <div class="truncate text-xs font-normal text-[rgb(var(--ec-page-text-muted))]">{user.email}</div>
134
+ )}
135
+ {providerName && (
136
+ <div class="truncate text-xs font-normal text-[rgb(var(--ec-page-text-muted))]">
137
+ Signed in with {providerName}
138
+ </div>
139
+ )}
140
+ </div>
141
+ </div>
142
+ <div class="p-1">
143
+ <button
144
+ id="signout-btn"
145
+ type="button"
146
+ role="menuitem"
147
+ class="flex w-full items-center gap-2 rounded-lg px-3 py-2 text-left text-sm font-medium text-[rgb(var(--ec-dropdown-text))] hover:bg-[rgb(var(--ec-dropdown-hover))] focus:outline-hidden focus-visible:bg-[rgb(var(--ec-dropdown-hover))] transition-colors"
148
+ >
149
+ <svg
150
+ viewBox="0 0 24 24"
151
+ class="h-4 w-4 text-[rgb(var(--ec-icon-color))]"
152
+ fill="none"
153
+ stroke="currentColor"
154
+ stroke-width="2"
155
+ stroke-linecap="round"
156
+ stroke-linejoin="round"
157
+ aria-hidden="true"
158
+ >
159
+ <path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" />
160
+ <polyline points="16 17 21 12 16 7" />
161
+ <line x1="21" x2="9" y1="12" y2="12" />
162
+ </svg>
163
+ Sign out
164
+ </button>
94
165
  </div>
95
- <button
96
- id="signout-btn"
97
- class="block w-full text-left px-4 py-2 text-sm text-[rgb(var(--ec-dropdown-text))] hover:bg-[rgb(var(--ec-dropdown-hover))] transition-colors font-medium"
98
- >
99
- Sign out
100
- </button>
101
166
  </div>
102
167
  </div>
103
168
  </div>
@@ -210,9 +275,15 @@ const repositoryUrl = catalog?.repositoryUrl || 'https://github.com/event-catalo
210
275
  <!-- Spacer to prevent content from being hidden under the fixed header -->
211
276
 
212
277
  <script>
213
- import { signOut } from 'auth-astro/client';
278
+ import { signOut } from '@utils/auth-astro/client';
214
279
 
215
280
  function initializeHeader() {
281
+ // ClientRouter fires astro:page-load on the first load too, and the header persists across
282
+ // navigations. Bind once per element so clicks don't toggle the menus open and shut again.
283
+ const header = document.getElementById('eventcatalog-header');
284
+ if (!header || header.dataset.initialized === 'true') return;
285
+ header.dataset.initialized = 'true';
286
+
216
287
  const menuToggle = document.getElementById('menu-toggle');
217
288
  const mobileMenu = document.getElementById('mobile-menu');
218
289
 
@@ -261,10 +332,7 @@ const repositoryUrl = catalog?.repositoryUrl || 'https://github.com/event-catalo
261
332
  }
262
333
  }
263
334
 
264
- // Initialize on first load
265
335
  initializeHeader();
266
-
267
- // Re-initialize after Astro view transitions (needed because of transition:persist on header)
268
336
  document.addEventListener('astro:page-load', initializeHeader);
269
337
  </script>
270
338
 
@@ -3,6 +3,6 @@
3
3
  * See /packages/core/eventcatalog/src/enterprise/LICENSE
4
4
  */
5
5
 
6
- import { AstroAuth } from 'auth-astro/server';
6
+ import { AstroAuth } from '@utils/auth-astro/server';
7
7
  export const prerender = false;
8
8
  export const { GET, POST } = AstroAuth();
@@ -5,7 +5,7 @@ const { title, logo } = config;
5
5
  import { join } from 'node:path';
6
6
  import { isAuthEnabled, isSSR } from '@utils/feature';
7
7
  import { buildUrl } from '@utils/url-builder';
8
- import { getSession } from 'auth-astro/server';
8
+ import { getSession } from '@utils/auth-astro/server';
9
9
 
10
10
  const catalogDirectory = process.env.PROJECT_DIR || process.cwd();
11
11
 
@@ -408,7 +408,7 @@ const providerConfig = {
408
408
  </html>
409
409
 
410
410
  <script>
411
- import { signIn } from 'auth-astro/client';
411
+ import { signIn } from '@utils/auth-astro/client';
412
412
 
413
413
  // Add event listeners to all provider login buttons
414
414
  const providerButtons = document.querySelectorAll('.provider-login-btn');
@@ -5,7 +5,7 @@
5
5
 
6
6
  // src/middleware/auth.ts
7
7
  import type { MiddlewareHandler } from 'astro';
8
- import { getSession } from 'auth-astro/server';
8
+ import { getSession } from '@utils/auth-astro/server';
9
9
  import { isAuthEnabled, isEventCatalogMCPAuthEnabled } from '@utils/feature';
10
10
  import jwt from 'jsonwebtoken';
11
11
  import { isLLMSTxtEnabled } from '@utils/feature';
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Adapted from auth-astro 4.2.0 (MIT, https://github.com/nowaythatworked/auth-astro).
3
+ * See ./config.ts for why this lives in Core.
4
+ */
5
+
6
+ interface AstroSignInOptions extends Record<string, unknown> {
7
+ /** Where to send the user after signing in. Defaults to the current page. */
8
+ callbackUrl?: string;
9
+ /** Redirect after signing in. Defaults to `true`. */
10
+ redirect?: boolean;
11
+ /** Base path for authentication. Defaults to `/api/auth`. */
12
+ prefix?: string;
13
+ }
14
+
15
+ interface AstroSignOutParams {
16
+ /** Where to send the user after signing out. Defaults to the current page. */
17
+ callbackUrl?: string;
18
+ /** Base path for authentication. Defaults to `/api/auth`. */
19
+ prefix?: string;
20
+ }
21
+
22
+ /**
23
+ * Starts a sign-in flow for a provider. Adds the CSRF token to the request.
24
+ */
25
+ export async function signIn(providerId?: string, options?: AstroSignInOptions, authorizationParams?: Record<string, string>) {
26
+ const { callbackUrl = window.location.href, redirect = true } = options ?? {};
27
+ const { prefix = '/api/auth', ...opts } = options ?? {};
28
+
29
+ const isCredentials = providerId === 'credentials';
30
+ const isEmail = providerId === 'email';
31
+ const isSupportingReturn = isCredentials || isEmail;
32
+
33
+ const signInUrl = `${prefix}/${isCredentials ? 'callback' : 'signin'}/${providerId}`;
34
+ const _signInUrl = `${signInUrl}?${new URLSearchParams(authorizationParams)}`;
35
+
36
+ const csrfTokenResponse = await fetch(`${prefix}/csrf`);
37
+ const { csrfToken } = await csrfTokenResponse.json();
38
+
39
+ const res = await fetch(_signInUrl, {
40
+ method: 'post',
41
+ headers: {
42
+ 'Content-Type': 'application/x-www-form-urlencoded',
43
+ 'X-Auth-Return-Redirect': '1',
44
+ },
45
+ body: new URLSearchParams({
46
+ ...(opts as Record<string, string>),
47
+ csrfToken,
48
+ callbackUrl,
49
+ }),
50
+ });
51
+
52
+ const data = await res.clone().json();
53
+ const error = new URL(data.url).searchParams.get('error');
54
+
55
+ if (redirect || !isSupportingReturn || !error) {
56
+ window.location.href = data.url ?? callbackUrl;
57
+ // The browser does not reload when only the hash changes
58
+ if (data.url.includes('#')) window.location.reload();
59
+ return;
60
+ }
61
+
62
+ return res;
63
+ }
64
+
65
+ /**
66
+ * Signs the user out by removing the session cookie. Adds the CSRF token to the request.
67
+ */
68
+ export async function signOut(options?: AstroSignOutParams) {
69
+ const { callbackUrl = window.location.href, prefix = '/api/auth' } = options ?? {};
70
+ const csrfTokenResponse = await fetch(`${prefix}/csrf`);
71
+ const { csrfToken } = await csrfTokenResponse.json();
72
+ const res = await fetch(`${prefix}/signout`, {
73
+ method: 'post',
74
+ headers: {
75
+ 'Content-Type': 'application/x-www-form-urlencoded',
76
+ 'X-Auth-Return-Redirect': '1',
77
+ },
78
+ body: new URLSearchParams({
79
+ csrfToken,
80
+ callbackUrl,
81
+ }),
82
+ });
83
+ const data = await res.json();
84
+
85
+ const url = data.url ?? callbackUrl;
86
+ window.location.href = url;
87
+ // The browser does not reload when only the hash changes
88
+ if (url.includes('#')) window.location.reload();
89
+ }