@eventcatalog/core 5.0.0-beta.6 → 5.0.0-beta.8

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 (85) 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 +3 -1
  4. package/dist/analytics/log-build.js +4 -4
  5. package/dist/{chunk-XMPFHLK4.js → chunk-4W55ZOOS.js} +2 -2
  6. package/dist/{chunk-PIYJM642.js → chunk-AWOO63ME.js} +70 -44
  7. package/dist/{chunk-QBP3BBSV.js → chunk-C6T2AAFR.js} +1 -1
  8. package/dist/{chunk-MDWB5HLO.js → chunk-ENAG6UH7.js} +5 -0
  9. package/dist/{chunk-56CD2NH6.js → chunk-PU25TOOJ.js} +1 -1
  10. package/dist/{chunk-L2IRFOE7.js → chunk-RDIZJ7UT.js} +1 -1
  11. package/dist/{chunk-DE3QHVJP.js → chunk-T4G3VVZ4.js} +1 -1
  12. package/dist/constants.cjs +1 -1
  13. package/dist/constants.js +1 -1
  14. package/dist/eventcatalog.cjs +138 -90
  15. package/dist/eventcatalog.config.d.cts +47 -0
  16. package/dist/eventcatalog.config.d.ts +47 -0
  17. package/dist/eventcatalog.js +30 -11
  18. package/dist/features.cjs +8 -2
  19. package/dist/features.d.cts +3 -1
  20. package/dist/features.d.ts +3 -1
  21. package/dist/features.js +5 -3
  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/dist/utils/license-status.cjs +70 -44
  27. package/dist/utils/license-status.d.cts +14 -5
  28. package/dist/utils/license-status.d.ts +14 -5
  29. package/dist/utils/license-status.js +1 -1
  30. package/eventcatalog/integrations/studio-server.mjs +15 -3
  31. package/eventcatalog/src/components/Header.astro +3 -3
  32. package/eventcatalog/src/features/integrations/eventcatalog-features.ts +10 -2
  33. package/eventcatalog/src/features/mcp/apps/canvas/shared.ts +9 -1
  34. package/eventcatalog/src/features/mcp/apps/canvas/view.tsx +9 -2
  35. package/eventcatalog/src/features/mcp/apps/generated/canvas.html +70 -69
  36. package/eventcatalog/src/features/mcp/apps/generated/viewer.html +69 -69
  37. package/eventcatalog/src/features/mcp/mcp-server.ts +5 -3
  38. package/eventcatalog/src/features/studio/agent-choreography.ts +8 -5
  39. package/eventcatalog/src/features/studio/canvas-actions.ts +148 -21
  40. package/eventcatalog/src/features/studio/canvas-doc.ts +138 -9
  41. package/eventcatalog/src/features/studio/canvas-preview.ts +53 -0
  42. package/eventcatalog/src/features/studio/catalog-resources.ts +5 -2
  43. package/eventcatalog/src/features/studio/clipboard.ts +97 -0
  44. package/eventcatalog/src/features/studio/components/CanvasControls.tsx +4 -3
  45. package/eventcatalog/src/features/studio/components/CanvasHeader.tsx +39 -22
  46. package/eventcatalog/src/features/studio/components/CanvasStatusMenu.tsx +117 -0
  47. package/eventcatalog/src/features/studio/components/Comments.tsx +345 -30
  48. package/eventcatalog/src/features/studio/components/ConnectAgentDialog.tsx +140 -76
  49. package/eventcatalog/src/features/studio/components/ConnectionsChooser.tsx +144 -0
  50. package/eventcatalog/src/features/studio/components/ContainerChooser.tsx +1 -1
  51. package/eventcatalog/src/features/studio/components/DragGhost.tsx +115 -0
  52. package/eventcatalog/src/features/studio/components/JoinForm.tsx +0 -24
  53. package/eventcatalog/src/features/studio/components/LeftPanel.tsx +131 -35
  54. package/eventcatalog/src/features/studio/components/Picture.tsx +21 -0
  55. package/eventcatalog/src/features/studio/components/Presence.tsx +2 -0
  56. package/eventcatalog/src/features/studio/components/PropertiesPanel.tsx +187 -50
  57. package/eventcatalog/src/features/studio/components/ShareDialog.tsx +260 -0
  58. package/eventcatalog/src/features/studio/components/StudioDesigner.tsx +442 -40
  59. package/eventcatalog/src/features/studio/components/canvas-nodes.tsx +198 -22
  60. package/eventcatalog/src/features/studio/components/icons.ts +6 -1
  61. package/eventcatalog/src/features/studio/components/status.ts +30 -1
  62. package/eventcatalog/src/features/studio/edges.ts +2 -0
  63. package/eventcatalog/src/features/studio/grouping.ts +95 -2
  64. package/eventcatalog/src/features/studio/hooks/presence-store.ts +5 -1
  65. package/eventcatalog/src/features/studio/hooks/use-canvas-webmcp.ts +13 -3
  66. package/eventcatalog/src/features/studio/hooks/use-comments.ts +8 -1
  67. package/eventcatalog/src/features/studio/hooks/use-follow-camera.ts +7 -2
  68. package/eventcatalog/src/features/studio/hooks/use-studio-flow.ts +174 -27
  69. package/eventcatalog/src/features/studio/identity.ts +35 -0
  70. package/eventcatalog/src/features/studio/layout.ts +7 -4
  71. package/eventcatalog/src/features/studio/levels.ts +138 -72
  72. package/eventcatalog/src/features/studio/node-types.ts +35 -3
  73. package/eventcatalog/src/features/studio/pages/[id].astro +7 -2
  74. package/eventcatalog/src/features/studio/pages/index.astro +555 -2
  75. package/eventcatalog/src/features/studio/pages/new.astro +24 -0
  76. package/eventcatalog/src/features/studio/pages/wake.ts +15 -0
  77. package/eventcatalog/src/features/studio/server/canvas-mcp.ts +48 -13
  78. package/eventcatalog/src/features/studio/server/runtime.ts +193 -14
  79. package/eventcatalog/src/features/studio/server/sign-in.ts +18 -0
  80. package/eventcatalog/src/features/studio/server/storage.ts +110 -0
  81. package/eventcatalog/src/features/studio/studio.css +184 -0
  82. package/eventcatalog/src/layouts/VerticalSideBarLayout.astro +1 -1
  83. package/eventcatalog/src/utils/feature.ts +7 -3
  84. package/eventcatalog/src/utils/node-graphs/export-mermaid.ts +0 -2
  85. package/package.json +4 -4
@@ -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 = "5.0.0-beta.6";
184
+ var version = "5.0.0-beta.8";
185
185
 
186
186
  // src/constants.ts
187
187
  var VERSION = version;
@@ -191,8 +191,8 @@ var getTimestamp = () => {
191
191
  const now = /* @__PURE__ */ new Date();
192
192
  return now.toLocaleTimeString("en-US", { hour12: false });
193
193
  };
194
- var formatMessage = (tag, message, tagColor) => {
195
- return `${import_picocolors.default.dim(getTimestamp())} ${tagColor(`[${tag}]`)} ${message}`;
194
+ var formatMessage = (tag, message2, tagColor) => {
195
+ return `${import_picocolors.default.dim(getTimestamp())} ${tagColor(`[${tag}]`)} ${message2}`;
196
196
  };
197
197
  var logger = {
198
198
  welcome: () => {
@@ -208,28 +208,28 @@ var logger = {
208
208
  );
209
209
  console.log();
210
210
  },
211
- info: (message, tag = "info") => {
212
- console.log(formatMessage(tag, message, import_picocolors.default.blue));
211
+ info: (message2, tag = "info") => {
212
+ console.log(formatMessage(tag, message2, import_picocolors.default.blue));
213
213
  },
214
- success: (message, tag = "success") => {
215
- console.log(formatMessage(tag, message, import_picocolors.default.green));
214
+ success: (message2, tag = "success") => {
215
+ console.log(formatMessage(tag, message2, import_picocolors.default.green));
216
216
  },
217
- error: (message, tag = "error") => {
218
- console.log(formatMessage(tag, message, import_picocolors.default.red));
217
+ error: (message2, tag = "error") => {
218
+ console.log(formatMessage(tag, message2, import_picocolors.default.red));
219
219
  },
220
- warning: (message, tag = "warn") => {
221
- console.log(formatMessage(tag, message, import_picocolors.default.yellow));
220
+ warning: (message2, tag = "warn") => {
221
+ console.log(formatMessage(tag, message2, import_picocolors.default.yellow));
222
222
  },
223
- line: (message = "") => {
224
- console.log(message);
223
+ line: (message2 = "") => {
224
+ console.log(message2);
225
225
  },
226
- diagnostic: (severity, message, rule, attributes = []) => {
226
+ diagnostic: (severity, message2, rule, attributes = []) => {
227
227
  const isError = severity === "error";
228
228
  const color = isError ? import_picocolors.default.red : import_picocolors.default.yellow;
229
229
  const icon = isError ? "\u2716" : "\u26A0";
230
- const label = `${icon} ${severity.padEnd(7)} ${message}`;
230
+ const label = `${icon} ${severity.padEnd(7)} ${message2}`;
231
231
  const ruleSpacing = " ".repeat(Math.max(2, 72 - label.length));
232
- console.log(` ${color(icon)} ${color(severity.padEnd(7))} ${message}${ruleSpacing}${import_picocolors.default.gray(rule)}`);
232
+ console.log(` ${color(icon)} ${color(severity.padEnd(7))} ${message2}${ruleSpacing}${import_picocolors.default.gray(rule)}`);
233
233
  for (const attribute of attributes) {
234
234
  console.log(` - ${import_picocolors.default.dim(`${attribute.label}:`)} ${attribute.value}`);
235
235
  }
@@ -243,8 +243,8 @@ var logger = {
243
243
  const warningCount = `${warnings} warning${warnings === 1 ? "" : "s"}`;
244
244
  console.log(color(`${icon} ${problems} (${errorCount}, ${warningCount})`));
245
245
  },
246
- dim: (message) => {
247
- console.log(import_picocolors.default.dim(message));
246
+ dim: (message2) => {
247
+ console.log(import_picocolors.default.dim(message2));
248
248
  }
249
249
  };
250
250
 
@@ -403,6 +403,7 @@ function serializeCounts(counts) {
403
403
 
404
404
  // src/utils/license-status.ts
405
405
  var import_boxen = __toESM(require("boxen"), 1);
406
+ var import_picocolors2 = __toESM(require("picocolors"), 1);
406
407
  var import_license2 = require("@eventcatalog/license");
407
408
 
408
409
  // eventcatalog/src/utils/trial.ts
@@ -525,75 +526,100 @@ var getLicenseAnalytics = (license = { state: "none" }, tsd) => ({
525
526
  });
526
527
  var formatDate = (date) => date.toLocaleDateString(void 0, { dateStyle: "medium" });
527
528
  var formatDays = (days) => `${days} ${days === 1 ? "day" : "days"}`;
528
- var getTrialLine = (tsd, now) => {
529
+ var PLAIN = { bold: (text) => text, dim: (text) => text, link: (text) => text };
530
+ var COLOURS = { bold: import_picocolors2.default.bold, dim: import_picocolors2.default.dim, link: (text) => import_picocolors2.default.cyan(text) };
531
+ var render = ({ headline, rows, links }, colours) => {
532
+ const width = Math.max(0, ...[...rows, ...links].map(([label]) => label.length));
533
+ const row = ([label, value]) => `${colours.dim(label.padEnd(width))} ${value}`;
534
+ return [
535
+ colours.bold(headline),
536
+ ...rows.length ? ["", ...rows.map(row)] : [],
537
+ ...links.length ? ["", ...links.map(([label, url]) => row([label, colours.link(url)]))] : []
538
+ ].join("\n");
539
+ };
540
+ var message = (content) => ({
541
+ ...content,
542
+ text: render(content, PLAIN)
543
+ });
544
+ var getTrialRow = (tsd, now) => {
529
545
  const trial = getTrialStatus(tsd, now);
530
546
  if (!trial) return;
531
- 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)}).`;
547
+ return [
548
+ "Trial",
549
+ trial.ended ? `Ended ${formatDate(trial.endsAt)}` : `${formatDays(trial.daysLeft)} left of ${TRIAL_LENGTH_DAYS} (ends ${formatDate(trial.endsAt)})`
550
+ ];
532
551
  };
533
552
  var getLicenseStatusMessage = (license, tsd, now = Date.now()) => {
553
+ const licensing = ["Licensing", LICENSE_FAQ_URL];
554
+ const renew = ["Renew", LICENSE_RENEW_URL];
555
+ const licensedTo = "org" in license && license.org ? [["Licensed to", license.org]] : [];
534
556
  if (license.state === "licensed") {
535
557
  const daysLeft = Math.max(0, Math.ceil((license.expiresAt.getTime() - now) / DAY_IN_MS2));
536
- const licensedTo = license.org ? `Licensed to ${license.org}` : "Licensed";
537
558
  if (daysLeft < LICENSE_REMINDER_DAYS) {
538
- return {
539
- title: "EventCatalog Commercial License",
559
+ return message({
560
+ title: "EventCatalog license",
540
561
  color: "yellow",
541
- text: [
542
- licensedTo,
543
- `Your license expires in ${formatDays(daysLeft)}, on ${formatDate(license.expiresAt)}.`,
544
- `Renew it to keep using EventCatalog commercially: ${LICENSE_RENEW_URL}`
545
- ].join("\n")
546
- };
562
+ headline: `Your commercial license expires in ${formatDays(daysLeft)}`,
563
+ rows: [...licensedTo, ["Expires", formatDate(license.expiresAt)]],
564
+ links: [renew]
565
+ });
547
566
  }
548
- return {
549
- title: "EventCatalog Commercial License",
567
+ return message({
568
+ title: "EventCatalog license",
550
569
  color: "green",
551
- text: `${licensedTo}
552
- Valid until ${formatDate(license.expiresAt)} (${formatDays(daysLeft)} left)`
553
- };
570
+ headline: "Commercial license active",
571
+ rows: [...licensedTo, ["Valid until", `${formatDate(license.expiresAt)} (${formatDays(daysLeft)} left)`]],
572
+ links: []
573
+ });
554
574
  }
555
575
  if (license.state === "plan") return;
556
- const trialLine = getTrialLine(tsd, now);
576
+ const trialRow = getTrialRow(tsd, now);
577
+ const trialRows = trialRow ? [trialRow] : [];
557
578
  if (license.state === "none") {
558
- if (!trialLine) return;
559
- const ended = getTrialStatus(tsd, now)?.ended;
560
- return {
579
+ const trial = getTrialStatus(tsd, now);
580
+ if (!trial || !trialRow) return;
581
+ return message({
561
582
  title: "EventCatalog trial",
562
- color: ended ? "yellow" : "green",
563
- text: `${trialLine}
564
- Learn about licensing: ${LICENSE_FAQ_URL}`
565
- };
583
+ color: trial.ended ? "yellow" : "green",
584
+ headline: trial.ended ? "Your EventCatalog trial has ended" : `You're on the ${TRIAL_LENGTH_DAYS}-day EventCatalog trial`,
585
+ rows: trialRows,
586
+ links: trial.ended ? [["Get a license", LICENSE_RENEW_URL], licensing] : [licensing]
587
+ });
566
588
  }
567
589
  if (license.state === "expired") {
568
- const expiredOn = license.expiresAt ? `expired on ${formatDate(license.expiresAt)} (${formatDays(Math.floor((now - license.expiresAt.getTime()) / DAY_IN_MS2))} ago)` : "has expired";
569
- return {
570
- title: "EventCatalog License Expired",
590
+ const expiredOn = license.expiresAt ? [
591
+ [
592
+ "Expired",
593
+ `${formatDate(license.expiresAt)} (${formatDays(Math.floor((now - license.expiresAt.getTime()) / DAY_IN_MS2))} ago)`
594
+ ]
595
+ ] : [];
596
+ return message({
597
+ title: "EventCatalog license",
571
598
  color: "yellow",
572
- text: [
573
- `Your EventCatalog commercial license${license.org ? ` for ${license.org}` : ""} ${expiredOn}.`,
574
- `Renew it to keep using EventCatalog commercially: ${LICENSE_RENEW_URL}`,
575
- trialLine,
576
- `Learn about licensing: ${LICENSE_FAQ_URL}`
577
- ].filter(Boolean).join("\n")
578
- };
599
+ headline: "Your commercial license has expired",
600
+ rows: [...licensedTo, ...expiredOn, ...trialRows],
601
+ links: [renew, licensing]
602
+ });
579
603
  }
580
- return {
604
+ return message({
581
605
  title: "EventCatalog license",
582
606
  color: "yellow",
583
- text: [`Your license.jwt could not be verified: ${license.reason}.`, trialLine, `Learn about licensing: ${LICENSE_FAQ_URL}`].filter(Boolean).join("\n")
584
- };
607
+ headline: "Your license.jwt couldn't be verified",
608
+ rows: [["Reason", license.reason.charAt(0).toUpperCase() + license.reason.slice(1)], ...trialRows],
609
+ links: [licensing]
610
+ });
585
611
  };
586
612
  var printLicenseStatus = (license, tsd) => {
587
- const message = getLicenseStatusMessage(license, tsd);
588
- if (!message) return;
613
+ const status = getLicenseStatusMessage(license, tsd);
614
+ if (!status) return;
589
615
  console.log(
590
- (0, import_boxen.default)(message.text, {
591
- padding: 1,
616
+ (0, import_boxen.default)(render(status, COLOURS), {
617
+ padding: { top: 1, bottom: 1, left: 2, right: 2 },
592
618
  margin: 1,
593
619
  borderStyle: "round",
594
- borderColor: message.color,
595
- title: message.title,
596
- titleAlignment: "center"
620
+ borderColor: status.color,
621
+ title: status.title,
622
+ titleAlignment: "left"
597
623
  })
598
624
  );
599
625
  };
@@ -769,6 +795,10 @@ var isOutputServer = async () => {
769
795
  const config = await getEventCatalogConfigFile(process.env.PROJECT_DIR || "");
770
796
  return config?.output === "server";
771
797
  };
798
+ var isStudioEnabled = async () => {
799
+ const config = await getEventCatalogConfigFile(process.env.PROJECT_DIR || "");
800
+ return config?.output === "server" && config?.studio?.enabled === true;
801
+ };
772
802
  var isIndexedSearchEnabled = async () => {
773
803
  const config = await getEventCatalogConfigFile(process.env.PROJECT_DIR || "");
774
804
  return config?.search?.type === "indexed";
@@ -836,13 +866,13 @@ var message_channels_to_service_channels_default = async (dir2) => {
836
866
  absolute: true
837
867
  }
838
868
  );
839
- const messagesWithChannels = messages.reduce((acc, message) => {
840
- const file = import_node_fs5.default.readFileSync(message, "utf8");
869
+ const messagesWithChannels = messages.reduce((acc, message2) => {
870
+ const file = import_node_fs5.default.readFileSync(message2, "utf8");
841
871
  const { data } = (0, import_gray_matter3.default)(file);
842
872
  if (data.channels?.length > 0) {
843
873
  acc.push({
844
874
  ...data,
845
- path: message
875
+ path: message2
846
876
  });
847
877
  }
848
878
  return acc;
@@ -853,9 +883,9 @@ var message_channels_to_service_channels_default = async (dir2) => {
853
883
  const servicesWithChannels = services.reduce((acc, service) => {
854
884
  const file = import_node_fs5.default.readFileSync(service, "utf8");
855
885
  const { data } = (0, import_gray_matter3.default)(file);
856
- const isSending = data.sends?.some((send) => messagesWithChannels.some((message) => message.id === send.id));
886
+ const isSending = data.sends?.some((send) => messagesWithChannels.some((message2) => message2.id === send.id));
857
887
  const isReceiving = data.receives?.some(
858
- (receive) => messagesWithChannels.some((message) => message.id === receive.id)
888
+ (receive) => messagesWithChannels.some((message2) => message2.id === receive.id)
859
889
  );
860
890
  if (isSending || isReceiving) {
861
891
  acc.push({
@@ -868,16 +898,16 @@ var message_channels_to_service_channels_default = async (dir2) => {
868
898
  for (const service of servicesWithChannels) {
869
899
  const file = import_node_fs5.default.readFileSync(service.path, "utf8");
870
900
  const { data } = (0, import_gray_matter3.default)(file);
871
- const messagesTheServiceSendsThatNeedUpdating = data.sends?.filter((send) => messagesWithChannels.some((message) => message.id === send.id)) ?? [];
872
- const messagesTheServiceReceivesThatNeedUpdating = data.receives?.filter((receive) => messagesWithChannels.some((message) => message.id === receive.id)) ?? [];
901
+ const messagesTheServiceSendsThatNeedUpdating = data.sends?.filter((send) => messagesWithChannels.some((message2) => message2.id === send.id)) ?? [];
902
+ const messagesTheServiceReceivesThatNeedUpdating = data.receives?.filter((receive) => messagesWithChannels.some((message2) => message2.id === receive.id)) ?? [];
873
903
  if (messagesTheServiceSendsThatNeedUpdating.length > 0 || messagesTheServiceReceivesThatNeedUpdating.length > 0) {
874
904
  const newSends = (data.sends ?? []).map((send) => {
875
- const match = messagesWithChannels.find((message) => message.id === send.id);
905
+ const match = messagesWithChannels.find((message2) => message2.id === send.id);
876
906
  if (!match) return send;
877
907
  return { ...send, to: (match.channels ?? []).filter((channel) => channel !== null) };
878
908
  });
879
909
  const newReceives = (data.receives ?? []).map((receive) => {
880
- const match = messagesWithChannels.find((message) => message.id === receive.id);
910
+ const match = messagesWithChannels.find((message2) => message2.id === receive.id);
881
911
  if (!match) return receive;
882
912
  return { ...receive, from: (match.channels ?? []).filter((channel) => channel !== null) };
883
913
  });
@@ -889,11 +919,11 @@ var message_channels_to_service_channels_default = async (dir2) => {
889
919
  import_node_fs5.default.writeFileSync(service.path, import_gray_matter3.default.stringify(file, newData));
890
920
  }
891
921
  }
892
- for (const message of messagesWithChannels) {
893
- const file = import_node_fs5.default.readFileSync(message.path, "utf8");
922
+ for (const message2 of messagesWithChannels) {
923
+ const file = import_node_fs5.default.readFileSync(message2.path, "utf8");
894
924
  const parsed = (0, import_gray_matter3.default)(file);
895
925
  const { channels, ...newData } = parsed.data;
896
- import_node_fs5.default.writeFileSync(message.path, import_gray_matter3.default.stringify(parsed.content, newData));
926
+ import_node_fs5.default.writeFileSync(message2.path, import_gray_matter3.default.stringify(parsed.content, newData));
897
927
  }
898
928
  console.log("\u2714 Channels migrated to new services API");
899
929
  };
@@ -1055,7 +1085,7 @@ var FieldsDatabase = class _FieldsDatabase {
1055
1085
  format,
1056
1086
  type,
1057
1087
  messageType,
1058
- message,
1088
+ message: message2,
1059
1089
  producer,
1060
1090
  consumer,
1061
1091
  required,
@@ -1089,9 +1119,9 @@ var FieldsDatabase = class _FieldsDatabase {
1089
1119
  conditions.push(`f.message_type IN (${types2.map(() => "?").join(", ")})`);
1090
1120
  bindings.push(...types2);
1091
1121
  }
1092
- if (message) {
1122
+ if (message2) {
1093
1123
  conditions.push(`f.message_id = ?`);
1094
- bindings.push(message);
1124
+ bindings.push(message2);
1095
1125
  }
1096
1126
  if (required) {
1097
1127
  conditions.push(`f.required = 1`);
@@ -1904,8 +1934,8 @@ var KNOWN_EVENTCATALOG_COLLECTIONS = /* @__PURE__ */ new Set([
1904
1934
  var EMPTY_COLLECTION_MESSAGE = /^The collection ("(?:\\.|[^"\\])*") does not exist or is empty\. Please check your content config file for errors\.$/;
1905
1935
  var ANSI_PATTERN = /\u001B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g;
1906
1936
  var ASTRO_CONTENT_WARN_PREFIX = /^[^[\]]*\[WARN\]\s+\[content\](?:\s+\[[^\]]+\])*\s*$/;
1907
- function collectionNameFromEmptyWarning(message) {
1908
- const match = message.match(EMPTY_COLLECTION_MESSAGE);
1937
+ function collectionNameFromEmptyWarning(message2) {
1938
+ const match = message2.match(EMPTY_COLLECTION_MESSAGE);
1909
1939
  if (!match) return null;
1910
1940
  try {
1911
1941
  const name = JSON.parse(match[1]);
@@ -1914,8 +1944,8 @@ function collectionNameFromEmptyWarning(message) {
1914
1944
  return null;
1915
1945
  }
1916
1946
  }
1917
- function isIntentionalEmptyCollectionMessage(message) {
1918
- const name = collectionNameFromEmptyWarning(message);
1947
+ function isIntentionalEmptyCollectionMessage(message2) {
1948
+ const name = collectionNameFromEmptyWarning(message2);
1919
1949
  return name !== null && KNOWN_EVENTCATALOG_COLLECTIONS.has(name);
1920
1950
  }
1921
1951
  function isIntentionalEmptyCollectionLine(line) {
@@ -1923,8 +1953,8 @@ function isIntentionalEmptyCollectionLine(line) {
1923
1953
  const marker = 'The collection "';
1924
1954
  const start = plain.indexOf(marker);
1925
1955
  if (start === -1) return false;
1926
- const message = plain.slice(start).trim();
1927
- if (!isIntentionalEmptyCollectionMessage(message)) return false;
1956
+ const message2 = plain.slice(start).trim();
1957
+ if (!isIntentionalEmptyCollectionMessage(message2)) return false;
1928
1958
  const before = plain.slice(0, start).trim();
1929
1959
  return before.length === 0 || ASTRO_CONTENT_WARN_PREFIX.test(before);
1930
1960
  }
@@ -2768,8 +2798,8 @@ var federateCatalog = async (projectDirectory, options = {}) => {
2768
2798
  generated: resolved.generated
2769
2799
  });
2770
2800
  } catch (error) {
2771
- const message = error instanceof Error ? error.message : String(error);
2772
- throw new Error(`Failed to federate source "${source.id}": ${message}`, { cause: error });
2801
+ const message2 = error instanceof Error ? error.message : String(error);
2802
+ throw new Error(`Failed to federate source "${source.id}": ${message2}`, { cause: error });
2773
2803
  }
2774
2804
  }
2775
2805
  const outDir = import_node_path19.default.join(projectDirectory, "federated");
@@ -3114,10 +3144,10 @@ Upgrade now: npm i @eventcatalog/core@beta`;
3114
3144
  if (!installedVersion) return;
3115
3145
  const pkg = { name: "@eventcatalog/core", version: installedVersion };
3116
3146
  const notifier = (0, import_update_notifier.default)({ pkg, updateCheckInterval: 0 });
3117
- const message = getEventCatalogUpdateMessage(installedVersion, notifier.update?.latest);
3118
- if (message) {
3147
+ const message2 = getEventCatalogUpdateMessage(installedVersion, notifier.update?.latest);
3148
+ if (message2) {
3119
3149
  console.log(
3120
- (0, import_boxen2.default)(message, {
3150
+ (0, import_boxen2.default)(message2, {
3121
3151
  padding: 1,
3122
3152
  margin: 1,
3123
3153
  align: "center",
@@ -3278,15 +3308,33 @@ var previewCatalog = async ({ command }) => {
3278
3308
  shouldFilterLine: createAstroLineFilter()
3279
3309
  });
3280
3310
  };
3311
+ var SERVER_BOOTSTRAP = `
3312
+ const entry = await import(process.argv[1]);
3313
+ const { server } = entry.startServer();
3314
+ globalThis[Symbol.for('eventcatalog.http-server')] = server.server;
3315
+ const wake = process.env.EVENTCATALOG_STUDIO_WAKE_PATH;
3316
+ if (wake) {
3317
+ server.server.once('listening', () => {
3318
+ // Where the server listens (HOST can be one address only); loopback when it listens on every address
3319
+ const { address, port } = server.server.address();
3320
+ const host = ['0.0.0.0', '::'].includes(address) ? '127.0.0.1' : address.includes(':') ? '[' + address + ']' : address;
3321
+ fetch('http://' + host + ':' + port + wake).catch((error) => console.error('[studio] Could not start Studio:', error.message));
3322
+ });
3323
+ }
3324
+ `;
3281
3325
  var startServerCatalog = async () => {
3282
3326
  const serverEntryPath = import_node_path21.default.join(dir, "dist", "server", "entry.mjs");
3327
+ const config = await getEventCatalogConfigFile(dir);
3328
+ const base = (config?.base || "/").replace(/\/$/, "");
3283
3329
  await runCommandWithFilteredOutput({
3284
3330
  command: process.execPath,
3285
- args: [serverEntryPath],
3331
+ args: ["--input-type=module", "-e", SERVER_BOOTSTRAP, (0, import_node_url2.pathToFileURL)(serverEntryPath).href],
3286
3332
  cwd: dir,
3287
3333
  env: {
3288
3334
  PROJECT_DIR: dir,
3289
- CATALOG_DIR: core
3335
+ CATALOG_DIR: core,
3336
+ ASTRO_NODE_AUTOSTART: "disabled",
3337
+ ...await isStudioEnabled() && { EVENTCATALOG_STUDIO_WAKE_PATH: `${base}/_eventcatalog/studio/wake` }
3290
3338
  },
3291
3339
  shouldFilterLine: createAstroLineFilter()
3292
3340
  });
@@ -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;
@@ -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;
@@ -4,11 +4,11 @@ import {
4
4
  import "./chunk-CA4U2JP7.js";
5
5
  import {
6
6
  log_build_default
7
- } from "./chunk-XMPFHLK4.js";
7
+ } from "./chunk-4W55ZOOS.js";
8
8
  import {
9
9
  getLicenseStatus,
10
10
  printLicenseStatus
11
- } from "./chunk-PIYJM642.js";
11
+ } from "./chunk-AWOO63ME.js";
12
12
  import "./chunk-ZAZHAVKB.js";
13
13
  import {
14
14
  FederationConflictError,
@@ -29,14 +29,15 @@ import {
29
29
  getProjectOutDir,
30
30
  isAuthEnabled,
31
31
  isIndexedSearchEnabled,
32
- isOutputServer
33
- } from "./chunk-MDWB5HLO.js";
32
+ isOutputServer,
33
+ isStudioEnabled
34
+ } from "./chunk-ENAG6UH7.js";
34
35
  import {
35
36
  generate
36
- } from "./chunk-DE3QHVJP.js";
37
+ } from "./chunk-T4G3VVZ4.js";
37
38
  import {
38
39
  logger
39
- } from "./chunk-56CD2NH6.js";
40
+ } from "./chunk-PU25TOOJ.js";
40
41
  import {
41
42
  resolve_catalog_dependencies_default
42
43
  } from "./chunk-36RYTYT5.js";
@@ -50,7 +51,7 @@ import {
50
51
  import {
51
52
  watch
52
53
  } from "./chunk-U63GDPPY.js";
53
- import "./chunk-QBP3BBSV.js";
54
+ import "./chunk-C6T2AAFR.js";
54
55
  import {
55
56
  getAstroConfigPath
56
57
  } from "./chunk-VIJJ7JKH.js";
@@ -66,7 +67,7 @@ import {
66
67
  } from "./chunk-LFQJXLHG.js";
67
68
  import {
68
69
  VERSION
69
- } from "./chunk-L2IRFOE7.js";
70
+ } from "./chunk-RDIZJ7UT.js";
70
71
  import {
71
72
  getEventCatalogConfigFile,
72
73
  verifyRequiredFieldsAreInCatalogConfigFile
@@ -79,7 +80,7 @@ import { join } from "path";
79
80
  import http from "http";
80
81
  import fs3 from "fs";
81
82
  import path2 from "path";
82
- import { fileURLToPath } from "url";
83
+ import { fileURLToPath, pathToFileURL } from "url";
83
84
  import { createRequire } from "module";
84
85
  import boxen from "boxen";
85
86
  import updateNotifier from "update-notifier";
@@ -1028,15 +1029,33 @@ var previewCatalog = async ({ command }) => {
1028
1029
  shouldFilterLine: createAstroLineFilter()
1029
1030
  });
1030
1031
  };
1032
+ var SERVER_BOOTSTRAP = `
1033
+ const entry = await import(process.argv[1]);
1034
+ const { server } = entry.startServer();
1035
+ globalThis[Symbol.for('eventcatalog.http-server')] = server.server;
1036
+ const wake = process.env.EVENTCATALOG_STUDIO_WAKE_PATH;
1037
+ if (wake) {
1038
+ server.server.once('listening', () => {
1039
+ // Where the server listens (HOST can be one address only); loopback when it listens on every address
1040
+ const { address, port } = server.server.address();
1041
+ const host = ['0.0.0.0', '::'].includes(address) ? '127.0.0.1' : address.includes(':') ? '[' + address + ']' : address;
1042
+ fetch('http://' + host + ':' + port + wake).catch((error) => console.error('[studio] Could not start Studio:', error.message));
1043
+ });
1044
+ }
1045
+ `;
1031
1046
  var startServerCatalog = async () => {
1032
1047
  const serverEntryPath = path2.join(dir, "dist", "server", "entry.mjs");
1048
+ const config = await getEventCatalogConfigFile(dir);
1049
+ const base = (config?.base || "/").replace(/\/$/, "");
1033
1050
  await runCommandWithFilteredOutput({
1034
1051
  command: process.execPath,
1035
- args: [serverEntryPath],
1052
+ args: ["--input-type=module", "-e", SERVER_BOOTSTRAP, pathToFileURL(serverEntryPath).href],
1036
1053
  cwd: dir,
1037
1054
  env: {
1038
1055
  PROJECT_DIR: dir,
1039
- CATALOG_DIR: core
1056
+ CATALOG_DIR: core,
1057
+ ASTRO_NODE_AUTOSTART: "disabled",
1058
+ ...await isStudioEnabled() && { EVENTCATALOG_STUDIO_WAKE_PATH: `${base}/_eventcatalog/studio/wake` }
1040
1059
  },
1041
1060
  shouldFilterLine: createAstroLineFilter()
1042
1061
  });
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
  });