artifacty 0.1.0 → 0.1.2

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.
@@ -1,6 +1,6 @@
1
1
  import path from "node:path";
2
2
  import { createHash } from "node:crypto";
3
- import { contentTypeForFormat, normalizeFormat } from "./storage.js";
3
+ import { ARTIFACT_TYPES, contentTypeForFormat, normalizeFormat } from "./storage.js";
4
4
 
5
5
  const KNOWN_AGENTS = new Set(["auto", "artifacty", "claude", "codex", "gemini", "generic"]);
6
6
 
@@ -68,6 +68,18 @@ export function convertAgentArtifact(input = {}) {
68
68
 
69
69
  export function detectFormat({ content = "", contentType = "", fileName = "" } = {}) {
70
70
  const type = optionalString(contentType).toLowerCase();
71
+ if (type.includes("vnd.ant.code") || type.includes("source-code")) {
72
+ return "code";
73
+ }
74
+ if (type.includes("svg")) {
75
+ return "svg";
76
+ }
77
+ if (type.includes("vnd.ant.mermaid") || type.includes("mermaid")) {
78
+ return "mermaid";
79
+ }
80
+ if (type.includes("vnd.ant.react") || type.includes("jsx")) {
81
+ return "react";
82
+ }
71
83
  if (type.includes("html")) {
72
84
  return "html";
73
85
  }
@@ -91,11 +103,32 @@ export function detectFormat({ content = "", contentType = "", fileName = "" } =
91
103
  if (extension === ".json") {
92
104
  return "json";
93
105
  }
106
+ if (extension === ".svg") {
107
+ return "svg";
108
+ }
109
+ if (extension === ".mmd" || extension === ".mermaid") {
110
+ return "mermaid";
111
+ }
112
+ if (extension === ".jsx" || extension === ".tsx") {
113
+ return "react";
114
+ }
115
+ if (isCodeExtension(extension)) {
116
+ return "code";
117
+ }
94
118
  if (extension === ".txt" || extension === ".log") {
95
119
  return "text";
96
120
  }
97
121
 
98
122
  const trimmed = optionalString(content);
123
+ if (/^(?:<\?xml[\s\S]*?\?>\s*)?<svg[\s>]/i.test(trimmed)) {
124
+ return "svg";
125
+ }
126
+ if (looksLikeMermaid(trimmed)) {
127
+ return "mermaid";
128
+ }
129
+ if (looksLikeReact(trimmed)) {
130
+ return "react";
131
+ }
99
132
  if (/^<!doctype html/i.test(trimmed) || /^<html[\s>]/i.test(trimmed)) {
100
133
  return "html";
101
134
  }
@@ -131,22 +164,50 @@ function decodeObjectPayload(value, agent) {
131
164
  return decodeBundlePayload(artifactObject, agent);
132
165
  }
133
166
 
167
+ const codexContinuation = decodeCodexContinuationPayload(artifactObject, agent);
168
+ if (codexContinuation) {
169
+ return codexContinuation;
170
+ }
171
+
134
172
  if (typeof artifactObject.content === "string" && !Array.isArray(artifactObject.content)) {
173
+ const sourceContentType = artifactObject.contentType ||
174
+ artifactObject.content_type ||
175
+ artifactObject.mimeType ||
176
+ artifactObject.mime_type ||
177
+ artifactObject.type;
135
178
  return {
136
179
  content: artifactObject.content,
137
- format: safeFormat(artifactObject.format || artifactObject.type || artifactObject.mimeType),
138
- contentType: artifactObject.contentType || artifactObject.mimeType,
180
+ format: safeFormat(artifactObject.format || sourceContentType),
181
+ contentType: sourceContentType,
139
182
  title: artifactObject.title || artifactObject.name,
140
183
  sourceAgent: artifactObject.sourceAgent || artifactObject.source_agent || artifactObject.agent,
141
184
  artifactType: artifactObject.artifactType || artifactObject.artifact_type,
142
185
  tags: artifactObject.tags,
143
186
  metadata: {
144
187
  originalPayloadShape: "content",
145
- originalId: artifactObject.id
188
+ originalId: artifactObject.id,
189
+ originalContentType: sourceContentType,
190
+ language: artifactObject.language
146
191
  }
147
192
  };
148
193
  }
149
194
 
195
+ if (typeof artifactObject.code === "string") {
196
+ return objectContent(artifactObject, "code", artifactObject.code, "code");
197
+ }
198
+
199
+ if (typeof artifactObject.svg === "string") {
200
+ return objectContent(artifactObject, "svg", artifactObject.svg, "svg");
201
+ }
202
+
203
+ if (typeof artifactObject.mermaid === "string") {
204
+ return objectContent(artifactObject, "mermaid", artifactObject.mermaid, "mermaid");
205
+ }
206
+
207
+ if (typeof artifactObject.react === "string" || typeof artifactObject.jsx === "string") {
208
+ return objectContent(artifactObject, "react", artifactObject.react || artifactObject.jsx, "react");
209
+ }
210
+
150
211
  if (typeof artifactObject.html === "string") {
151
212
  return objectContent(artifactObject, "html", artifactObject.html, "html");
152
213
  }
@@ -160,9 +221,10 @@ function decodeObjectPayload(value, agent) {
160
221
  }
161
222
 
162
223
  if (typeof artifactObject.returnDisplay === "string") {
224
+ const content = stripMarkdownCodeFence(artifactObject.returnDisplay);
163
225
  return {
164
- content: stripMarkdownCodeFence(artifactObject.returnDisplay),
165
- format: detectFormat({ content: artifactObject.returnDisplay }) === "json" ? "json" : "markdown",
226
+ content,
227
+ format: detectFormat({ content }) === "json" ? "json" : "markdown",
166
228
  title: artifactObject.title || artifactObject.name || "Gemini artifact",
167
229
  sourceAgent: agent === "auto" ? "gemini" : agent,
168
230
  artifactType: "document",
@@ -232,16 +294,20 @@ function decodeObjectPayload(value, agent) {
232
294
  }
233
295
 
234
296
  function objectContent(object, shape, content, format) {
297
+ const sourceContentType = object.contentType || object.content_type || object.mimeType || object.mime_type || object.type;
235
298
  return {
236
299
  content,
237
300
  format,
301
+ contentType: sourceContentType,
238
302
  title: object.title || object.name,
239
303
  sourceAgent: object.sourceAgent || object.source_agent || object.agent,
240
304
  artifactType: object.artifactType || object.artifact_type,
241
305
  tags: object.tags,
242
306
  metadata: {
243
307
  originalPayloadShape: shape,
244
- originalId: object.id
308
+ originalId: object.id,
309
+ originalContentType: sourceContentType,
310
+ language: object.language
245
311
  }
246
312
  };
247
313
  }
@@ -249,6 +315,7 @@ function objectContent(object, shape, content, format) {
249
315
  function decodeBundlePayload(object, agent) {
250
316
  const bundle = object.bundle && typeof object.bundle === "object" ? object.bundle : object;
251
317
  const files = Array.isArray(bundle.files) ? bundle.files : [];
318
+ const codexMetadata = collectCodexContinuationMetadata({ agent }, bundle, object);
252
319
  const normalizedFiles = files.map((file, index) => {
253
320
  const content = typeof file.content === "string" ? file.content : "";
254
321
  return {
@@ -268,7 +335,8 @@ function decodeBundlePayload(object, agent) {
268
335
  schemaVersion: 1,
269
336
  artifactType: "bundle",
270
337
  title: object.title || bundle.title || "Artifact bundle",
271
- files: normalizedFiles
338
+ files: normalizedFiles,
339
+ ...bundleDocumentMetadata(codexMetadata)
272
340
  }, null, 2),
273
341
  format: "json",
274
342
  contentType: "application/vnd.artifacty.bundle+json; charset=utf-8",
@@ -279,11 +347,320 @@ function decodeBundlePayload(object, agent) {
279
347
  metadata: {
280
348
  originalPayloadShape: "artifact-bundle",
281
349
  fileCount: normalizedFiles.length,
282
- bundlePolicy: "text-files-stored-inline-in-bundle-json"
350
+ bundlePolicy: "text-files-stored-inline-in-bundle-json",
351
+ ...codexMetadata
283
352
  }
284
353
  };
285
354
  }
286
355
 
356
+ function decodeCodexContinuationPayload(object, agent) {
357
+ if (!isCodexPayload(object, agent) || hasDirectContentField(object)) {
358
+ return null;
359
+ }
360
+
361
+ const artifactType = inferCodexContinuationType(object);
362
+ if (!artifactType) {
363
+ return null;
364
+ }
365
+
366
+ const metadata = collectCodexContinuationMetadata({ agent }, object);
367
+ return {
368
+ content: renderCodexContinuationMarkdown(object, artifactType, metadata),
369
+ format: "markdown",
370
+ contentType: "text/markdown; charset=utf-8",
371
+ title: object.title || object.name || titleForCodexType(artifactType),
372
+ sourceAgent: "codex",
373
+ artifactType,
374
+ tags: uniqueStrings([
375
+ artifactType,
376
+ "codex",
377
+ ...normalizeTags(object.tags)
378
+ ]),
379
+ metadata: {
380
+ originalPayloadShape: "codex-continuation",
381
+ continuationKind: artifactType,
382
+ ...metadata
383
+ }
384
+ };
385
+ }
386
+
387
+ function hasDirectContentField(object) {
388
+ return typeof object.content === "string" ||
389
+ Array.isArray(object.content) ||
390
+ typeof object.markdown === "string" ||
391
+ typeof object.text === "string" ||
392
+ typeof object.html === "string" ||
393
+ typeof object.code === "string" ||
394
+ typeof object.svg === "string" ||
395
+ typeof object.mermaid === "string" ||
396
+ typeof object.react === "string" ||
397
+ typeof object.jsx === "string";
398
+ }
399
+
400
+ function isCodexPayload(object, agent) {
401
+ const explicit = normalizeAgent(
402
+ object.sourceAgent ||
403
+ object.source_agent ||
404
+ object.agent ||
405
+ object.producer ||
406
+ object.createdBy ||
407
+ object.created_by ||
408
+ agent
409
+ );
410
+ return explicit === "codex";
411
+ }
412
+
413
+ function inferCodexContinuationType(object) {
414
+ const explicit = optionalString(object.artifactType || object.artifact_type || object.kind || object.category || object.purpose || object.type).toLowerCase();
415
+ if (explicit.includes("review")) {
416
+ return "code-review";
417
+ }
418
+ if (explicit.includes("test") || explicit.includes("verification") || explicit.includes("report")) {
419
+ return "test-report";
420
+ }
421
+ if (explicit.includes("diff") || explicit.includes("patch")) {
422
+ return "diff-walkthrough";
423
+ }
424
+ if (explicit.includes("handoff") || explicit.includes("continuation")) {
425
+ return "handoff";
426
+ }
427
+
428
+ if (hasNonEmptyArray(object.findings) || hasNonEmptyArray(object.review?.findings)) {
429
+ return "code-review";
430
+ }
431
+ if (object.verification || hasNonEmptyArray(object.tests) || hasNonEmptyArray(object.testResults) || hasNonEmptyArray(object.test_results) || optionalString(object.testStatus)) {
432
+ return "test-report";
433
+ }
434
+ if (optionalString(object.diff || object.patch)) {
435
+ return "diff-walkthrough";
436
+ }
437
+ if (
438
+ optionalString(object.goal || object.currentState || object.current_state || object.summary) ||
439
+ hasNonEmptyArray(object.decisions) ||
440
+ hasNonEmptyArray(object.blockers) ||
441
+ hasNonEmptyArray(object.nextSteps) ||
442
+ hasNonEmptyArray(object.next_steps) ||
443
+ hasNonEmptyArray(object.changedFiles) ||
444
+ hasNonEmptyArray(object.changed_files)
445
+ ) {
446
+ return "handoff";
447
+ }
448
+
449
+ return null;
450
+ }
451
+
452
+ function collectCodexContinuationMetadata(...sources) {
453
+ const objects = sources.filter((item) => item && typeof item === "object");
454
+ if (!objects.some((item) => isCodexPayload(item, "auto"))) {
455
+ return {};
456
+ }
457
+ const source = Object.assign({}, ...objects);
458
+ const continuation = pruneEmpty({
459
+ summary: optionalString(source.summary),
460
+ goal: optionalString(source.goal),
461
+ currentState: optionalString(source.currentState || source.current_state),
462
+ changedFiles: normalizeChangedFiles(firstPresent(source.changedFiles, source.changed_files, source.filesChanged, source.files_changed)),
463
+ commands: normalizeEvents(firstPresent(source.commands, source.commandsRun, source.commands_run, source.verification?.commands)),
464
+ tests: normalizeEvents(firstPresent(source.tests, source.testResults, source.test_results, source.verification?.tests)),
465
+ testStatus: optionalString(source.testStatus || source.test_status || source.verification?.status || source.status),
466
+ blockers: normalizeStringList(firstPresent(source.blockers, source.blockedBy, source.blocked_by)),
467
+ nextSteps: normalizeStringList(firstPresent(source.nextSteps, source.next_steps)),
468
+ decisions: normalizeStringList(source.decisions),
469
+ findings: normalizeFindings(firstPresent(source.findings, source.review?.findings)),
470
+ diff: optionalString(source.diff || source.patch),
471
+ residualRisk: optionalString(source.residualRisk || source.residual_risk)
472
+ });
473
+
474
+ return Object.keys(continuation).length > 0
475
+ ? { codexContinuation: continuation }
476
+ : {};
477
+ }
478
+
479
+ function bundleDocumentMetadata(metadata) {
480
+ return metadata.codexContinuation
481
+ ? { codexContinuation: metadata.codexContinuation }
482
+ : {};
483
+ }
484
+
485
+ function renderCodexContinuationMarkdown(object, artifactType, metadata) {
486
+ const continuation = metadata.codexContinuation || {};
487
+ const title = object.title || object.name || titleForCodexType(artifactType);
488
+ const lines = [`# ${title}`, "", `Source: Codex`, `Artifact type: ${artifactType}`, ""];
489
+
490
+ addTextSection(lines, "Summary", continuation.summary);
491
+ addTextSection(lines, "Goal", continuation.goal);
492
+ addTextSection(lines, "Current State", continuation.currentState);
493
+ addRecordSection(lines, "Changed Files", continuation.changedFiles, formatChangedFile);
494
+ addRecordSection(lines, "Commands", continuation.commands, formatEvent);
495
+ addRecordSection(lines, "Tests", continuation.tests, formatEvent);
496
+ addTextSection(lines, "Test Status", continuation.testStatus);
497
+ addRecordSection(lines, "Findings", continuation.findings, formatFinding);
498
+ addListSection(lines, "Decisions", continuation.decisions);
499
+ addListSection(lines, "Blockers", continuation.blockers);
500
+ addListSection(lines, "Next Steps", continuation.nextSteps);
501
+ addTextSection(lines, "Residual Risk", continuation.residualRisk);
502
+
503
+ if (continuation.diff) {
504
+ lines.push("## Diff", "", "```diff", continuation.diff, "```", "");
505
+ }
506
+
507
+ return lines.join("\n").replace(/\n{3,}/g, "\n\n").trimEnd();
508
+ }
509
+
510
+ function titleForCodexType(artifactType) {
511
+ if (artifactType === "code-review") {
512
+ return "Codex Code Review";
513
+ }
514
+ if (artifactType === "test-report") {
515
+ return "Codex Verification Report";
516
+ }
517
+ if (artifactType === "diff-walkthrough") {
518
+ return "Codex Diff Walkthrough";
519
+ }
520
+ return "Codex Handoff";
521
+ }
522
+
523
+ function addTextSection(lines, title, value) {
524
+ if (!value) {
525
+ return;
526
+ }
527
+ lines.push(`## ${title}`, "", value, "");
528
+ }
529
+
530
+ function addListSection(lines, title, values) {
531
+ if (!values?.length) {
532
+ return;
533
+ }
534
+ lines.push(`## ${title}`, "", ...values.map((value) => `- ${value}`), "");
535
+ }
536
+
537
+ function addRecordSection(lines, title, values, formatter) {
538
+ if (!values?.length) {
539
+ return;
540
+ }
541
+ lines.push(`## ${title}`, "", ...values.map((value) => `- ${formatter(value)}`), "");
542
+ }
543
+
544
+ function formatChangedFile(file) {
545
+ return [
546
+ file.path ? `\`${file.path}\`` : "",
547
+ file.status,
548
+ file.summary
549
+ ].filter(Boolean).join(" — ");
550
+ }
551
+
552
+ function formatEvent(event) {
553
+ return [
554
+ event.command ? `\`${event.command}\`` : event.name || event.title || "",
555
+ event.status,
556
+ event.summary
557
+ ].filter(Boolean).join(" — ");
558
+ }
559
+
560
+ function formatFinding(finding) {
561
+ return [
562
+ finding.severity,
563
+ finding.file ? `\`${finding.file}${finding.line ? `:${finding.line}` : ""}\`` : "",
564
+ finding.title || finding.message || finding.summary
565
+ ].filter(Boolean).join(" — ");
566
+ }
567
+
568
+ function normalizeChangedFiles(value) {
569
+ return normalizeRecordList(value).map((item) => pruneEmpty({
570
+ path: typeof item === "string" ? optionalString(item) : optionalString(item.path || item.file || item.name),
571
+ status: optionalString(item.status || item.changeType || item.change_type),
572
+ summary: optionalString(item.summary || item.description),
573
+ additions: integerOrUndefined(item.additions),
574
+ deletions: integerOrUndefined(item.deletions)
575
+ })).filter((item) => Object.keys(item).length > 0);
576
+ }
577
+
578
+ function normalizeEvents(value) {
579
+ return normalizeRecordList(value).map((item) => {
580
+ if (typeof item === "string") {
581
+ return { command: item };
582
+ }
583
+ return pruneEmpty({
584
+ command: optionalString(item.command || item.cmd || item.name),
585
+ title: optionalString(item.title),
586
+ status: optionalString(item.status || item.result),
587
+ summary: optionalString(item.summary || item.output || item.message)
588
+ });
589
+ }).filter((item) => Object.keys(item).length > 0);
590
+ }
591
+
592
+ function normalizeFindings(value) {
593
+ return normalizeRecordList(value).map((item) => pruneEmpty({
594
+ severity: optionalString(item.severity || item.priority),
595
+ file: optionalString(item.file || item.path),
596
+ line: integerOrUndefined(item.line || item.start),
597
+ title: optionalString(item.title),
598
+ message: typeof item === "string" ? optionalString(item) : optionalString(item.message || item.body),
599
+ summary: optionalString(item.summary)
600
+ })).filter((item) => Object.keys(item).length > 0);
601
+ }
602
+
603
+ function normalizeStringList(value) {
604
+ if (value === undefined || value === null) {
605
+ return [];
606
+ }
607
+ if (Array.isArray(value)) {
608
+ return value.map((item) => {
609
+ if (typeof item === "string") {
610
+ return optionalString(item);
611
+ }
612
+ if (item && typeof item === "object") {
613
+ return optionalString(item.summary || item.title || item.message || JSON.stringify(item));
614
+ }
615
+ return optionalString(item);
616
+ }).filter(Boolean);
617
+ }
618
+ return [optionalString(value)].filter(Boolean);
619
+ }
620
+
621
+ function normalizeRecordList(value) {
622
+ if (value === undefined || value === null) {
623
+ return [];
624
+ }
625
+ if (Array.isArray(value)) {
626
+ return value;
627
+ }
628
+ if (typeof value === "object") {
629
+ return [value];
630
+ }
631
+ return [String(value)];
632
+ }
633
+
634
+ function firstPresent(...values) {
635
+ return values.find((value) => value !== undefined && value !== null);
636
+ }
637
+
638
+ function hasNonEmptyArray(value) {
639
+ return Array.isArray(value) && value.length > 0;
640
+ }
641
+
642
+ function integerOrUndefined(value) {
643
+ if (value === undefined || value === null || value === "") {
644
+ return undefined;
645
+ }
646
+ const parsed = Number(value);
647
+ return Number.isInteger(parsed) ? parsed : undefined;
648
+ }
649
+
650
+ function pruneEmpty(value) {
651
+ return Object.fromEntries(
652
+ Object.entries(value).filter(([, item]) => {
653
+ if (item === undefined || item === null || item === "") {
654
+ return false;
655
+ }
656
+ if (Array.isArray(item) && item.length === 0) {
657
+ return false;
658
+ }
659
+ return true;
660
+ })
661
+ );
662
+ }
663
+
287
664
  function detectAgent(parsed, fileName) {
288
665
  if (parsed.kind === "json" && parsed.value && typeof parsed.value === "object") {
289
666
  const value = parsed.value.artifact || parsed.value;
@@ -317,6 +694,18 @@ function safeFormat(value) {
317
694
  if (normalized === "md") {
318
695
  return "markdown";
319
696
  }
697
+ if (normalized === "jsx" || normalized === "tsx" || normalized.includes("vnd.ant.react")) {
698
+ return "react";
699
+ }
700
+ if (normalized === "svg" || normalized.includes("svg")) {
701
+ return "svg";
702
+ }
703
+ if (normalized === "mmd" || normalized === "mermaid" || normalized.includes("vnd.ant.mermaid")) {
704
+ return "mermaid";
705
+ }
706
+ if (normalized === "code" || normalized.includes("vnd.ant.code") || normalized.includes("source-code")) {
707
+ return "code";
708
+ }
320
709
  if (normalized === "html" || normalized.includes("html")) {
321
710
  return "html";
322
711
  }
@@ -477,6 +866,15 @@ function inferArtifactType({ format, fileName, metadata }) {
477
866
  if (format === "html") {
478
867
  return "html-page";
479
868
  }
869
+ if (format === "svg" || format === "mermaid") {
870
+ return "diagram";
871
+ }
872
+ if (format === "react") {
873
+ return "component";
874
+ }
875
+ if (format === "code") {
876
+ return "snippet";
877
+ }
480
878
  const lowerName = optionalString(fileName).toLowerCase();
481
879
  if (lowerName.includes("handoff")) {
482
880
  return "handoff";
@@ -495,7 +893,7 @@ function normalizeArtifactType(value) {
495
893
  if (!normalized) {
496
894
  return "document";
497
895
  }
498
- const allowed = new Set(["document", "html-page", "handoff", "code-review", "test-report", "dashboard", "design-option", "diff-walkthrough", "bundle", "asset", "unknown"]);
896
+ const allowed = new Set(ARTIFACT_TYPES);
499
897
  return allowed.has(normalized) ? normalized : "unknown";
500
898
  }
501
899
 
@@ -530,6 +928,46 @@ function looksLikeJson(value) {
530
928
  (trimmed.startsWith("[") && trimmed.endsWith("]"));
531
929
  }
532
930
 
931
+ function looksLikeMermaid(value) {
932
+ const firstMeaningfulLine = optionalString(value)
933
+ .split(/\r?\n/)
934
+ .map((line) => line.trim())
935
+ .find((line) => line && !line.startsWith("%%"));
936
+ return /^(graph|flowchart|sequenceDiagram|classDiagram|stateDiagram|stateDiagram-v2|erDiagram|gantt|pie|mindmap|journey)\b/.test(firstMeaningfulLine || "");
937
+ }
938
+
939
+ function looksLikeReact(value) {
940
+ const text = optionalString(value);
941
+ return /\b(import\s+React|from\s+['"]react['"]|export\s+default\s+function|export\s+default\s+\()/m.test(text) ||
942
+ /<[A-Z][A-Za-z0-9]*[\s/>]/.test(text);
943
+ }
944
+
945
+ function isCodeExtension(extension) {
946
+ return new Set([
947
+ ".js",
948
+ ".ts",
949
+ ".py",
950
+ ".rb",
951
+ ".go",
952
+ ".rs",
953
+ ".java",
954
+ ".c",
955
+ ".cc",
956
+ ".cpp",
957
+ ".cs",
958
+ ".php",
959
+ ".swift",
960
+ ".kt",
961
+ ".sh",
962
+ ".bash",
963
+ ".zsh",
964
+ ".sql",
965
+ ".toml",
966
+ ".yaml",
967
+ ".yml"
968
+ ]).has(extension);
969
+ }
970
+
533
971
  function stripMarkdownCodeFence(value) {
534
972
  const trimmed = value.trim();
535
973
  const match = /^```(?:\w+)?\n([\s\S]*?)\n```$/.exec(trimmed);
@@ -1,6 +1,7 @@
1
1
  import path from "node:path";
2
2
 
3
3
  export const EDITOR_CLIENT_PATH = "/assets/editor.js";
4
+ export const VIEWER_CLIENT_PATH = "/assets/viewer.js";
4
5
 
5
6
  export const EDITOR_VENDOR_PACKAGES = [
6
7
  "@codemirror/autocomplete",
@@ -24,36 +25,78 @@ export const EDITOR_VENDOR_PACKAGES = [
24
25
  "@lezer/lr",
25
26
  "@lezer/markdown",
26
27
  "@marijn/find-cluster-break",
28
+ "@babel/standalone",
27
29
  "codemirror",
28
30
  "crelt",
31
+ "mermaid",
32
+ "react",
33
+ "react-dom",
29
34
  "style-mod",
30
35
  "w3c-keyname"
31
36
  ];
32
37
 
33
38
  const EDITOR_VENDOR_SET = new Set(EDITOR_VENDOR_PACKAGES);
34
39
  const EDITOR_VENDOR_ENTRY_OVERRIDES = {
40
+ "@babel/standalone": "babel.min.js",
35
41
  "@marijn/find-cluster-break": path.join("src", "index.js"),
36
42
  crelt: "index.js",
43
+ mermaid: path.join("dist", "mermaid.esm.min.mjs"),
44
+ react: path.join("umd", "react.production.min.js"),
45
+ "react-dom": path.join("umd", "react-dom.production.min.js"),
37
46
  "style-mod": path.join("src", "style-mod.js"),
38
47
  "w3c-keyname": "index.js"
39
48
  };
40
49
 
50
+ const EDITOR_VENDOR_IMPORT_OVERRIDES = {
51
+ mermaid: "/vendor/npm/mermaid/dist/mermaid.esm.min.mjs"
52
+ };
53
+
41
54
  export function editorImportMapJson() {
42
55
  return JSON.stringify({
43
56
  imports: Object.fromEntries(
44
- EDITOR_VENDOR_PACKAGES.map((packageName) => [packageName, `/vendor/npm/${packageName}`])
57
+ EDITOR_VENDOR_PACKAGES.map((packageName) => [
58
+ packageName,
59
+ EDITOR_VENDOR_IMPORT_OVERRIDES[packageName] || `/vendor/npm/${packageName}`
60
+ ])
45
61
  )
46
62
  });
47
63
  }
48
64
 
49
- export function editorVendorPath(packageName, packageRoot) {
65
+ export function editorVendorPath(specifier, packageRoot) {
66
+ const { packageName, subpath } = splitPackageSpecifier(specifier);
50
67
  if (!EDITOR_VENDOR_SET.has(packageName)) {
51
68
  return null;
52
69
  }
70
+ if (subpath) {
71
+ const packageDir = path.join(packageRoot, "node_modules", packageName);
72
+ const candidate = path.normalize(path.join(packageDir, subpath));
73
+ if (!candidate.startsWith(packageDir + path.sep)) {
74
+ return null;
75
+ }
76
+ return candidate;
77
+ }
53
78
  const entry = EDITOR_VENDOR_ENTRY_OVERRIDES[packageName] || path.join("dist", "index.js");
54
79
  return path.join(packageRoot, "node_modules", packageName, entry);
55
80
  }
56
81
 
82
+ function splitPackageSpecifier(specifier) {
83
+ const parts = String(specifier || "").split("/").filter(Boolean);
84
+ if (parts[0]?.startsWith("@")) {
85
+ return {
86
+ packageName: parts.slice(0, 2).join("/"),
87
+ subpath: parts.slice(2).join("/")
88
+ };
89
+ }
90
+ return {
91
+ packageName: parts[0] || "",
92
+ subpath: parts.slice(1).join("/")
93
+ };
94
+ }
95
+
57
96
  export function editorClientFilePath(packageRoot) {
58
97
  return path.join(packageRoot, "src", "client", "editor.js");
59
98
  }
99
+
100
+ export function viewerClientFilePath(packageRoot) {
101
+ return path.join(packageRoot, "src", "client", "viewer.js");
102
+ }