@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
@@ -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.7";
184
+ var version = "5.0.0-beta.9";
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
 
@@ -304,7 +304,23 @@ var generate = async (PROJECT_DIRECTORY) => {
304
304
 
305
305
  // src/analytics/analytics.js
306
306
  var import_os = __toESM(require("os"), 1);
307
- async function raiseEvent(eventData) {
307
+
308
+ // src/analytics/telemetry.ts
309
+ var TELEMETRY_REQUEST_TIMEOUT_MS = 2e3;
310
+ var isOptOutEnvValue = (value) => {
311
+ if (typeof value !== "string") return false;
312
+ const normalized = value.trim().toLowerCase();
313
+ return normalized === "1" || normalized === "true";
314
+ };
315
+ var isTelemetryDisabled = (config, env) => {
316
+ if (isOptOutEnvValue(env.EVENTCATALOG_TELEMETRY_DISABLED)) return true;
317
+ if (isOptOutEnvValue(env.DO_NOT_TRACK)) return true;
318
+ return config?.telemetry === false;
319
+ };
320
+
321
+ // src/analytics/analytics.js
322
+ async function raiseEvent(eventData, config) {
323
+ if (isTelemetryDisabled(config, process.env)) return;
308
324
  if (typeof eventData?.command !== "string" || typeof eventData?.cId !== "string") return;
309
325
  const url = "https://queue.simpleanalyticscdn.com/events";
310
326
  const userAgent = `@eventcatalog/eventcatalog@${VERSION} (${import_os.default.platform()}; ${import_os.default.arch()}; Node/${process.version})`;
@@ -326,7 +342,8 @@ async function raiseEvent(eventData) {
326
342
  const response = await fetch(url, {
327
343
  method: "POST",
328
344
  headers,
329
- body: JSON.stringify(payload)
345
+ body: JSON.stringify(payload),
346
+ signal: AbortSignal.timeout(TELEMETRY_REQUEST_TIMEOUT_MS)
330
347
  });
331
348
  if (!response.ok) {
332
349
  throw new Error(`Failed to raise analytics event: ${response.status}`);
@@ -403,6 +420,7 @@ function serializeCounts(counts) {
403
420
 
404
421
  // src/utils/license-status.ts
405
422
  var import_boxen = __toESM(require("boxen"), 1);
423
+ var import_picocolors2 = __toESM(require("picocolors"), 1);
406
424
  var import_license2 = require("@eventcatalog/license");
407
425
 
408
426
  // eventcatalog/src/utils/trial.ts
@@ -525,75 +543,100 @@ var getLicenseAnalytics = (license = { state: "none" }, tsd) => ({
525
543
  });
526
544
  var formatDate = (date) => date.toLocaleDateString(void 0, { dateStyle: "medium" });
527
545
  var formatDays = (days) => `${days} ${days === 1 ? "day" : "days"}`;
528
- var getTrialLine = (tsd, now) => {
546
+ var PLAIN = { bold: (text) => text, dim: (text) => text, link: (text) => text };
547
+ var COLOURS = { bold: import_picocolors2.default.bold, dim: import_picocolors2.default.dim, link: (text) => import_picocolors2.default.cyan(text) };
548
+ var render = ({ headline, rows, links }, colours) => {
549
+ const width = Math.max(0, ...[...rows, ...links].map(([label]) => label.length));
550
+ const row = ([label, value]) => `${colours.dim(label.padEnd(width))} ${value}`;
551
+ return [
552
+ colours.bold(headline),
553
+ ...rows.length ? ["", ...rows.map(row)] : [],
554
+ ...links.length ? ["", ...links.map(([label, url]) => row([label, colours.link(url)]))] : []
555
+ ].join("\n");
556
+ };
557
+ var message = (content) => ({
558
+ ...content,
559
+ text: render(content, PLAIN)
560
+ });
561
+ var getTrialRow = (tsd, now) => {
529
562
  const trial = getTrialStatus(tsd, now);
530
563
  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)}).`;
564
+ return [
565
+ "Trial",
566
+ trial.ended ? `Ended ${formatDate(trial.endsAt)}` : `${formatDays(trial.daysLeft)} left of ${TRIAL_LENGTH_DAYS} (ends ${formatDate(trial.endsAt)})`
567
+ ];
532
568
  };
533
569
  var getLicenseStatusMessage = (license, tsd, now = Date.now()) => {
570
+ const licensing = ["Licensing", LICENSE_FAQ_URL];
571
+ const renew = ["Renew", LICENSE_RENEW_URL];
572
+ const licensedTo = "org" in license && license.org ? [["Licensed to", license.org]] : [];
534
573
  if (license.state === "licensed") {
535
574
  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
575
  if (daysLeft < LICENSE_REMINDER_DAYS) {
538
- return {
539
- title: "EventCatalog Commercial License",
576
+ return message({
577
+ title: "EventCatalog license",
540
578
  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
- };
579
+ headline: `Your commercial license expires in ${formatDays(daysLeft)}`,
580
+ rows: [...licensedTo, ["Expires", formatDate(license.expiresAt)]],
581
+ links: [renew]
582
+ });
547
583
  }
548
- return {
549
- title: "EventCatalog Commercial License",
584
+ return message({
585
+ title: "EventCatalog license",
550
586
  color: "green",
551
- text: `${licensedTo}
552
- Valid until ${formatDate(license.expiresAt)} (${formatDays(daysLeft)} left)`
553
- };
587
+ headline: "Commercial license active",
588
+ rows: [...licensedTo, ["Valid until", `${formatDate(license.expiresAt)} (${formatDays(daysLeft)} left)`]],
589
+ links: []
590
+ });
554
591
  }
555
592
  if (license.state === "plan") return;
556
- const trialLine = getTrialLine(tsd, now);
593
+ const trialRow = getTrialRow(tsd, now);
594
+ const trialRows = trialRow ? [trialRow] : [];
557
595
  if (license.state === "none") {
558
- if (!trialLine) return;
559
- const ended = getTrialStatus(tsd, now)?.ended;
560
- return {
596
+ const trial = getTrialStatus(tsd, now);
597
+ if (!trial || !trialRow) return;
598
+ return message({
561
599
  title: "EventCatalog trial",
562
- color: ended ? "yellow" : "green",
563
- text: `${trialLine}
564
- Learn about licensing: ${LICENSE_FAQ_URL}`
565
- };
600
+ color: trial.ended ? "yellow" : "green",
601
+ headline: trial.ended ? "Your EventCatalog trial has ended" : `You're on the ${TRIAL_LENGTH_DAYS}-day EventCatalog trial`,
602
+ rows: trialRows,
603
+ links: trial.ended ? [["Get a license", LICENSE_RENEW_URL], licensing] : [licensing]
604
+ });
566
605
  }
567
606
  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",
607
+ const expiredOn = license.expiresAt ? [
608
+ [
609
+ "Expired",
610
+ `${formatDate(license.expiresAt)} (${formatDays(Math.floor((now - license.expiresAt.getTime()) / DAY_IN_MS2))} ago)`
611
+ ]
612
+ ] : [];
613
+ return message({
614
+ title: "EventCatalog license",
571
615
  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
- };
616
+ headline: "Your commercial license has expired",
617
+ rows: [...licensedTo, ...expiredOn, ...trialRows],
618
+ links: [renew, licensing]
619
+ });
579
620
  }
580
- return {
621
+ return message({
581
622
  title: "EventCatalog license",
582
623
  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
- };
624
+ headline: "Your license.jwt couldn't be verified",
625
+ rows: [["Reason", license.reason.charAt(0).toUpperCase() + license.reason.slice(1)], ...trialRows],
626
+ links: [licensing]
627
+ });
585
628
  };
586
629
  var printLicenseStatus = (license, tsd) => {
587
- const message = getLicenseStatusMessage(license, tsd);
588
- if (!message) return;
630
+ const status = getLicenseStatusMessage(license, tsd);
631
+ if (!status) return;
589
632
  console.log(
590
- (0, import_boxen.default)(message.text, {
591
- padding: 1,
633
+ (0, import_boxen.default)(render(status, COLOURS), {
634
+ padding: { top: 1, bottom: 1, left: 2, right: 2 },
592
635
  margin: 1,
593
636
  borderStyle: "round",
594
- borderColor: message.color,
595
- title: message.title,
596
- titleAlignment: "center"
637
+ borderColor: status.color,
638
+ title: status.title,
639
+ titleAlignment: "left"
597
640
  })
598
641
  );
599
642
  };
@@ -653,50 +696,69 @@ var toCloudResourceCounts = (counts) => ({
653
696
  customPages: counts.customPages || 0,
654
697
  customApis: counts.customApis || 0
655
698
  });
656
- var reportCloudResourceInventory = async (configFile, resourceCounts) => {
699
+ var getEnabledCloudAnalytics = (configFile) => {
657
700
  const analytics = configFile.cloud?.analytics;
658
- if (!analytics?.enabled || !analytics.trackingId || !analytics.writeKey) return;
701
+ if (!analytics?.enabled || !analytics.trackingId || !analytics.writeKey) return null;
702
+ return analytics;
703
+ };
704
+ var reportCloudResourceInventory = async (configFile, resourceCounts) => {
705
+ const analytics = getEnabledCloudAnalytics(configFile);
706
+ if (!analytics) return;
659
707
  const endpoint = analytics.endpoint || CLOUD_ANALYTICS_ENDPOINT;
660
- await fetch(endpoint, {
661
- method: "POST",
662
- headers: {
663
- "Content-Type": "application/json",
664
- "X-EventCatalog-Analytics-Key": analytics.writeKey
665
- },
666
- body: JSON.stringify({
667
- trackingId: analytics.trackingId,
668
- event: "catalog.resource_inventory_reported",
669
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
670
- counts: toCloudResourceCounts(resourceCounts)
671
- })
672
- });
708
+ try {
709
+ await fetch(endpoint, {
710
+ method: "POST",
711
+ headers: {
712
+ "Content-Type": "application/json",
713
+ "X-EventCatalog-Analytics-Key": analytics.writeKey
714
+ },
715
+ body: JSON.stringify({
716
+ trackingId: analytics.trackingId,
717
+ event: "catalog.resource_inventory_reported",
718
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
719
+ counts: toCloudResourceCounts(resourceCounts)
720
+ }),
721
+ signal: AbortSignal.timeout(TELEMETRY_REQUEST_TIMEOUT_MS)
722
+ });
723
+ } catch {
724
+ }
673
725
  };
674
726
  var main = async (projectDir, { command = "build", license } = {}) => {
675
727
  try {
676
728
  await verifyRequiredFieldsAreInCatalogConfigFile(projectDir);
677
729
  const configFile = await getEventCatalogConfigFile(projectDir);
730
+ if (isTelemetryDisabled(configFile, process.env)) {
731
+ if (getEnabledCloudAnalytics(configFile)) {
732
+ const resourceCounts2 = await countResources(projectDir);
733
+ await reportCloudResourceInventory(configFile, resourceCounts2);
734
+ }
735
+ return;
736
+ }
678
737
  const { cId, tsd, organizationName, generators = [] } = configFile;
679
738
  const generatorNames = generators.length > 0 ? generators.map((generator) => generator[0]) : ["none"];
680
739
  const features = await getFeatures(configFile);
681
740
  const resourceCounts = await countResources(projectDir);
682
741
  const contentHash = await hashCatalogContent(projectDir);
683
742
  await reportCloudResourceInventory(configFile, resourceCounts);
684
- await raiseEvent({
685
- command,
686
- org: organizationName,
687
- cId,
688
- // Trial start date (unix ms) from eventcatalog.config.js
689
- tsd,
690
- // Commercial license or trial, and when each expires (unix ms)
691
- ...getLicenseAnalytics(license, tsd),
692
- // CI redeploys and human-run builds are different signals; tag rather than suppress
693
- ci: process.env.CI ? "true" : "false",
694
- contentHash,
695
- generators: generatorNames.toString(),
696
- features: Object.keys(features).map((feature) => `${feature}:${features[feature]}`).join(","),
697
- directorySources: serializeDirectorySources(configFile),
698
- resources: serializeCounts(resourceCounts)
699
- });
743
+ await raiseEvent(
744
+ {
745
+ command,
746
+ org: organizationName,
747
+ cId,
748
+ // Trial start date (unix ms) from eventcatalog.config.js
749
+ tsd,
750
+ // Commercial license or trial, and when each expires (unix ms)
751
+ ...getLicenseAnalytics(license, tsd),
752
+ // CI redeploys and human-run builds are different signals; tag rather than suppress
753
+ ci: process.env.CI ? "true" : "false",
754
+ contentHash,
755
+ generators: generatorNames.toString(),
756
+ features: Object.keys(features).map((feature) => `${feature}:${features[feature]}`).join(","),
757
+ directorySources: serializeDirectorySources(configFile),
758
+ resources: serializeCounts(resourceCounts)
759
+ },
760
+ configFile
761
+ );
700
762
  } catch (error) {
701
763
  }
702
764
  };
@@ -769,6 +831,10 @@ var isOutputServer = async () => {
769
831
  const config = await getEventCatalogConfigFile(process.env.PROJECT_DIR || "");
770
832
  return config?.output === "server";
771
833
  };
834
+ var isStudioEnabled = async () => {
835
+ const config = await getEventCatalogConfigFile(process.env.PROJECT_DIR || "");
836
+ return config?.output === "server" && config?.studio?.enabled === true;
837
+ };
772
838
  var isIndexedSearchEnabled = async () => {
773
839
  const config = await getEventCatalogConfigFile(process.env.PROJECT_DIR || "");
774
840
  return config?.search?.type === "indexed";
@@ -836,13 +902,13 @@ var message_channels_to_service_channels_default = async (dir2) => {
836
902
  absolute: true
837
903
  }
838
904
  );
839
- const messagesWithChannels = messages.reduce((acc, message) => {
840
- const file = import_node_fs5.default.readFileSync(message, "utf8");
905
+ const messagesWithChannels = messages.reduce((acc, message2) => {
906
+ const file = import_node_fs5.default.readFileSync(message2, "utf8");
841
907
  const { data } = (0, import_gray_matter3.default)(file);
842
908
  if (data.channels?.length > 0) {
843
909
  acc.push({
844
910
  ...data,
845
- path: message
911
+ path: message2
846
912
  });
847
913
  }
848
914
  return acc;
@@ -853,9 +919,9 @@ var message_channels_to_service_channels_default = async (dir2) => {
853
919
  const servicesWithChannels = services.reduce((acc, service) => {
854
920
  const file = import_node_fs5.default.readFileSync(service, "utf8");
855
921
  const { data } = (0, import_gray_matter3.default)(file);
856
- const isSending = data.sends?.some((send) => messagesWithChannels.some((message) => message.id === send.id));
922
+ const isSending = data.sends?.some((send) => messagesWithChannels.some((message2) => message2.id === send.id));
857
923
  const isReceiving = data.receives?.some(
858
- (receive) => messagesWithChannels.some((message) => message.id === receive.id)
924
+ (receive) => messagesWithChannels.some((message2) => message2.id === receive.id)
859
925
  );
860
926
  if (isSending || isReceiving) {
861
927
  acc.push({
@@ -868,16 +934,16 @@ var message_channels_to_service_channels_default = async (dir2) => {
868
934
  for (const service of servicesWithChannels) {
869
935
  const file = import_node_fs5.default.readFileSync(service.path, "utf8");
870
936
  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)) ?? [];
937
+ const messagesTheServiceSendsThatNeedUpdating = data.sends?.filter((send) => messagesWithChannels.some((message2) => message2.id === send.id)) ?? [];
938
+ const messagesTheServiceReceivesThatNeedUpdating = data.receives?.filter((receive) => messagesWithChannels.some((message2) => message2.id === receive.id)) ?? [];
873
939
  if (messagesTheServiceSendsThatNeedUpdating.length > 0 || messagesTheServiceReceivesThatNeedUpdating.length > 0) {
874
940
  const newSends = (data.sends ?? []).map((send) => {
875
- const match = messagesWithChannels.find((message) => message.id === send.id);
941
+ const match = messagesWithChannels.find((message2) => message2.id === send.id);
876
942
  if (!match) return send;
877
943
  return { ...send, to: (match.channels ?? []).filter((channel) => channel !== null) };
878
944
  });
879
945
  const newReceives = (data.receives ?? []).map((receive) => {
880
- const match = messagesWithChannels.find((message) => message.id === receive.id);
946
+ const match = messagesWithChannels.find((message2) => message2.id === receive.id);
881
947
  if (!match) return receive;
882
948
  return { ...receive, from: (match.channels ?? []).filter((channel) => channel !== null) };
883
949
  });
@@ -889,11 +955,11 @@ var message_channels_to_service_channels_default = async (dir2) => {
889
955
  import_node_fs5.default.writeFileSync(service.path, import_gray_matter3.default.stringify(file, newData));
890
956
  }
891
957
  }
892
- for (const message of messagesWithChannels) {
893
- const file = import_node_fs5.default.readFileSync(message.path, "utf8");
958
+ for (const message2 of messagesWithChannels) {
959
+ const file = import_node_fs5.default.readFileSync(message2.path, "utf8");
894
960
  const parsed = (0, import_gray_matter3.default)(file);
895
961
  const { channels, ...newData } = parsed.data;
896
- import_node_fs5.default.writeFileSync(message.path, import_gray_matter3.default.stringify(parsed.content, newData));
962
+ import_node_fs5.default.writeFileSync(message2.path, import_gray_matter3.default.stringify(parsed.content, newData));
897
963
  }
898
964
  console.log("\u2714 Channels migrated to new services API");
899
965
  };
@@ -1055,7 +1121,7 @@ var FieldsDatabase = class _FieldsDatabase {
1055
1121
  format,
1056
1122
  type,
1057
1123
  messageType,
1058
- message,
1124
+ message: message2,
1059
1125
  producer,
1060
1126
  consumer,
1061
1127
  required,
@@ -1089,9 +1155,9 @@ var FieldsDatabase = class _FieldsDatabase {
1089
1155
  conditions.push(`f.message_type IN (${types2.map(() => "?").join(", ")})`);
1090
1156
  bindings.push(...types2);
1091
1157
  }
1092
- if (message) {
1158
+ if (message2) {
1093
1159
  conditions.push(`f.message_id = ?`);
1094
- bindings.push(message);
1160
+ bindings.push(message2);
1095
1161
  }
1096
1162
  if (required) {
1097
1163
  conditions.push(`f.required = 1`);
@@ -1904,8 +1970,8 @@ var KNOWN_EVENTCATALOG_COLLECTIONS = /* @__PURE__ */ new Set([
1904
1970
  var EMPTY_COLLECTION_MESSAGE = /^The collection ("(?:\\.|[^"\\])*") does not exist or is empty\. Please check your content config file for errors\.$/;
1905
1971
  var ANSI_PATTERN = /\u001B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g;
1906
1972
  var ASTRO_CONTENT_WARN_PREFIX = /^[^[\]]*\[WARN\]\s+\[content\](?:\s+\[[^\]]+\])*\s*$/;
1907
- function collectionNameFromEmptyWarning(message) {
1908
- const match = message.match(EMPTY_COLLECTION_MESSAGE);
1973
+ function collectionNameFromEmptyWarning(message2) {
1974
+ const match = message2.match(EMPTY_COLLECTION_MESSAGE);
1909
1975
  if (!match) return null;
1910
1976
  try {
1911
1977
  const name = JSON.parse(match[1]);
@@ -1914,8 +1980,8 @@ function collectionNameFromEmptyWarning(message) {
1914
1980
  return null;
1915
1981
  }
1916
1982
  }
1917
- function isIntentionalEmptyCollectionMessage(message) {
1918
- const name = collectionNameFromEmptyWarning(message);
1983
+ function isIntentionalEmptyCollectionMessage(message2) {
1984
+ const name = collectionNameFromEmptyWarning(message2);
1919
1985
  return name !== null && KNOWN_EVENTCATALOG_COLLECTIONS.has(name);
1920
1986
  }
1921
1987
  function isIntentionalEmptyCollectionLine(line) {
@@ -1923,8 +1989,8 @@ function isIntentionalEmptyCollectionLine(line) {
1923
1989
  const marker = 'The collection "';
1924
1990
  const start = plain.indexOf(marker);
1925
1991
  if (start === -1) return false;
1926
- const message = plain.slice(start).trim();
1927
- if (!isIntentionalEmptyCollectionMessage(message)) return false;
1992
+ const message2 = plain.slice(start).trim();
1993
+ if (!isIntentionalEmptyCollectionMessage(message2)) return false;
1928
1994
  const before = plain.slice(0, start).trim();
1929
1995
  return before.length === 0 || ASTRO_CONTENT_WARN_PREFIX.test(before);
1930
1996
  }
@@ -2768,8 +2834,8 @@ var federateCatalog = async (projectDirectory, options = {}) => {
2768
2834
  generated: resolved.generated
2769
2835
  });
2770
2836
  } 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 });
2837
+ const message2 = error instanceof Error ? error.message : String(error);
2838
+ throw new Error(`Failed to federate source "${source.id}": ${message2}`, { cause: error });
2773
2839
  }
2774
2840
  }
2775
2841
  const outDir = import_node_path19.default.join(projectDirectory, "federated");
@@ -3114,10 +3180,10 @@ Upgrade now: npm i @eventcatalog/core@beta`;
3114
3180
  if (!installedVersion) return;
3115
3181
  const pkg = { name: "@eventcatalog/core", version: installedVersion };
3116
3182
  const notifier = (0, import_update_notifier.default)({ pkg, updateCheckInterval: 0 });
3117
- const message = getEventCatalogUpdateMessage(installedVersion, notifier.update?.latest);
3118
- if (message) {
3183
+ const message2 = getEventCatalogUpdateMessage(installedVersion, notifier.update?.latest);
3184
+ if (message2) {
3119
3185
  console.log(
3120
- (0, import_boxen2.default)(message, {
3186
+ (0, import_boxen2.default)(message2, {
3121
3187
  padding: 1,
3122
3188
  margin: 1,
3123
3189
  align: "center",
@@ -3278,15 +3344,33 @@ var previewCatalog = async ({ command }) => {
3278
3344
  shouldFilterLine: createAstroLineFilter()
3279
3345
  });
3280
3346
  };
3347
+ var SERVER_BOOTSTRAP = `
3348
+ const entry = await import(process.argv[1]);
3349
+ const { server } = entry.startServer();
3350
+ globalThis[Symbol.for('eventcatalog.http-server')] = server.server;
3351
+ const wake = process.env.EVENTCATALOG_STUDIO_WAKE_PATH;
3352
+ if (wake) {
3353
+ server.server.once('listening', () => {
3354
+ // Where the server listens (HOST can be one address only); loopback when it listens on every address
3355
+ const { address, port } = server.server.address();
3356
+ const host = ['0.0.0.0', '::'].includes(address) ? '127.0.0.1' : address.includes(':') ? '[' + address + ']' : address;
3357
+ fetch('http://' + host + ':' + port + wake).catch((error) => console.error('[studio] Could not start Studio:', error.message));
3358
+ });
3359
+ }
3360
+ `;
3281
3361
  var startServerCatalog = async () => {
3282
3362
  const serverEntryPath = import_node_path21.default.join(dir, "dist", "server", "entry.mjs");
3363
+ const config = await getEventCatalogConfigFile(dir);
3364
+ const base = (config?.base || "/").replace(/\/$/, "");
3283
3365
  await runCommandWithFilteredOutput({
3284
3366
  command: process.execPath,
3285
- args: [serverEntryPath],
3367
+ args: ["--input-type=module", "-e", SERVER_BOOTSTRAP, (0, import_node_url2.pathToFileURL)(serverEntryPath).href],
3286
3368
  cwd: dir,
3287
3369
  env: {
3288
3370
  PROJECT_DIR: dir,
3289
- CATALOG_DIR: core
3371
+ CATALOG_DIR: core,
3372
+ ASTRO_NODE_AUTOSTART: "disabled",
3373
+ ...await isStudioEnabled() && { EVENTCATALOG_STUDIO_WAKE_PATH: `${base}/_eventcatalog/studio/wake` }
3290
3374
  },
3291
3375
  shouldFilterLine: createAstroLineFilter()
3292
3376
  });
@@ -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;