@eventcatalog/core 5.0.0-beta.7 → 5.0.0-beta.9

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 (95) hide show
  1. package/LICENSE +227 -128
  2. package/dist/analytics/analytics.cjs +18 -3
  3. package/dist/analytics/analytics.d.cts +1 -1
  4. package/dist/analytics/analytics.d.ts +1 -1
  5. package/dist/analytics/analytics.js +3 -2
  6. package/dist/analytics/log-build.cjs +70 -34
  7. package/dist/analytics/log-build.js +5 -4
  8. package/dist/analytics/telemetry.cjs +42 -0
  9. package/dist/analytics/telemetry.d.cts +20 -0
  10. package/dist/analytics/telemetry.d.ts +20 -0
  11. package/dist/analytics/telemetry.js +8 -0
  12. package/dist/{chunk-3VBB2ZMP.js → chunk-4CFZUHGG.js} +9 -3
  13. package/dist/{chunk-PIYJM642.js → chunk-AWOO63ME.js} +70 -44
  14. package/dist/chunk-CPW6G62V.js +17 -0
  15. package/dist/{chunk-MDWB5HLO.js → chunk-ENAG6UH7.js} +5 -0
  16. package/dist/{chunk-FARBIZ7Y.js → chunk-EUNQJO3J.js} +1 -1
  17. package/dist/{chunk-S7ODHVII.js → chunk-GAHKCOQM.js} +1 -1
  18. package/dist/{chunk-BOKAG5XC.js → chunk-JQMYHZWI.js} +58 -35
  19. package/dist/{chunk-SJJKWZYZ.js → chunk-TZVDMWQX.js} +1 -1
  20. package/dist/constants.cjs +1 -1
  21. package/dist/constants.js +1 -1
  22. package/dist/eventcatalog.cjs +207 -123
  23. package/dist/eventcatalog.config.d.cts +55 -0
  24. package/dist/eventcatalog.config.d.ts +55 -0
  25. package/dist/eventcatalog.js +39 -19
  26. package/dist/features.cjs +8 -2
  27. package/dist/features.d.cts +3 -1
  28. package/dist/features.d.ts +3 -1
  29. package/dist/features.js +5 -3
  30. package/dist/federation/federate.js +4 -4
  31. package/dist/generate.cjs +1 -1
  32. package/dist/generate.js +3 -3
  33. package/dist/utils/cli-logger.cjs +1 -1
  34. package/dist/utils/cli-logger.js +2 -2
  35. package/dist/utils/license-status.cjs +70 -44
  36. package/dist/utils/license-status.d.cts +14 -5
  37. package/dist/utils/license-status.d.ts +14 -5
  38. package/dist/utils/license-status.js +1 -1
  39. package/eventcatalog/integrations/studio-server.mjs +15 -3
  40. package/eventcatalog/src/components/Header.astro +3 -3
  41. package/eventcatalog/src/features/integrations/eventcatalog-features.ts +10 -2
  42. package/eventcatalog/src/features/mcp/apps/canvas/shared.ts +9 -1
  43. package/eventcatalog/src/features/mcp/apps/canvas/view.tsx +9 -2
  44. package/eventcatalog/src/features/mcp/apps/generated/canvas.html +70 -69
  45. package/eventcatalog/src/features/mcp/apps/generated/viewer.html +69 -69
  46. package/eventcatalog/src/features/mcp/mcp-server.ts +5 -3
  47. package/eventcatalog/src/features/studio/agent-choreography.ts +8 -5
  48. package/eventcatalog/src/features/studio/canvas-actions.ts +148 -21
  49. package/eventcatalog/src/features/studio/canvas-doc.ts +138 -9
  50. package/eventcatalog/src/features/studio/canvas-preview.ts +53 -0
  51. package/eventcatalog/src/features/studio/catalog-resources.ts +5 -2
  52. package/eventcatalog/src/features/studio/clipboard.ts +97 -0
  53. package/eventcatalog/src/features/studio/components/CanvasControls.tsx +4 -3
  54. package/eventcatalog/src/features/studio/components/CanvasHeader.tsx +39 -22
  55. package/eventcatalog/src/features/studio/components/CanvasStatusMenu.tsx +117 -0
  56. package/eventcatalog/src/features/studio/components/Comments.tsx +345 -30
  57. package/eventcatalog/src/features/studio/components/ConnectAgentDialog.tsx +140 -76
  58. package/eventcatalog/src/features/studio/components/ConnectionsChooser.tsx +144 -0
  59. package/eventcatalog/src/features/studio/components/ContainerChooser.tsx +1 -1
  60. package/eventcatalog/src/features/studio/components/DragGhost.tsx +115 -0
  61. package/eventcatalog/src/features/studio/components/JoinForm.tsx +0 -24
  62. package/eventcatalog/src/features/studio/components/LeftPanel.tsx +131 -35
  63. package/eventcatalog/src/features/studio/components/Picture.tsx +21 -0
  64. package/eventcatalog/src/features/studio/components/Presence.tsx +2 -0
  65. package/eventcatalog/src/features/studio/components/PropertiesPanel.tsx +187 -50
  66. package/eventcatalog/src/features/studio/components/ShareDialog.tsx +260 -0
  67. package/eventcatalog/src/features/studio/components/StudioDesigner.tsx +442 -40
  68. package/eventcatalog/src/features/studio/components/canvas-nodes.tsx +198 -22
  69. package/eventcatalog/src/features/studio/components/icons.ts +6 -1
  70. package/eventcatalog/src/features/studio/components/status.ts +30 -1
  71. package/eventcatalog/src/features/studio/edges.ts +2 -0
  72. package/eventcatalog/src/features/studio/grouping.ts +95 -2
  73. package/eventcatalog/src/features/studio/hooks/presence-store.ts +5 -1
  74. package/eventcatalog/src/features/studio/hooks/use-canvas-webmcp.ts +13 -3
  75. package/eventcatalog/src/features/studio/hooks/use-comments.ts +8 -1
  76. package/eventcatalog/src/features/studio/hooks/use-follow-camera.ts +7 -2
  77. package/eventcatalog/src/features/studio/hooks/use-studio-flow.ts +174 -27
  78. package/eventcatalog/src/features/studio/identity.ts +35 -0
  79. package/eventcatalog/src/features/studio/layout.ts +7 -4
  80. package/eventcatalog/src/features/studio/levels.ts +138 -72
  81. package/eventcatalog/src/features/studio/node-types.ts +35 -3
  82. package/eventcatalog/src/features/studio/pages/[id].astro +7 -2
  83. package/eventcatalog/src/features/studio/pages/index.astro +555 -2
  84. package/eventcatalog/src/features/studio/pages/new.astro +24 -0
  85. package/eventcatalog/src/features/studio/pages/wake.ts +15 -0
  86. package/eventcatalog/src/features/studio/server/canvas-mcp.ts +48 -13
  87. package/eventcatalog/src/features/studio/server/runtime.ts +193 -14
  88. package/eventcatalog/src/features/studio/server/sign-in.ts +18 -0
  89. package/eventcatalog/src/features/studio/server/storage.ts +110 -0
  90. package/eventcatalog/src/features/studio/studio.css +184 -0
  91. package/eventcatalog/src/layouts/VerticalSideBarLayout.astro +1 -1
  92. package/eventcatalog/src/utils/feature.ts +7 -3
  93. package/eventcatalog/src/utils/node-graphs/export-mermaid.ts +0 -2
  94. package/package.json +3 -3
  95. package/dist/{chunk-AYNZXUB5.js → chunk-ZEY2XUJN.js} +7 -7
@@ -197,6 +197,40 @@ type McpConfig = {
197
197
  enabled?: boolean;
198
198
  auth?: McpAuthConfig;
199
199
  };
200
+ /**
201
+ * Where EventCatalog Studio keeps canvases. People and agents always work on the copy held in memory by the
202
+ * server, so storage doesn't slow editing down: it's read when the server starts and written a moment after a
203
+ * canvas changes (and as the server stops).
204
+ */
205
+ type StudioStorageConfig =
206
+ /** Kept only while the server runs: canvases are lost when it restarts or redeploys (the default) */
207
+ {
208
+ type: 'memory';
209
+ }
210
+ /** A SQLite database file. Uses Node.js's built-in SQLite, so needs Node.js 22.13 or later. */
211
+ | {
212
+ type: 'sqlite';
213
+ /**
214
+ * The database file, relative to the catalog. In a container, put it on a volume (e.g. `/data/studio.db`)
215
+ * so canvases survive redeploys.
216
+ * @default '.eventcatalog/studio.db'
217
+ */
218
+ path?: string;
219
+ };
220
+ type StudioConfig = {
221
+ /**
222
+ * Turns Studio on in production (`output: 'server'`, served with `eventcatalog start`). It's on in the dev server
223
+ * unless this is `false`, which turns it off everywhere. Experimental. With authentication on, only signed-in people
224
+ * can open canvases, and they're shown by the name and picture they signed in with.
225
+ * @default false
226
+ */
227
+ enabled?: boolean;
228
+ /**
229
+ * Where canvases are kept between restarts.
230
+ * @default { type: 'memory' }
231
+ */
232
+ storage?: StudioStorageConfig;
233
+ };
200
234
  type GA4Config = {
201
235
  measurementId: string;
202
236
  };
@@ -257,6 +291,17 @@ interface Config {
257
291
  };
258
292
  security?: {
259
293
  checkOrigin?: boolean;
294
+ /**
295
+ * Hosts to trust in a proxy's X-Forwarded-Host and X-Forwarded-Proto headers (server output), e.g. when deployed
296
+ * behind Fly.io, Railway or a load balancer that serves HTTPS. Without it, form posts (like starting a canvas in
297
+ * Studio) fail the origin check. Passed to Astro's `security.allowedDomains`.
298
+ * @example [{ hostname: 'catalog.example.com', protocol: 'https' }]
299
+ */
300
+ allowedDomains?: {
301
+ hostname?: string;
302
+ protocol?: string;
303
+ port?: string;
304
+ }[];
260
305
  };
261
306
  /**
262
307
  * Theme for the catalog UI.
@@ -270,6 +315,8 @@ interface Config {
270
315
  theme?: CatalogTheme;
271
316
  auth?: AuthConfig;
272
317
  mcp?: McpConfig;
318
+ /** EventCatalog Studio: collaborative canvases (experimental) */
319
+ studio?: StudioConfig;
273
320
  rss?: {
274
321
  enabled?: boolean;
275
322
  limit?: number;
@@ -410,6 +457,14 @@ interface Config {
410
457
  queries?: {
411
458
  tableConfiguration?: TableConfiguration;
412
459
  };
460
+ /**
461
+ * Anonymous telemetry for `eventcatalog dev` and `eventcatalog build`.
462
+ * On by default. Set to false to opt out.
463
+ * `EVENTCATALOG_TELEMETRY_DISABLED` or `DO_NOT_TRACK` set to `1` or `true` also opts out.
464
+ * `cloud.analytics` stays its own opt-in for EventCatalog Cloud.
465
+ * @default true
466
+ */
467
+ telemetry?: boolean;
413
468
  cloud?: EventCatalogCloudConfig;
414
469
  integrations?: IntegrationsConfig;
415
470
  scalarConfiguration?: ScalarConfiguration;
@@ -1,42 +1,44 @@
1
+ import {
2
+ log_build_default
3
+ } from "./chunk-JQMYHZWI.js";
4
+ import "./chunk-4CFZUHGG.js";
5
+ import "./chunk-ZAZHAVKB.js";
6
+ import "./chunk-CPW6G62V.js";
1
7
  import {
2
8
  runMigrations
3
9
  } from "./chunk-XUAF2H54.js";
4
10
  import "./chunk-CA4U2JP7.js";
5
- import {
6
- log_build_default
7
- } from "./chunk-BOKAG5XC.js";
8
11
  import {
9
12
  getLicenseStatus,
10
13
  printLicenseStatus
11
- } from "./chunk-PIYJM642.js";
12
- import "./chunk-ZAZHAVKB.js";
14
+ } from "./chunk-AWOO63ME.js";
13
15
  import {
14
16
  FederationConflictError,
15
17
  FederationDiagnosticError,
16
18
  federateCatalog
17
- } from "./chunk-AYNZXUB5.js";
18
- import "./chunk-5EPNFDT5.js";
19
- import "./chunk-A2RZR3U4.js";
20
- import "./chunk-R7Z5ALYI.js";
19
+ } from "./chunk-ZEY2XUJN.js";
21
20
  import {
22
21
  getFederationDiagnosticCounts,
23
22
  getVisibleFederationDiagnostics
24
23
  } from "./chunk-JJPB6EOZ.js";
24
+ import "./chunk-WRNH5C3U.js";
25
+ import "./chunk-5EPNFDT5.js";
26
+ import "./chunk-A2RZR3U4.js";
25
27
  import "./chunk-SDZQJTJW.js";
26
28
  import "./chunk-352FGP6W.js";
27
- import "./chunk-WRNH5C3U.js";
28
29
  import {
29
30
  getProjectOutDir,
30
31
  isAuthEnabled,
31
32
  isIndexedSearchEnabled,
32
- isOutputServer
33
- } from "./chunk-MDWB5HLO.js";
33
+ isOutputServer,
34
+ isStudioEnabled
35
+ } from "./chunk-ENAG6UH7.js";
34
36
  import {
35
37
  generate
36
- } from "./chunk-SJJKWZYZ.js";
38
+ } from "./chunk-TZVDMWQX.js";
37
39
  import {
38
40
  logger
39
- } from "./chunk-S7ODHVII.js";
41
+ } from "./chunk-GAHKCOQM.js";
40
42
  import {
41
43
  resolve_catalog_dependencies_default
42
44
  } from "./chunk-36RYTYT5.js";
@@ -50,7 +52,7 @@ import {
50
52
  import {
51
53
  watch
52
54
  } from "./chunk-U63GDPPY.js";
53
- import "./chunk-3VBB2ZMP.js";
55
+ import "./chunk-R7Z5ALYI.js";
54
56
  import {
55
57
  getAstroConfigPath
56
58
  } from "./chunk-VIJJ7JKH.js";
@@ -66,7 +68,7 @@ import {
66
68
  } from "./chunk-LFQJXLHG.js";
67
69
  import {
68
70
  VERSION
69
- } from "./chunk-FARBIZ7Y.js";
71
+ } from "./chunk-EUNQJO3J.js";
70
72
  import {
71
73
  getEventCatalogConfigFile,
72
74
  verifyRequiredFieldsAreInCatalogConfigFile
@@ -79,7 +81,7 @@ import { join } from "path";
79
81
  import http from "http";
80
82
  import fs3 from "fs";
81
83
  import path2 from "path";
82
- import { fileURLToPath } from "url";
84
+ import { fileURLToPath, pathToFileURL } from "url";
83
85
  import { createRequire } from "module";
84
86
  import boxen from "boxen";
85
87
  import updateNotifier from "update-notifier";
@@ -1028,15 +1030,33 @@ var previewCatalog = async ({ command }) => {
1028
1030
  shouldFilterLine: createAstroLineFilter()
1029
1031
  });
1030
1032
  };
1033
+ var SERVER_BOOTSTRAP = `
1034
+ const entry = await import(process.argv[1]);
1035
+ const { server } = entry.startServer();
1036
+ globalThis[Symbol.for('eventcatalog.http-server')] = server.server;
1037
+ const wake = process.env.EVENTCATALOG_STUDIO_WAKE_PATH;
1038
+ if (wake) {
1039
+ server.server.once('listening', () => {
1040
+ // Where the server listens (HOST can be one address only); loopback when it listens on every address
1041
+ const { address, port } = server.server.address();
1042
+ const host = ['0.0.0.0', '::'].includes(address) ? '127.0.0.1' : address.includes(':') ? '[' + address + ']' : address;
1043
+ fetch('http://' + host + ':' + port + wake).catch((error) => console.error('[studio] Could not start Studio:', error.message));
1044
+ });
1045
+ }
1046
+ `;
1031
1047
  var startServerCatalog = async () => {
1032
1048
  const serverEntryPath = path2.join(dir, "dist", "server", "entry.mjs");
1049
+ const config = await getEventCatalogConfigFile(dir);
1050
+ const base = (config?.base || "/").replace(/\/$/, "");
1033
1051
  await runCommandWithFilteredOutput({
1034
1052
  command: process.execPath,
1035
- args: [serverEntryPath],
1053
+ args: ["--input-type=module", "-e", SERVER_BOOTSTRAP, pathToFileURL(serverEntryPath).href],
1036
1054
  cwd: dir,
1037
1055
  env: {
1038
1056
  PROJECT_DIR: dir,
1039
- CATALOG_DIR: core
1057
+ CATALOG_DIR: core,
1058
+ ASTRO_NODE_AUTOSTART: "disabled",
1059
+ ...await isStudioEnabled() && { EVENTCATALOG_STUDIO_WAKE_PATH: `${base}/_eventcatalog/studio/wake` }
1040
1060
  },
1041
1061
  shouldFilterLine: createAstroLineFilter()
1042
1062
  });
package/dist/features.cjs CHANGED
@@ -33,7 +33,8 @@ __export(features_exports, {
33
33
  getProjectOutDir: () => getProjectOutDir,
34
34
  isAuthEnabled: () => isAuthEnabled,
35
35
  isIndexedSearchEnabled: () => isIndexedSearchEnabled,
36
- isOutputServer: () => isOutputServer
36
+ isOutputServer: () => isOutputServer,
37
+ isStudioEnabled: () => isStudioEnabled
37
38
  });
38
39
  module.exports = __toCommonJS(features_exports);
39
40
 
@@ -121,6 +122,10 @@ var isOutputServer = async () => {
121
122
  const config = await getEventCatalogConfigFile(process.env.PROJECT_DIR || "");
122
123
  return config?.output === "server";
123
124
  };
125
+ var isStudioEnabled = async () => {
126
+ const config = await getEventCatalogConfigFile(process.env.PROJECT_DIR || "");
127
+ return config?.output === "server" && config?.studio?.enabled === true;
128
+ };
124
129
  var isIndexedSearchEnabled = async () => {
125
130
  const config = await getEventCatalogConfigFile(process.env.PROJECT_DIR || "");
126
131
  return config?.search?.type === "indexed";
@@ -135,5 +140,6 @@ var isAuthEnabled = async () => {
135
140
  getProjectOutDir,
136
141
  isAuthEnabled,
137
142
  isIndexedSearchEnabled,
138
- isOutputServer
143
+ isOutputServer,
144
+ isStudioEnabled
139
145
  });
@@ -1,6 +1,8 @@
1
1
  declare const getProjectOutDir: () => Promise<any>;
2
2
  declare const isOutputServer: () => Promise<boolean>;
3
+ /** EventCatalog Studio in production (server output, studio.enabled) */
4
+ declare const isStudioEnabled: () => Promise<boolean>;
3
5
  declare const isIndexedSearchEnabled: () => Promise<boolean>;
4
6
  declare const isAuthEnabled: () => Promise<boolean>;
5
7
 
6
- export { getProjectOutDir, isAuthEnabled, isIndexedSearchEnabled, isOutputServer };
8
+ export { getProjectOutDir, isAuthEnabled, isIndexedSearchEnabled, isOutputServer, isStudioEnabled };
@@ -1,6 +1,8 @@
1
1
  declare const getProjectOutDir: () => Promise<any>;
2
2
  declare const isOutputServer: () => Promise<boolean>;
3
+ /** EventCatalog Studio in production (server output, studio.enabled) */
4
+ declare const isStudioEnabled: () => Promise<boolean>;
3
5
  declare const isIndexedSearchEnabled: () => Promise<boolean>;
4
6
  declare const isAuthEnabled: () => Promise<boolean>;
5
7
 
6
- export { getProjectOutDir, isAuthEnabled, isIndexedSearchEnabled, isOutputServer };
8
+ export { getProjectOutDir, isAuthEnabled, isIndexedSearchEnabled, isOutputServer, isStudioEnabled };
package/dist/features.js CHANGED
@@ -2,12 +2,14 @@ import {
2
2
  getProjectOutDir,
3
3
  isAuthEnabled,
4
4
  isIndexedSearchEnabled,
5
- isOutputServer
6
- } from "./chunk-MDWB5HLO.js";
5
+ isOutputServer,
6
+ isStudioEnabled
7
+ } from "./chunk-ENAG6UH7.js";
7
8
  import "./chunk-RFIXKATC.js";
8
9
  export {
9
10
  getProjectOutDir,
10
11
  isAuthEnabled,
11
12
  isIndexedSearchEnabled,
12
- isOutputServer
13
+ isOutputServer,
14
+ isStudioEnabled
13
15
  };
@@ -2,14 +2,14 @@ import {
2
2
  FederationConflictError,
3
3
  FederationDiagnosticError,
4
4
  federateCatalog
5
- } from "../chunk-AYNZXUB5.js";
5
+ } from "../chunk-ZEY2XUJN.js";
6
+ import "../chunk-JJPB6EOZ.js";
7
+ import "../chunk-WRNH5C3U.js";
6
8
  import "../chunk-5EPNFDT5.js";
7
9
  import "../chunk-A2RZR3U4.js";
8
- import "../chunk-R7Z5ALYI.js";
9
- import "../chunk-JJPB6EOZ.js";
10
10
  import "../chunk-SDZQJTJW.js";
11
11
  import "../chunk-352FGP6W.js";
12
- import "../chunk-WRNH5C3U.js";
12
+ import "../chunk-R7Z5ALYI.js";
13
13
  import "../chunk-RFIXKATC.js";
14
14
  export {
15
15
  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 = "5.0.0-beta.7";
115
+ var version = "5.0.0-beta.9";
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-SJJKWZYZ.js";
4
- import "./chunk-S7ODHVII.js";
5
- import "./chunk-FARBIZ7Y.js";
3
+ } from "./chunk-TZVDMWQX.js";
4
+ import "./chunk-GAHKCOQM.js";
5
+ import "./chunk-EUNQJO3J.js";
6
6
  import "./chunk-RFIXKATC.js";
7
7
  export {
8
8
  generate
@@ -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 = "5.0.0-beta.7";
39
+ var version = "5.0.0-beta.9";
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-S7ODHVII.js";
4
- import "../chunk-FARBIZ7Y.js";
3
+ } from "../chunk-GAHKCOQM.js";
4
+ import "../chunk-EUNQJO3J.js";
5
5
  export {
6
6
  logger
7
7
  };
@@ -37,6 +37,7 @@ __export(license_status_exports, {
37
37
  });
38
38
  module.exports = __toCommonJS(license_status_exports);
39
39
  var import_boxen = __toESM(require("boxen"), 1);
40
+ var import_picocolors = __toESM(require("picocolors"), 1);
40
41
  var import_license2 = require("@eventcatalog/license");
41
42
 
42
43
  // eventcatalog/src/utils/trial.ts
@@ -159,75 +160,100 @@ var getLicenseAnalytics = (license = { state: "none" }, tsd) => ({
159
160
  });
160
161
  var formatDate = (date) => date.toLocaleDateString(void 0, { dateStyle: "medium" });
161
162
  var formatDays = (days) => `${days} ${days === 1 ? "day" : "days"}`;
162
- var getTrialLine = (tsd, now) => {
163
+ var PLAIN = { bold: (text) => text, dim: (text) => text, link: (text) => text };
164
+ var COLOURS = { bold: import_picocolors.default.bold, dim: import_picocolors.default.dim, link: (text) => import_picocolors.default.cyan(text) };
165
+ var render = ({ headline, rows, links }, colours) => {
166
+ const width = Math.max(0, ...[...rows, ...links].map(([label]) => label.length));
167
+ const row = ([label, value]) => `${colours.dim(label.padEnd(width))} ${value}`;
168
+ return [
169
+ colours.bold(headline),
170
+ ...rows.length ? ["", ...rows.map(row)] : [],
171
+ ...links.length ? ["", ...links.map(([label, url]) => row([label, colours.link(url)]))] : []
172
+ ].join("\n");
173
+ };
174
+ var message = (content) => ({
175
+ ...content,
176
+ text: render(content, PLAIN)
177
+ });
178
+ var getTrialRow = (tsd, now) => {
163
179
  const trial = getTrialStatus(tsd, now);
164
180
  if (!trial) return;
165
- return trial.ended ? `Your ${TRIAL_LENGTH_DAYS}-day EventCatalog trial ended on ${formatDate(trial.endsAt)}.` : `${formatDays(trial.daysLeft)} left of your ${TRIAL_LENGTH_DAYS}-day EventCatalog trial (ends ${formatDate(trial.endsAt)}).`;
181
+ return [
182
+ "Trial",
183
+ trial.ended ? `Ended ${formatDate(trial.endsAt)}` : `${formatDays(trial.daysLeft)} left of ${TRIAL_LENGTH_DAYS} (ends ${formatDate(trial.endsAt)})`
184
+ ];
166
185
  };
167
186
  var getLicenseStatusMessage = (license, tsd, now = Date.now()) => {
187
+ const licensing = ["Licensing", LICENSE_FAQ_URL];
188
+ const renew = ["Renew", LICENSE_RENEW_URL];
189
+ const licensedTo = "org" in license && license.org ? [["Licensed to", license.org]] : [];
168
190
  if (license.state === "licensed") {
169
191
  const daysLeft = Math.max(0, Math.ceil((license.expiresAt.getTime() - now) / DAY_IN_MS2));
170
- const licensedTo = license.org ? `Licensed to ${license.org}` : "Licensed";
171
192
  if (daysLeft < LICENSE_REMINDER_DAYS) {
172
- return {
173
- title: "EventCatalog Commercial License",
193
+ return message({
194
+ title: "EventCatalog license",
174
195
  color: "yellow",
175
- text: [
176
- licensedTo,
177
- `Your license expires in ${formatDays(daysLeft)}, on ${formatDate(license.expiresAt)}.`,
178
- `Renew it to keep using EventCatalog commercially: ${LICENSE_RENEW_URL}`
179
- ].join("\n")
180
- };
196
+ headline: `Your commercial license expires in ${formatDays(daysLeft)}`,
197
+ rows: [...licensedTo, ["Expires", formatDate(license.expiresAt)]],
198
+ links: [renew]
199
+ });
181
200
  }
182
- return {
183
- title: "EventCatalog Commercial License",
201
+ return message({
202
+ title: "EventCatalog license",
184
203
  color: "green",
185
- text: `${licensedTo}
186
- Valid until ${formatDate(license.expiresAt)} (${formatDays(daysLeft)} left)`
187
- };
204
+ headline: "Commercial license active",
205
+ rows: [...licensedTo, ["Valid until", `${formatDate(license.expiresAt)} (${formatDays(daysLeft)} left)`]],
206
+ links: []
207
+ });
188
208
  }
189
209
  if (license.state === "plan") return;
190
- const trialLine = getTrialLine(tsd, now);
210
+ const trialRow = getTrialRow(tsd, now);
211
+ const trialRows = trialRow ? [trialRow] : [];
191
212
  if (license.state === "none") {
192
- if (!trialLine) return;
193
- const ended = getTrialStatus(tsd, now)?.ended;
194
- return {
213
+ const trial = getTrialStatus(tsd, now);
214
+ if (!trial || !trialRow) return;
215
+ return message({
195
216
  title: "EventCatalog trial",
196
- color: ended ? "yellow" : "green",
197
- text: `${trialLine}
198
- Learn about licensing: ${LICENSE_FAQ_URL}`
199
- };
217
+ color: trial.ended ? "yellow" : "green",
218
+ headline: trial.ended ? "Your EventCatalog trial has ended" : `You're on the ${TRIAL_LENGTH_DAYS}-day EventCatalog trial`,
219
+ rows: trialRows,
220
+ links: trial.ended ? [["Get a license", LICENSE_RENEW_URL], licensing] : [licensing]
221
+ });
200
222
  }
201
223
  if (license.state === "expired") {
202
- const expiredOn = license.expiresAt ? `expired on ${formatDate(license.expiresAt)} (${formatDays(Math.floor((now - license.expiresAt.getTime()) / DAY_IN_MS2))} ago)` : "has expired";
203
- return {
204
- title: "EventCatalog License Expired",
224
+ const expiredOn = license.expiresAt ? [
225
+ [
226
+ "Expired",
227
+ `${formatDate(license.expiresAt)} (${formatDays(Math.floor((now - license.expiresAt.getTime()) / DAY_IN_MS2))} ago)`
228
+ ]
229
+ ] : [];
230
+ return message({
231
+ title: "EventCatalog license",
205
232
  color: "yellow",
206
- text: [
207
- `Your EventCatalog commercial license${license.org ? ` for ${license.org}` : ""} ${expiredOn}.`,
208
- `Renew it to keep using EventCatalog commercially: ${LICENSE_RENEW_URL}`,
209
- trialLine,
210
- `Learn about licensing: ${LICENSE_FAQ_URL}`
211
- ].filter(Boolean).join("\n")
212
- };
233
+ headline: "Your commercial license has expired",
234
+ rows: [...licensedTo, ...expiredOn, ...trialRows],
235
+ links: [renew, licensing]
236
+ });
213
237
  }
214
- return {
238
+ return message({
215
239
  title: "EventCatalog license",
216
240
  color: "yellow",
217
- text: [`Your license.jwt could not be verified: ${license.reason}.`, trialLine, `Learn about licensing: ${LICENSE_FAQ_URL}`].filter(Boolean).join("\n")
218
- };
241
+ headline: "Your license.jwt couldn't be verified",
242
+ rows: [["Reason", license.reason.charAt(0).toUpperCase() + license.reason.slice(1)], ...trialRows],
243
+ links: [licensing]
244
+ });
219
245
  };
220
246
  var printLicenseStatus = (license, tsd) => {
221
- const message = getLicenseStatusMessage(license, tsd);
222
- if (!message) return;
247
+ const status = getLicenseStatusMessage(license, tsd);
248
+ if (!status) return;
223
249
  console.log(
224
- (0, import_boxen.default)(message.text, {
225
- padding: 1,
250
+ (0, import_boxen.default)(render(status, COLOURS), {
251
+ padding: { top: 1, bottom: 1, left: 2, right: 2 },
226
252
  margin: 1,
227
253
  borderStyle: "round",
228
- borderColor: message.color,
229
- title: message.title,
230
- titleAlignment: "center"
254
+ borderColor: status.color,
255
+ title: status.title,
256
+ titleAlignment: "left"
231
257
  })
232
258
  );
233
259
  };
@@ -15,14 +15,23 @@ declare const getLicenseAnalytics: (license?: LicenseStatus, tsd?: unknown) => {
15
15
  licenseExpiry: number | undefined;
16
16
  trialExpiry: number | undefined;
17
17
  };
18
+ type Row = [label: string, value: string];
18
19
  /**
19
- * The license (or trial) box for the terminal. Undefined when there's nothing to show.
20
+ * What the license box says: a headline, the facts as aligned rows ("Licensed to acme"), and links on their own
21
+ * rows, so it can be read at a glance. `text` is the box's content without colours.
20
22
  */
21
- declare const getLicenseStatusMessage: (license: LicenseStatus, tsd: unknown, now?: number) => {
23
+ type LicenseStatusMessage = {
22
24
  title: string;
23
- color: string;
25
+ color: 'green' | 'yellow';
26
+ headline: string;
27
+ rows: Row[];
28
+ links: Row[];
24
29
  text: string;
25
- } | undefined;
30
+ };
31
+ /**
32
+ * The license (or trial) box for the terminal. Undefined when there's nothing to show.
33
+ */
34
+ declare const getLicenseStatusMessage: (license: LicenseStatus, tsd: unknown, now?: number) => LicenseStatusMessage | undefined;
26
35
  declare const printLicenseStatus: (license: LicenseStatus, tsd: unknown) => void;
27
36
 
28
- export { LicenseStatus, getLicenseAnalytics, getLicenseStatus, getLicenseStatusMessage, printLicenseStatus };
37
+ export { LicenseStatus, type LicenseStatusMessage, getLicenseAnalytics, getLicenseStatus, getLicenseStatusMessage, printLicenseStatus };
@@ -15,14 +15,23 @@ declare const getLicenseAnalytics: (license?: LicenseStatus, tsd?: unknown) => {
15
15
  licenseExpiry: number | undefined;
16
16
  trialExpiry: number | undefined;
17
17
  };
18
+ type Row = [label: string, value: string];
18
19
  /**
19
- * The license (or trial) box for the terminal. Undefined when there's nothing to show.
20
+ * What the license box says: a headline, the facts as aligned rows ("Licensed to acme"), and links on their own
21
+ * rows, so it can be read at a glance. `text` is the box's content without colours.
20
22
  */
21
- declare const getLicenseStatusMessage: (license: LicenseStatus, tsd: unknown, now?: number) => {
23
+ type LicenseStatusMessage = {
22
24
  title: string;
23
- color: string;
25
+ color: 'green' | 'yellow';
26
+ headline: string;
27
+ rows: Row[];
28
+ links: Row[];
24
29
  text: string;
25
- } | undefined;
30
+ };
31
+ /**
32
+ * The license (or trial) box for the terminal. Undefined when there's nothing to show.
33
+ */
34
+ declare const getLicenseStatusMessage: (license: LicenseStatus, tsd: unknown, now?: number) => LicenseStatusMessage | undefined;
26
35
  declare const printLicenseStatus: (license: LicenseStatus, tsd: unknown) => void;
27
36
 
28
- export { LicenseStatus, getLicenseAnalytics, getLicenseStatus, getLicenseStatusMessage, printLicenseStatus };
37
+ export { LicenseStatus, type LicenseStatusMessage, getLicenseAnalytics, getLicenseStatus, getLicenseStatusMessage, printLicenseStatus };
@@ -3,7 +3,7 @@ import {
3
3
  getLicenseStatus,
4
4
  getLicenseStatusMessage,
5
5
  printLicenseStatus
6
- } from "../chunk-PIYJM642.js";
6
+ } from "../chunk-AWOO63ME.js";
7
7
  export {
8
8
  getLicenseAnalytics,
9
9
  getLicenseStatus,
@@ -1,4 +1,5 @@
1
1
  import { getStudioRuntime } from '../src/features/studio/server/runtime.ts';
2
+ import config from '../src/utils/eventcatalog-config/source.ts';
2
3
  import { isCanvasEnabled } from '../src/utils/feature.ts';
3
4
 
4
5
  // Collaborative canvases: the collaboration WebSocket (Yjs over Hocuspocus), on the dev server's port.
@@ -7,10 +8,21 @@ export default function studioServer() {
7
8
  return {
8
9
  name: 'eventcatalog-studio-server',
9
10
  hooks: {
10
- 'astro:server:setup': ({ server, logger }) => {
11
+ 'astro:server:setup': async ({ server, logger }) => {
11
12
  if (!isCanvasEnabled() || !server.httpServer) return;
12
- getStudioRuntime().attach(server.httpServer);
13
- logger.info('Collaboration server ready at /_eventcatalog/studio');
13
+ const runtime = getStudioRuntime();
14
+ const projectDirectory = process.env.PROJECT_DIR || process.cwd();
15
+ // Canvases are loaded from storage before anyone can open one
16
+ try {
17
+ await runtime.useStorage(config.studio?.storage, projectDirectory);
18
+ } catch (error) {
19
+ logger.error(`Could not use the Studio storage configured, keeping canvases in memory: ${error.message}`);
20
+ await runtime.useStorage({ type: 'memory' }, projectDirectory);
21
+ }
22
+ runtime.attach(server.httpServer);
23
+ const { type, location } = runtime.storage;
24
+ const kept = type === 'memory' ? 'kept in memory until the server restarts' : `stored in ${location}`;
25
+ logger.info(`Collaboration server ready at /_eventcatalog/studio (canvases ${kept})`);
14
26
  },
15
27
  },
16
28
  };
@@ -94,16 +94,16 @@ const providerName = user?.provider ? providerNames[user.provider] || user.provi
94
94
  <img
95
95
  src={userImage}
96
96
  alt=""
97
- class="h-8 w-8 rounded-full object-cover ring-1 ring-[rgb(var(--ec-page-border))]"
97
+ class="h-7 w-7 rounded-full object-cover ring-1 ring-[rgb(var(--ec-page-border))]"
98
98
  />
99
99
  ) : (
100
- <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">
100
+ <span class="h-7 w-7 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-[11px] font-semibold">
101
101
  {userInitials}
102
102
  </span>
103
103
  )}
104
104
  <svg
105
105
  viewBox="0 0 24 24"
106
- class="h-4 w-4 transition-transform group-aria-expanded:rotate-180"
106
+ class="h-3.5 w-3.5 transition-transform group-aria-expanded:rotate-180"
107
107
  fill="none"
108
108
  stroke="currentColor"
109
109
  stroke-width="2"
@@ -176,17 +176,25 @@ export default function eventCatalogIntegration(): AstroIntegration {
176
176
  });
177
177
  }
178
178
 
179
- // Collaborative canvases in EventCatalog Studio (/studio starts one). Their WebSocket server is in
180
- // integrations/studio-server.mjs.
179
+ // Collaborative canvases in EventCatalog Studio (/studio lists them, /studio/new starts one). Their WebSocket
180
+ // server is in integrations/studio-server.mjs.
181
181
  if (isCanvasEnabled()) {
182
182
  params.injectRoute({
183
183
  pattern: '/studio',
184
184
  entrypoint: path.join(packageDirectory, 'src/features/studio/pages/index.astro'),
185
185
  });
186
+ params.injectRoute({
187
+ pattern: '/studio/new',
188
+ entrypoint: path.join(packageDirectory, 'src/features/studio/pages/new.astro'),
189
+ });
186
190
  params.injectRoute({
187
191
  pattern: '/studio/[id]',
188
192
  entrypoint: path.join(packageDirectory, 'src/features/studio/pages/[id].astro'),
189
193
  });
194
+ params.injectRoute({
195
+ pattern: '/_eventcatalog/studio/wake',
196
+ entrypoint: path.join(packageDirectory, 'src/features/studio/pages/wake.ts'),
197
+ });
190
198
  params.injectRoute({
191
199
  pattern: '/webmcp-relay/[file]',
192
200
  entrypoint: path.join(packageDirectory, 'src/features/studio/pages/webmcp-relay.ts'),