yumiamd 0.1.6 → 0.1.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.
package/dist/bin.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runCli
4
- } from "./chunk-FHM6V4JH.js";
4
+ } from "./chunk-WUJKHJJU.js";
5
5
 
6
6
  // src/bin.ts
7
7
  var result = await runCli(process.argv);
@@ -47,6 +47,17 @@ function createCard(elements, title, variant) {
47
47
  ...variant ? { variant } : {}
48
48
  };
49
49
  }
50
+ function createMetric(value, label, variant, description, unit, change) {
51
+ return {
52
+ type: "metric",
53
+ value,
54
+ label,
55
+ ...variant ? { variant } : {},
56
+ ...description ? { description } : {},
57
+ ...unit ? { unit } : {},
58
+ ...change ? { change } : {}
59
+ };
60
+ }
50
61
  function createCode(code, language, highlightLines) {
51
62
  return {
52
63
  type: "code",
@@ -111,13 +122,14 @@ var DefaultYumiaParser = class {
111
122
  }
112
123
  const normalized = source.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
113
124
  const { metadata, content, lineOffset } = this.extractFrontmatter(normalized);
125
+ if (!content.trim()) {
126
+ return createPresentation(metadata, []);
127
+ }
114
128
  const slideChunks = this.splitSlides(content, lineOffset);
115
129
  const slides = [];
116
130
  for (const chunk of slideChunks) {
117
131
  const slide = this.parseSlide(chunk.content, chunk.startLine);
118
- if (slide.elements.length > 0 || slide.notes !== void 0 || slide.background !== void 0) {
119
- slides.push(slide);
120
- }
132
+ slides.push(slide);
121
133
  }
122
134
  const presentation = createPresentation(metadata, slides);
123
135
  if (this.diagnostics.length > 0) {
@@ -346,6 +358,28 @@ var DefaultYumiaParser = class {
346
358
  i++;
347
359
  continue;
348
360
  }
361
+ if (directiveHeader.startsWith("metric")) {
362
+ const valueMatch = directiveHeader.match(/value=['"](.*?)['"]/);
363
+ const labelMatch = directiveHeader.match(/label=['"](.*?)['"]/);
364
+ const variantMatch = directiveHeader.match(/variant=['"](.*?)['"]/);
365
+ const descMatch = directiveHeader.match(/description=['"](.*?)['"]/);
366
+ const unitMatch = directiveHeader.match(/unit=['"](.*?)['"]/);
367
+ const changeMatch = directiveHeader.match(/change=['"](.*?)['"]/);
368
+ const value = valueMatch ? valueMatch[1] : "0";
369
+ const label = labelMatch ? labelMatch[1] : "";
370
+ const variant = variantMatch ? variantMatch[1] : void 0;
371
+ const desc = descMatch ? descMatch[1] : void 0;
372
+ const unit = unitMatch ? unitMatch[1] : void 0;
373
+ const change = changeMatch ? changeMatch[1] : void 0;
374
+ const el = createMetric(value, label, variant, desc, unit, change);
375
+ el.loc = {
376
+ start: { line: currentLineNum, column: 1 },
377
+ end: { line: currentLineNum, column: rawLine.length + 1 }
378
+ };
379
+ elements.push(el);
380
+ i++;
381
+ continue;
382
+ }
349
383
  const [directiveName, ...args] = directiveHeader.split(" ");
350
384
  const directiveArg = args.join(" ").trim();
351
385
  const blockLines = [];
@@ -354,7 +388,8 @@ var DefaultYumiaParser = class {
354
388
  let closed = false;
355
389
  while (i < lines.length) {
356
390
  const innerLine = lines[i]?.trim() ?? "";
357
- if (innerLine.startsWith(":::") && innerLine.length > 3) {
391
+ const isSingleLine = innerLine.startsWith(":::metric") || innerLine.startsWith(":::layout");
392
+ if (innerLine.startsWith(":::") && innerLine.length > 3 && !isSingleLine) {
358
393
  nestedCount++;
359
394
  } else if (innerLine === ":::") {
360
395
  nestedCount--;
@@ -382,9 +417,17 @@ var DefaultYumiaParser = class {
382
417
  if (directiveName === "notes") {
383
418
  notes = blockLines.map((l) => l.trim()).filter(Boolean).join("\n");
384
419
  } else if (directiveName === "card") {
385
- const title = directiveArg.replace(/^['"](.*)['"]$/, "$1") || void 0;
420
+ let cardTitle;
421
+ let cardVariant;
422
+ const variantMatch = directiveArg.match(/variant=['"](.*?)['"]/);
423
+ if (variantMatch) {
424
+ cardVariant = variantMatch[1];
425
+ cardTitle = directiveArg.replace(/variant=['"].*?['"]/, "").trim().replace(/^['"](.*)['"]$/, "$1") || void 0;
426
+ } else {
427
+ cardTitle = directiveArg.replace(/^['"](.*)['"]$/, "$1") || void 0;
428
+ }
386
429
  const { elements: cardElements } = this.parseLines(blockLines, blockBaseLine);
387
- const el = createCard(cardElements, title);
430
+ const el = createCard(cardElements, cardTitle, cardVariant);
388
431
  el.loc = {
389
432
  start: { line: directiveStartLine, column: 1 },
390
433
  end: { line: baseLine + i - 1, column: 1 }
@@ -530,7 +573,8 @@ var DefaultYumiaParser = class {
530
573
  let nestedCount = 1;
531
574
  while (i < lines.length) {
532
575
  const innerLine = lines[i]?.trim() ?? "";
533
- if (innerLine.startsWith(":::") && innerLine.length > 3) {
576
+ const isSingleLine = innerLine.startsWith(":::metric") || innerLine.startsWith(":::layout");
577
+ if (innerLine.startsWith(":::") && innerLine.length > 3 && !isSingleLine) {
534
578
  nestedCount++;
535
579
  } else if (innerLine === ":::") {
536
580
  nestedCount--;
@@ -574,7 +618,7 @@ var VIEWPORT_PRESETS = {
574
618
  var DefaultLayoutEngine = class {
575
619
  computeSlide(slide, viewport = DEFAULT_VIEWPORT, options = {}) {
576
620
  const padding = options.padding ?? 64;
577
- const gap = options.gap ?? 24;
621
+ const gap = options.gap ?? 32;
578
622
  const availableWidth = Math.max(100, viewport.width - padding * 2);
579
623
  const availableHeight = Math.max(100, viewport.height - padding * 2);
580
624
  if (slide.elements.length === 0) {
@@ -626,7 +670,7 @@ var DefaultLayoutEngine = class {
626
670
  return { element, bounds: { x, y, width, height } };
627
671
  }
628
672
  case "list": {
629
- const height = this.estimateListHeight(element);
673
+ const height = this.estimateListHeight(element, width);
630
674
  return { element, bounds: { x, y, width, height } };
631
675
  }
632
676
  case "code": {
@@ -645,6 +689,10 @@ var DefaultLayoutEngine = class {
645
689
  const height = this.estimateImageHeight(element);
646
690
  return { element, bounds: { x, y, width, height } };
647
691
  }
692
+ case "metric": {
693
+ const height = this.estimateMetricHeight(element);
694
+ return { element, bounds: { x, y, width, height } };
695
+ }
648
696
  case "card": {
649
697
  return this.layoutCard(element, x, y, width, gap);
650
698
  }
@@ -658,13 +706,13 @@ var DefaultLayoutEngine = class {
658
706
  }
659
707
  }
660
708
  layoutCard(element, x, y, width, gap) {
661
- const cardPadding = 28;
709
+ const cardPadding = 32;
662
710
  const innerWidth = Math.max(10, width - cardPadding * 2);
663
- const titleHeight = element.title ? 56 : 0;
711
+ const titleHeight = element.title ? 60 : 0;
664
712
  const innerStartY = y + cardPadding + titleHeight;
665
- const { nodes: children, totalHeight: innerHeight } = this.layoutElementList(element.elements, x + cardPadding, innerStartY, innerWidth, gap / 1.5);
666
- const cardHeight = titleHeight + innerHeight + cardPadding * 2;
667
- const bounds = { x, y, width, height: Math.max(100, cardHeight) };
713
+ const { nodes: children, totalHeight: innerHeight } = this.layoutElementList(element.elements, x + cardPadding, innerStartY, innerWidth, gap);
714
+ const cardHeight = titleHeight + innerHeight + cardPadding * 2 + 12;
715
+ const bounds = { x, y, width, height: Math.max(120, cardHeight) };
668
716
  return {
669
717
  element,
670
718
  bounds,
@@ -727,24 +775,31 @@ var DefaultLayoutEngine = class {
727
775
  estimateParagraphHeight(paragraph, width) {
728
776
  const charsPerLine = Math.max(20, Math.floor(width / 14));
729
777
  const lines = Math.ceil(paragraph.text.length / charsPerLine) || 1;
730
- return Math.max(40, lines * 32);
778
+ return Math.max(40, lines * 36);
731
779
  }
732
- estimateListHeight(list) {
733
- return Math.max(40, list.items.length * 40);
780
+ estimateListHeight(list, width = 800) {
781
+ const charsPerLine = Math.max(15, Math.floor(width / 13));
782
+ let totalHeight = 0;
783
+ for (const item of list.items) {
784
+ const cleanLen = item.text.replace(/\*\*/g, "").replace(/\*/g, "").length;
785
+ const lines = Math.ceil(cleanLen / charsPerLine) || 1;
786
+ totalHeight += lines * 34 + 18;
787
+ }
788
+ return Math.max(40, totalHeight);
734
789
  }
735
790
  estimateCodeHeight(code) {
736
791
  const lines = code.code.split("\n").length || 1;
737
- return lines * 26 + 32;
792
+ return lines * 28 + 48;
738
793
  }
739
794
  estimateQuoteHeight(quote, width) {
740
795
  const charsPerLine = Math.max(20, Math.floor(width / 14));
741
796
  const lines = Math.ceil(quote.text.length / charsPerLine) || 1;
742
- return lines * 32 + 24;
797
+ return lines * 34 + 32;
743
798
  }
744
799
  estimateTableHeight(table) {
745
- const headerHeight = table.headers && table.headers.length > 0 ? 50 : 0;
746
- const rowsHeight = (table.rows ? table.rows.length : 0) * 42;
747
- return Math.max(60, headerHeight + rowsHeight + 16);
800
+ const headerHeight = table.headers && table.headers.length > 0 ? 56 : 0;
801
+ const rowsHeight = (table.rows ? table.rows.length : 0) * 44;
802
+ return Math.max(60, headerHeight + rowsHeight + 20);
748
803
  }
749
804
  estimateImageHeight(image) {
750
805
  if (typeof image.height === "number") {
@@ -752,6 +807,9 @@ var DefaultLayoutEngine = class {
752
807
  }
753
808
  return 320;
754
809
  }
810
+ estimateMetricHeight(metric) {
811
+ return metric.change ? 140 : 120;
812
+ }
755
813
  };
756
814
 
757
815
  // ../theme/dist/default-theme.js
@@ -766,7 +824,11 @@ var defaultTheme = {
766
824
  text: "#0f172a",
767
825
  muted: "#64748b",
768
826
  accent: "#06b6d4",
769
- border: "#e2e8f0"
827
+ border: "#e2e8f0",
828
+ success: "#10b981",
829
+ warning: "#f59e0b",
830
+ danger: "#ef4444",
831
+ info: "#3b82f6"
770
832
  },
771
833
  typography: {
772
834
  headingFont: "Inter, system-ui, -apple-system, sans-serif",
@@ -823,29 +885,523 @@ var defaultTheme = {
823
885
  }
824
886
  }
825
887
  };
826
- function createTheme(overrides) {
888
+ var cyberpunkTheme = {
889
+ name: "cyberpunk",
890
+ description: "High-energy dark cyberpunk theme with neon pink and cyan accents",
891
+ colors: {
892
+ primary: "#FF2E88",
893
+ secondary: "#00F0FF",
894
+ background: "#0B0B12",
895
+ surface: "#151522",
896
+ text: "#FFFFFF",
897
+ muted: "#A7A7B5",
898
+ accent: "#7B61FF",
899
+ border: "#2E2E48",
900
+ success: "#00FF9F",
901
+ warning: "#FFE600",
902
+ danger: "#FF0055",
903
+ info: "#00F0FF"
904
+ },
905
+ typography: {
906
+ headingFont: "Outfit, Inter, sans-serif",
907
+ bodyFont: "Inter, sans-serif",
908
+ codeFont: "JetBrains Mono, monospace",
909
+ sizes: {
910
+ h1: 46,
911
+ h2: 38,
912
+ h3: 28,
913
+ h4: 22,
914
+ body: 18,
915
+ small: 14,
916
+ code: 16
917
+ }
918
+ },
919
+ spacing: {
920
+ unit: 8,
921
+ slidePadding: 48,
922
+ elementGap: 28
923
+ },
924
+ radius: {
925
+ default: 12,
926
+ sm: 6,
927
+ md: 12,
928
+ lg: 20,
929
+ full: 9999
930
+ },
931
+ components: {
932
+ card: {
933
+ background: "#151522",
934
+ borderColor: "#FF2E88",
935
+ borderRadius: 16,
936
+ padding: 28
937
+ },
938
+ code: {
939
+ background: "#07070D",
940
+ textColor: "#00F0FF",
941
+ borderRadius: 8
942
+ }
943
+ }
944
+ };
945
+ var minimalTheme = {
946
+ name: "minimal",
947
+ description: "Monochrome Swiss-style design with maximum clarity and contrast",
948
+ colors: {
949
+ primary: "#111111",
950
+ secondary: "#333333",
951
+ background: "#FFFFFF",
952
+ surface: "#F5F5F7",
953
+ text: "#111111",
954
+ muted: "#777777",
955
+ accent: "#000000",
956
+ border: "#E5E5E5",
957
+ success: "#10B981",
958
+ warning: "#F59E0B",
959
+ danger: "#EF4444",
960
+ info: "#3B82F6"
961
+ },
962
+ typography: {
963
+ headingFont: "Helvetica Neue, Arial, sans-serif",
964
+ bodyFont: "Helvetica Neue, Arial, sans-serif",
965
+ codeFont: "Menlo, Monaco, monospace",
966
+ sizes: {
967
+ h1: 44,
968
+ h2: 34,
969
+ h3: 26,
970
+ h4: 20,
971
+ body: 18,
972
+ small: 14,
973
+ code: 15
974
+ }
975
+ },
976
+ spacing: {
977
+ unit: 8,
978
+ slidePadding: 56,
979
+ elementGap: 24
980
+ },
981
+ radius: {
982
+ default: 4,
983
+ sm: 2,
984
+ md: 4,
985
+ lg: 8,
986
+ full: 9999
987
+ },
988
+ components: {
989
+ card: {
990
+ background: "#F5F5F7",
991
+ borderColor: "#E5E5E5",
992
+ borderRadius: 6,
993
+ padding: 24
994
+ }
995
+ }
996
+ };
997
+ var corporateTheme = {
998
+ name: "corporate",
999
+ description: "Executive navy blue palette tailored for business and enterprise decks",
1000
+ colors: {
1001
+ primary: "#0A2540",
1002
+ secondary: "#635BFF",
1003
+ background: "#FFFFFF",
1004
+ surface: "#F8FAFC",
1005
+ text: "#1A1F36",
1006
+ muted: "#4F566B",
1007
+ accent: "#00D4B2",
1008
+ border: "#E3E8EE",
1009
+ success: "#00D4B2",
1010
+ warning: "#FFC043",
1011
+ danger: "#DF1B41",
1012
+ info: "#635BFF"
1013
+ },
1014
+ typography: {
1015
+ headingFont: "Inter, Segoe UI, sans-serif",
1016
+ bodyFont: "Inter, Segoe UI, sans-serif",
1017
+ codeFont: "Consolas, monospace",
1018
+ sizes: {
1019
+ h1: 42,
1020
+ h2: 34,
1021
+ h3: 26,
1022
+ h4: 20,
1023
+ body: 18,
1024
+ small: 14,
1025
+ code: 15
1026
+ }
1027
+ },
1028
+ spacing: {
1029
+ unit: 8,
1030
+ slidePadding: 48,
1031
+ elementGap: 24
1032
+ },
1033
+ radius: {
1034
+ default: 8,
1035
+ sm: 4,
1036
+ md: 8,
1037
+ lg: 12,
1038
+ full: 9999
1039
+ },
1040
+ components: {
1041
+ card: {
1042
+ background: "#F8FAFC",
1043
+ borderColor: "#0A2540",
1044
+ borderRadius: 8,
1045
+ padding: 24
1046
+ }
1047
+ }
1048
+ };
1049
+ var terminalTheme = {
1050
+ name: "terminal",
1051
+ description: "Retro hacker green-on-dark theme with pure monospace aesthetic",
1052
+ colors: {
1053
+ primary: "#00FF66",
1054
+ secondary: "#33FF88",
1055
+ background: "#0C100C",
1056
+ surface: "#141A14",
1057
+ text: "#E0FFE0",
1058
+ muted: "#4E7A4E",
1059
+ accent: "#FFB000",
1060
+ border: "#1F331F",
1061
+ success: "#00FF66",
1062
+ warning: "#FFB000",
1063
+ danger: "#FF3333",
1064
+ info: "#00E5FF"
1065
+ },
1066
+ typography: {
1067
+ headingFont: "JetBrains Mono, Courier New, monospace",
1068
+ bodyFont: "JetBrains Mono, Courier New, monospace",
1069
+ codeFont: "JetBrains Mono, Courier New, monospace",
1070
+ sizes: {
1071
+ h1: 40,
1072
+ h2: 32,
1073
+ h3: 26,
1074
+ h4: 20,
1075
+ body: 17,
1076
+ small: 13,
1077
+ code: 15
1078
+ }
1079
+ },
1080
+ spacing: {
1081
+ unit: 8,
1082
+ slidePadding: 48,
1083
+ elementGap: 24
1084
+ },
1085
+ radius: {
1086
+ default: 0,
1087
+ sm: 0,
1088
+ md: 0,
1089
+ lg: 0,
1090
+ full: 0
1091
+ },
1092
+ components: {
1093
+ card: {
1094
+ background: "#141A14",
1095
+ borderColor: "#00FF66",
1096
+ borderRadius: 0,
1097
+ padding: 24
1098
+ }
1099
+ }
1100
+ };
1101
+ var academicTheme = {
1102
+ name: "academic",
1103
+ description: "Scholarly paper theme with serif typography and crimson accents",
1104
+ colors: {
1105
+ primary: "#8B0000",
1106
+ secondary: "#1C3F60",
1107
+ background: "#FCFBF7",
1108
+ surface: "#F5F2EB",
1109
+ text: "#2C2B29",
1110
+ muted: "#6B6862",
1111
+ accent: "#C29B38",
1112
+ border: "#E0DACD",
1113
+ success: "#2E7D32",
1114
+ warning: "#E65100",
1115
+ danger: "#C62828",
1116
+ info: "#1565C0"
1117
+ },
1118
+ typography: {
1119
+ headingFont: "Georgia, Times New Roman, serif",
1120
+ bodyFont: "Georgia, Times New Roman, serif",
1121
+ codeFont: "Courier New, monospace",
1122
+ sizes: {
1123
+ h1: 42,
1124
+ h2: 34,
1125
+ h3: 26,
1126
+ h4: 20,
1127
+ body: 18,
1128
+ small: 14,
1129
+ code: 15
1130
+ }
1131
+ },
1132
+ spacing: {
1133
+ unit: 8,
1134
+ slidePadding: 52,
1135
+ elementGap: 24
1136
+ },
1137
+ radius: {
1138
+ default: 4,
1139
+ sm: 2,
1140
+ md: 4,
1141
+ lg: 6,
1142
+ full: 9999
1143
+ },
1144
+ components: {
1145
+ card: {
1146
+ background: "#F5F2EB",
1147
+ borderColor: "#8B0000",
1148
+ borderRadius: 4,
1149
+ padding: 24
1150
+ }
1151
+ }
1152
+ };
1153
+ var THEME_REGISTRY = {
1154
+ default: defaultTheme,
1155
+ cyberpunk: cyberpunkTheme,
1156
+ minimal: minimalTheme,
1157
+ corporate: corporateTheme,
1158
+ terminal: terminalTheme,
1159
+ academic: academicTheme
1160
+ };
1161
+ function resolveTheme(themeNameOrRef, explicitOverrides) {
1162
+ const name = typeof themeNameOrRef === "string" ? themeNameOrRef : themeNameOrRef?.name || "default";
1163
+ const baseTheme = THEME_REGISTRY[name.toLowerCase()] || defaultTheme;
1164
+ const refOverrides = typeof themeNameOrRef === "object" ? themeNameOrRef.overrides : void 0;
1165
+ const mergedOverrides = {
1166
+ ...refOverrides,
1167
+ ...explicitOverrides,
1168
+ colors: { ...baseTheme.colors, ...refOverrides?.colors, ...explicitOverrides?.colors },
1169
+ typography: {
1170
+ ...baseTheme.typography,
1171
+ ...refOverrides?.typography,
1172
+ ...explicitOverrides?.typography
1173
+ },
1174
+ components: {
1175
+ ...baseTheme.components,
1176
+ ...refOverrides?.components,
1177
+ ...explicitOverrides?.components
1178
+ }
1179
+ };
827
1180
  return {
828
- ...defaultTheme,
829
- ...overrides,
830
- colors: { ...defaultTheme.colors, ...overrides.colors },
831
- typography: { ...defaultTheme.typography, ...overrides.typography },
832
- spacing: { ...defaultTheme.spacing, ...overrides.spacing },
833
- radius: { ...defaultTheme.radius, ...overrides.radius },
834
- shadows: { ...defaultTheme.shadows, ...overrides.shadows },
835
- components: { ...defaultTheme.components, ...overrides.components }
1181
+ ...baseTheme,
1182
+ ...mergedOverrides
836
1183
  };
837
1184
  }
1185
+ function createTheme(overrides) {
1186
+ return resolveTheme(overrides.name, overrides);
1187
+ }
1188
+
1189
+ // ../core/dist/linter.js
1190
+ var YumiaLinter = class {
1191
+ layoutEngine;
1192
+ constructor(layoutEngine) {
1193
+ this.layoutEngine = layoutEngine ?? new DefaultLayoutEngine();
1194
+ }
1195
+ lint(presentation, options = {}) {
1196
+ const issues = [];
1197
+ const layout = this.layoutEngine.computePresentation(presentation, options.viewport);
1198
+ if (presentation.diagnostics) {
1199
+ for (const diag of presentation.diagnostics) {
1200
+ issues.push({
1201
+ code: diag.code || "YUM002",
1202
+ slide: 1,
1203
+ message: diag.message,
1204
+ severity: diag.severity === "error" ? "error" : "warning",
1205
+ loc: diag.loc
1206
+ });
1207
+ }
1208
+ }
1209
+ presentation.slides.forEach((slide, index) => {
1210
+ const slideNum = index + 1;
1211
+ const slideLayout = layout.slides[index];
1212
+ if (slide.elements.length === 0) {
1213
+ issues.push({
1214
+ code: "YUM006",
1215
+ slide: slideNum,
1216
+ message: "Slide contains no content elements.",
1217
+ severity: "warning",
1218
+ loc: slide.loc
1219
+ });
1220
+ return;
1221
+ }
1222
+ if (slideLayout && slideLayout.overflow) {
1223
+ const overflowPx = Math.round(slideLayout.overflowAmount || 0);
1224
+ issues.push({
1225
+ code: "YUM001",
1226
+ slide: slideNum,
1227
+ message: `Content exceeds slide viewport bounds by ~${overflowPx}px. Consider reducing font size or splitting into multiple slides.`,
1228
+ severity: options.strict ? "error" : "warning",
1229
+ loc: slide.loc
1230
+ });
1231
+ }
1232
+ const hasHeading = this.slideHasHeading(slide.elements);
1233
+ if (!hasHeading) {
1234
+ issues.push({
1235
+ code: "YUM003",
1236
+ slide: slideNum,
1237
+ message: "Slide has no heading (h1-h4). Consider adding a descriptive title for structure.",
1238
+ severity: "warning",
1239
+ loc: slide.loc
1240
+ });
1241
+ }
1242
+ const { listCount, wordCount } = this.calculateSlideDensity(slide.elements);
1243
+ if (listCount > 7) {
1244
+ issues.push({
1245
+ code: "YUM004",
1246
+ slide: slideNum,
1247
+ message: `High information density: slide contains ${listCount} list items (recommended max: 7).`,
1248
+ severity: "warning",
1249
+ loc: slide.loc
1250
+ });
1251
+ } else if (wordCount > 120) {
1252
+ issues.push({
1253
+ code: "YUM004",
1254
+ slide: slideNum,
1255
+ message: `High information density: slide contains ~${wordCount} words (recommended max: 120 words).`,
1256
+ severity: "warning",
1257
+ loc: slide.loc
1258
+ });
1259
+ }
1260
+ this.checkImages(slide.elements, slideNum, issues);
1261
+ const maxDepth = this.getMaxNestingDepth(slide.elements, 0);
1262
+ if (maxDepth > 2) {
1263
+ issues.push({
1264
+ code: "YUM008",
1265
+ slide: slideNum,
1266
+ message: `Excessive directive nesting depth (${maxDepth} levels). Maximum recommended depth is 2.`,
1267
+ severity: "warning",
1268
+ loc: slide.loc
1269
+ });
1270
+ }
1271
+ if (options.includeOptionalNotesRule && (!slide.notes || slide.notes.trim() === "")) {
1272
+ issues.push({
1273
+ code: "YUM009",
1274
+ slide: slideNum,
1275
+ message: "Slide has no speaker notes (:::notes directive).",
1276
+ severity: "info",
1277
+ loc: slide.loc
1278
+ });
1279
+ }
1280
+ });
1281
+ const errors = issues.filter((i) => i.severity === "error");
1282
+ const warnings = issues.filter((i) => i.severity === "warning");
1283
+ const infos = issues.filter((i) => i.severity === "info");
1284
+ return {
1285
+ passed: options.strict ? errors.length === 0 && warnings.length === 0 : errors.length === 0,
1286
+ totalSlides: presentation.slides.length,
1287
+ issueCount: issues.length,
1288
+ errors,
1289
+ warnings,
1290
+ infos
1291
+ };
1292
+ }
1293
+ slideHasHeading(elements) {
1294
+ for (const el of elements) {
1295
+ if (el.type === "heading")
1296
+ return true;
1297
+ if (el.type === "card" && el.title)
1298
+ return true;
1299
+ if (el.type === "columns") {
1300
+ const cols = el.columns;
1301
+ for (const col of cols) {
1302
+ if (this.slideHasHeading(col.elements))
1303
+ return true;
1304
+ }
1305
+ }
1306
+ }
1307
+ return false;
1308
+ }
1309
+ calculateSlideDensity(elements) {
1310
+ let listCount = 0;
1311
+ let wordCount = 0;
1312
+ const traverse = (els) => {
1313
+ for (const el of els) {
1314
+ if (el.type === "list") {
1315
+ const list = el;
1316
+ listCount += list.items.length;
1317
+ for (const item of list.items) {
1318
+ wordCount += item.text.split(/\s+/).filter(Boolean).length;
1319
+ }
1320
+ } else if (el.type === "paragraph") {
1321
+ const p = el;
1322
+ wordCount += p.text.split(/\s+/).filter(Boolean).length;
1323
+ } else if (el.type === "card") {
1324
+ const card = el;
1325
+ if (card.title)
1326
+ wordCount += card.title.split(/\s+/).filter(Boolean).length;
1327
+ if (card.elements)
1328
+ traverse(card.elements);
1329
+ } else if (el.type === "columns") {
1330
+ const cols = el;
1331
+ for (const col of cols.columns) {
1332
+ traverse(col.elements);
1333
+ }
1334
+ }
1335
+ }
1336
+ };
1337
+ traverse(elements);
1338
+ return { listCount, wordCount };
1339
+ }
1340
+ checkImages(elements, slideNum, issues) {
1341
+ const traverse = (els) => {
1342
+ for (const el of els) {
1343
+ if (el.type === "image") {
1344
+ const img = el;
1345
+ if (!img.alt || img.alt.trim() === "") {
1346
+ issues.push({
1347
+ code: "YUM007",
1348
+ slide: slideNum,
1349
+ message: `Image '${img.src}' is missing descriptive alt text for accessibility.`,
1350
+ severity: "warning",
1351
+ loc: img.loc
1352
+ });
1353
+ }
1354
+ if (!img.src || img.src.trim() === "") {
1355
+ issues.push({
1356
+ code: "YUM005",
1357
+ slide: slideNum,
1358
+ message: "Image element has empty src attribute.",
1359
+ severity: "error",
1360
+ loc: img.loc
1361
+ });
1362
+ }
1363
+ } else if (el.type === "card" && el.elements) {
1364
+ traverse(el.elements);
1365
+ } else if (el.type === "columns") {
1366
+ for (const col of el.columns) {
1367
+ traverse(col.elements);
1368
+ }
1369
+ }
1370
+ }
1371
+ };
1372
+ traverse(elements);
1373
+ }
1374
+ getMaxNestingDepth(elements, currentDepth) {
1375
+ let max = currentDepth;
1376
+ for (const el of elements) {
1377
+ if (el.type === "card" && el.elements) {
1378
+ const d = this.getMaxNestingDepth(el.elements, currentDepth + 1);
1379
+ if (d > max)
1380
+ max = d;
1381
+ } else if (el.type === "columns") {
1382
+ for (const col of el.columns) {
1383
+ const d = this.getMaxNestingDepth(col.elements, currentDepth + 1);
1384
+ if (d > max)
1385
+ max = d;
1386
+ }
1387
+ }
1388
+ }
1389
+ return max;
1390
+ }
1391
+ };
838
1392
 
839
1393
  // ../core/dist/compiler.js
840
1394
  var YumiaCompiler = class {
841
1395
  parser;
842
1396
  layoutEngine;
843
1397
  defaultTheme;
1398
+ linter;
844
1399
  viewport;
845
1400
  constructor(config = {}) {
846
1401
  this.parser = config.parser ?? new DefaultYumiaParser();
847
1402
  this.layoutEngine = config.layoutEngine ?? new DefaultLayoutEngine();
848
1403
  this.defaultTheme = config.defaultTheme ?? defaultTheme;
1404
+ this.linter = new YumiaLinter(this.layoutEngine);
849
1405
  if (config.viewport) {
850
1406
  this.viewport = config.viewport;
851
1407
  }
@@ -865,12 +1421,20 @@ var YumiaCompiler = class {
865
1421
  warnings
866
1422
  };
867
1423
  }
1424
+ lint(sourceOrPresentation, options) {
1425
+ const presentation = typeof sourceOrPresentation === "string" ? this.parse(sourceOrPresentation) : sourceOrPresentation;
1426
+ return this.linter.lint(presentation, {
1427
+ ...this.viewport ? { viewport: this.viewport } : {},
1428
+ ...options
1429
+ });
1430
+ }
868
1431
  layout(presentation, viewport) {
869
1432
  return this.layoutEngine.computePresentation(presentation, viewport ?? this.viewport);
870
1433
  }
871
1434
  async render(presentation, renderer, contextOverrides = {}) {
872
1435
  const layout = contextOverrides.layout ?? this.layout(presentation);
873
- const theme = contextOverrides.theme ?? this.defaultTheme;
1436
+ const presentationTheme = presentation.metadata.theme ? resolveTheme(presentation.metadata.theme) : this.defaultTheme;
1437
+ const theme = contextOverrides.theme ?? presentationTheme;
874
1438
  const context = {
875
1439
  theme,
876
1440
  layout,
@@ -896,7 +1460,10 @@ var YumiaCompiler = class {
896
1460
  subtitle: { type: "string" },
897
1461
  author: { type: "string" },
898
1462
  date: { type: "string" },
899
- theme: { type: "string" },
1463
+ theme: {
1464
+ type: "string",
1465
+ enum: ["default", "cyberpunk", "minimal", "corporate", "terminal", "academic"]
1466
+ },
900
1467
  aspectRatio: { enum: ["16:9", "4:3", "16:10"] }
901
1468
  }
902
1469
  },
@@ -908,9 +1475,13 @@ var YumiaCompiler = class {
908
1475
  description: "Multi-column grid layout"
909
1476
  },
910
1477
  card: {
911
- syntax: ":::card [Title]\\n...\\n:::",
1478
+ syntax: ':::card [Title] [variant="primary|success|warning|danger|info"]\\n...\\n:::',
912
1479
  description: "Visual container card with theme border and background"
913
1480
  },
1481
+ metric: {
1482
+ syntax: ':::metric value="99.9%" label="Uptime" change="+0.4%" variant="success"',
1483
+ description: "Stat callout box with prominent value, label, and trend"
1484
+ },
914
1485
  notes: {
915
1486
  syntax: ":::notes\\nSpeaker notes text\\n:::",
916
1487
  description: "Speaker notes attached to slide metadata"
@@ -944,6 +1515,37 @@ var CSS_NAMED_COLORS = {
944
1515
  slate: "0f172a",
945
1516
  transparent: "ffffff"
946
1517
  };
1518
+ function parseInlineMarkdown(rawText, baseOptions) {
1519
+ const chunks = [];
1520
+ const regex = /(\*\*.*?\*\*|\*.*?\*|`.*?`)/g;
1521
+ const parts = rawText.split(regex);
1522
+ for (const part of parts) {
1523
+ if (!part)
1524
+ continue;
1525
+ if (part.startsWith("**") && part.endsWith("**") && part.length >= 4) {
1526
+ chunks.push({
1527
+ text: part.slice(2, -2),
1528
+ options: { ...baseOptions, bold: true }
1529
+ });
1530
+ } else if (part.startsWith("*") && part.endsWith("*") && part.length >= 2) {
1531
+ chunks.push({
1532
+ text: part.slice(1, -1),
1533
+ options: { ...baseOptions, italic: true }
1534
+ });
1535
+ } else if (part.startsWith("`") && part.endsWith("`") && part.length >= 2) {
1536
+ chunks.push({
1537
+ text: part.slice(1, -1),
1538
+ options: { ...baseOptions, fontFace: "Courier New" }
1539
+ });
1540
+ } else {
1541
+ chunks.push({
1542
+ text: part,
1543
+ options: { ...baseOptions }
1544
+ });
1545
+ }
1546
+ }
1547
+ return chunks.length > 0 ? chunks : [{ text: rawText, options: baseOptions }];
1548
+ }
947
1549
  var PptxRenderer = class {
948
1550
  name = "PptxRenderer";
949
1551
  targetFormat = "pptx";
@@ -954,7 +1556,8 @@ var PptxRenderer = class {
954
1556
  async render(presentation, context = {}) {
955
1557
  const PptxConstructor = pptxgen.default || pptxgen;
956
1558
  const pptx = new PptxConstructor();
957
- const theme = context.theme || defaultTheme;
1559
+ const resolvedTheme = presentation.metadata.theme ? resolveTheme(presentation.metadata.theme) : defaultTheme;
1560
+ const theme = context.theme || resolvedTheme;
958
1561
  const title = presentation.metadata.title || "Yumia Presentation";
959
1562
  const author = presentation.metadata.author || "YumiaMD";
960
1563
  pptx.title = title;
@@ -1014,6 +1617,9 @@ var PptxRenderer = class {
1014
1617
  case "image":
1015
1618
  this.renderImage(pptxSlide, element, rect);
1016
1619
  break;
1620
+ case "metric":
1621
+ this.renderMetric(pptxSlide, pptx, node, scaleX, scaleY, theme);
1622
+ break;
1017
1623
  case "card":
1018
1624
  this.renderCard(pptxSlide, pptx, node, scaleX, scaleY, theme);
1019
1625
  break;
@@ -1027,15 +1633,17 @@ var PptxRenderer = class {
1027
1633
  renderHeading(pptxSlide, heading, rect, theme) {
1028
1634
  const fontSize = this.getHeadingFontSize(heading.level, theme);
1029
1635
  const color = this.cleanHexColor(heading.level === 1 ? theme.colors.primary : theme.colors.text);
1030
- pptxSlide.addText(heading.text, {
1636
+ const chunks = parseInlineMarkdown(heading.text, {
1637
+ fontSize,
1638
+ bold: true,
1639
+ fontFace: theme.typography.headingFont.split(",")[0]?.trim() || "Arial",
1640
+ color
1641
+ });
1642
+ pptxSlide.addText(chunks, {
1031
1643
  x: rect.x,
1032
1644
  y: rect.y,
1033
1645
  w: rect.w,
1034
1646
  h: rect.h,
1035
- fontSize,
1036
- bold: true,
1037
- fontFace: theme.typography.headingFont.split(",")[0]?.trim() || "Arial",
1038
- color,
1039
1647
  align: heading.align || "left",
1040
1648
  valign: "middle",
1041
1649
  margin: 0
@@ -1044,14 +1652,16 @@ var PptxRenderer = class {
1044
1652
  renderParagraph(pptxSlide, paragraph, rect, theme) {
1045
1653
  const fontSize = theme.typography.sizes?.body || 18;
1046
1654
  const color = this.cleanHexColor(theme.colors.text);
1047
- pptxSlide.addText(paragraph.text, {
1655
+ const chunks = parseInlineMarkdown(paragraph.text, {
1656
+ fontSize,
1657
+ fontFace: theme.typography.bodyFont.split(",")[0]?.trim() || "Arial",
1658
+ color
1659
+ });
1660
+ pptxSlide.addText(chunks, {
1048
1661
  x: rect.x,
1049
1662
  y: rect.y,
1050
1663
  w: rect.w,
1051
1664
  h: rect.h,
1052
- fontSize,
1053
- fontFace: theme.typography.bodyFont.split(",")[0]?.trim() || "Arial",
1054
- color,
1055
1665
  align: paragraph.align || "left",
1056
1666
  valign: "top",
1057
1667
  margin: 0
@@ -1060,18 +1670,24 @@ var PptxRenderer = class {
1060
1670
  renderList(pptxSlide, list, rect, theme) {
1061
1671
  const fontSize = theme.typography.sizes?.body || 18;
1062
1672
  const color = this.cleanHexColor(theme.colors.text);
1063
- const textItems = list.items.map((item) => ({
1064
- text: item.text,
1065
- options: {
1066
- bullet: list.ordered ? { type: "number" } : true,
1673
+ const allChunks = [];
1674
+ list.items.forEach((item) => {
1675
+ const itemChunks = parseInlineMarkdown(item.text, {
1067
1676
  fontSize,
1068
1677
  color,
1069
1678
  fontFace: theme.typography.bodyFont.split(",")[0]?.trim() || "Arial",
1070
1679
  indentLevel: item.depth || 0,
1071
1680
  paraSpaceAfter: 8
1681
+ });
1682
+ if (itemChunks.length > 0) {
1683
+ itemChunks[0].options = {
1684
+ ...itemChunks[0].options,
1685
+ bullet: list.ordered ? { type: "number" } : true
1686
+ };
1072
1687
  }
1073
- }));
1074
- pptxSlide.addText(textItems, {
1688
+ allChunks.push(...itemChunks);
1689
+ });
1690
+ pptxSlide.addText(allChunks, {
1075
1691
  x: rect.x,
1076
1692
  y: rect.y,
1077
1693
  w: rect.w,
@@ -1086,7 +1702,7 @@ var PptxRenderer = class {
1086
1702
  const borderColor = this.cleanHexColor(theme.colors.border || "#cbd5e1");
1087
1703
  if (table.headers && table.headers.length > 0) {
1088
1704
  tableRows.push(table.headers.map((h) => ({
1089
- text: h,
1705
+ text: h.replace(/\*\*/g, ""),
1090
1706
  options: {
1091
1707
  bold: true,
1092
1708
  color: "ffffff",
@@ -1100,7 +1716,7 @@ var PptxRenderer = class {
1100
1716
  table.rows.forEach((row, rowIndex) => {
1101
1717
  const rowBg = rowIndex % 2 === 1 ? "f8fafc" : "ffffff";
1102
1718
  tableRows.push(row.map((cell) => ({
1103
- text: cell,
1719
+ text: cell.replace(/\*\*/g, ""),
1104
1720
  options: {
1105
1721
  color: this.cleanHexColor(theme.colors.text),
1106
1722
  fill: { color: rowBg },
@@ -1149,12 +1765,79 @@ var PptxRenderer = class {
1149
1765
  });
1150
1766
  }
1151
1767
  }
1768
+ renderMetric(pptxSlide, pptx, node, scaleX, scaleY, theme) {
1769
+ const metric = node.element;
1770
+ const rect = this.toInches(node.bounds, scaleX, scaleY);
1771
+ const cardTheme = theme.components?.card;
1772
+ let accentColor = theme.colors.primary;
1773
+ if (metric.variant && theme.colors[metric.variant]) {
1774
+ accentColor = theme.colors[metric.variant];
1775
+ }
1776
+ const fillColor = this.cleanHexColor(cardTheme?.background || theme.colors.surface);
1777
+ const borderColor = this.cleanHexColor(metric.variant && theme.colors[metric.variant] ? theme.colors[metric.variant] : cardTheme?.borderColor || theme.colors.border || "#cbd5e1");
1778
+ pptxSlide.addShape(pptx.ShapeType.roundRect, {
1779
+ x: rect.x,
1780
+ y: rect.y,
1781
+ w: rect.w,
1782
+ h: rect.h,
1783
+ fill: { color: fillColor },
1784
+ line: { color: borderColor, width: 1.5 },
1785
+ rectRadius: 0.08
1786
+ });
1787
+ pptxSlide.addText(metric.label.toUpperCase(), {
1788
+ x: rect.x + 0.15,
1789
+ y: rect.y + 0.15,
1790
+ w: rect.w - 0.3,
1791
+ h: 0.25,
1792
+ fontSize: 11,
1793
+ bold: true,
1794
+ color: this.cleanHexColor(theme.colors.muted || "#64748b"),
1795
+ fontFace: theme.typography.headingFont.split(",")[0]?.trim() || "Arial",
1796
+ align: "center",
1797
+ valign: "middle"
1798
+ });
1799
+ const displayValue = metric.unit ? `${metric.value} ${metric.unit}` : metric.value;
1800
+ pptxSlide.addText(displayValue, {
1801
+ x: rect.x + 0.1,
1802
+ y: rect.y + 0.4,
1803
+ w: rect.w - 0.2,
1804
+ h: 0.55,
1805
+ fontSize: 30,
1806
+ bold: true,
1807
+ color: this.cleanHexColor(accentColor),
1808
+ fontFace: theme.typography.headingFont.split(",")[0]?.trim() || "Arial",
1809
+ align: "center",
1810
+ valign: "middle"
1811
+ });
1812
+ if (metric.change) {
1813
+ const isPositive = metric.change.startsWith("+");
1814
+ const changeColor = isPositive ? this.cleanHexColor(theme.colors.success || "#10b981") : this.cleanHexColor(theme.colors.danger || "#ef4444");
1815
+ pptxSlide.addText(metric.change, {
1816
+ x: rect.x + 0.1,
1817
+ y: rect.y + rect.h - 0.32,
1818
+ w: rect.w - 0.2,
1819
+ h: 0.22,
1820
+ fontSize: 12,
1821
+ bold: true,
1822
+ color: changeColor,
1823
+ fontFace: theme.typography.bodyFont.split(",")[0]?.trim() || "Arial",
1824
+ align: "center",
1825
+ valign: "middle"
1826
+ });
1827
+ }
1828
+ }
1152
1829
  renderCard(pptxSlide, pptx, node, scaleX, scaleY, theme) {
1153
1830
  const card = node.element;
1154
1831
  const rect = this.toInches(node.bounds, scaleX, scaleY);
1155
1832
  const cardTheme = theme.components?.card;
1156
1833
  const fillColor = this.cleanHexColor(cardTheme?.background || theme.colors.surface);
1157
- const borderColor = this.cleanHexColor(cardTheme?.borderColor || theme.colors.border || "#cbd5e1");
1834
+ let borderColor = this.cleanHexColor(cardTheme?.borderColor || theme.colors.border || "#cbd5e1");
1835
+ let titleColor = this.cleanHexColor(theme.colors.primary);
1836
+ if (card.variant && theme.colors[card.variant]) {
1837
+ const variantHex = this.cleanHexColor(theme.colors[card.variant]);
1838
+ borderColor = variantHex;
1839
+ titleColor = variantHex;
1840
+ }
1158
1841
  pptxSlide.addShape(pptx.ShapeType.roundRect, {
1159
1842
  x: rect.x,
1160
1843
  y: rect.y,
@@ -1162,17 +1845,17 @@ var PptxRenderer = class {
1162
1845
  h: rect.h,
1163
1846
  fill: { color: fillColor },
1164
1847
  line: { color: borderColor, width: 1.5 },
1165
- rectRadius: 0.1
1848
+ rectRadius: 0.08
1166
1849
  });
1167
1850
  if (card.title) {
1168
1851
  pptxSlide.addText(card.title, {
1169
- x: rect.x + 28 * scaleX,
1170
- y: rect.y + 20 * scaleY,
1171
- w: rect.w - 56 * scaleX,
1172
- h: 40 * scaleY,
1173
- fontSize: 22,
1852
+ x: rect.x + 0.25,
1853
+ y: rect.y + 0.18,
1854
+ w: rect.w - 0.5,
1855
+ h: 0.35,
1856
+ fontSize: 20,
1174
1857
  bold: true,
1175
- color: this.cleanHexColor(theme.colors.primary),
1858
+ color: titleColor,
1176
1859
  fontFace: theme.typography.headingFont.split(",")[0]?.trim() || "Arial"
1177
1860
  });
1178
1861
  }
@@ -1225,12 +1908,13 @@ var PptxRenderer = class {
1225
1908
  h: rect.h,
1226
1909
  fill: { color: accentColor }
1227
1910
  });
1228
- pptxSlide.addText(`"${quote.text}"`, {
1911
+ const cleanText = quote.text.replace(/^["'“”«»]+|["'“”«»]+$/g, "").trim();
1912
+ pptxSlide.addText(`\u201C${cleanText}\u201D`, {
1229
1913
  x: rect.x + 0.2,
1230
1914
  y: rect.y,
1231
1915
  w: rect.w - 0.2,
1232
1916
  h: rect.h,
1233
- fontSize: 20,
1917
+ fontSize: 18,
1234
1918
  italic: true,
1235
1919
  color: this.cleanHexColor(theme.colors.muted || theme.colors.text),
1236
1920
  fontFace: theme.typography.bodyFont.split(",")[0]?.trim() || "Arial",
@@ -1282,7 +1966,7 @@ var PptxRenderer = class {
1282
1966
  // src/cli.ts
1283
1967
  import { mkdirSync, readFileSync, writeFileSync } from "fs";
1284
1968
  import { basename, dirname, extname, join, resolve } from "path";
1285
- var VERSION = "0.1.6";
1969
+ var VERSION = "0.1.8";
1286
1970
  function printHelp() {
1287
1971
  return `
1288
1972
  YumiaMD \u2014 Markdown-based presentation compiler (v${VERSION})
@@ -1301,6 +1985,7 @@ Commands:
1301
1985
  Options:
1302
1986
  --out, -o <file> Specify output file path (default: dist/<name>.pptx)
1303
1987
  --format, -f <fmt> Target output format: pptx (default)
1988
+ --strict Enforce zero warnings in 'lint' (exits with code 1 on warning)
1304
1989
  --json Output results formatted as JSON for CI/CD and AI tools
1305
1990
  --layout Show computed geometric bounding boxes in 'inspect'
1306
1991
  -h, --help Show this help message
@@ -1316,6 +2001,7 @@ async function runCli(argv) {
1316
2001
  return { exitCode: 0, output: `yumia v${VERSION}` };
1317
2002
  }
1318
2003
  const isJson = args.includes("--json");
2004
+ const isStrict = args.includes("--strict");
1319
2005
  const nonFlagArgs = args.filter((a) => !a.startsWith("-"));
1320
2006
  const command = nonFlagArgs[0];
1321
2007
  const target = nonFlagArgs[1];
@@ -1452,57 +2138,46 @@ ${errorLines.join("\n")}`
1452
2138
  }
1453
2139
  try {
1454
2140
  const source = readFileSync(target, "utf-8");
1455
- const presentation = parseYumia(source);
1456
- const engine = new DefaultLayoutEngine();
1457
- const layout = engine.computePresentation(presentation);
1458
- const issues = [];
1459
- layout.slides.forEach((slideResult, index) => {
1460
- const slideNum = index + 1;
1461
- if (slideResult.overflow) {
1462
- issues.push({
1463
- slide: slideNum,
1464
- type: "overflow",
1465
- message: `Content exceeds slide height by ~${Math.round(slideResult.overflowAmount || 0)}px`
1466
- });
1467
- }
1468
- const slide = presentation.slides[index];
1469
- if (slide.elements.length === 0) {
1470
- issues.push({
1471
- slide: slideNum,
1472
- type: "empty",
1473
- message: "Slide has no content elements."
1474
- });
1475
- }
1476
- });
2141
+ const compiler = new YumiaCompiler();
2142
+ const report = compiler.lint(source, { strict: isStrict });
1477
2143
  if (isJson) {
1478
2144
  return {
1479
- exitCode: 0,
2145
+ exitCode: report.passed ? 0 : 1,
1480
2146
  output: JSON.stringify(
1481
2147
  {
1482
2148
  target,
1483
- passed: issues.length === 0,
1484
- slideCount: presentation.slides.length,
1485
- issues
2149
+ ...report
1486
2150
  },
1487
2151
  null,
1488
2152
  2
1489
2153
  )
1490
2154
  };
1491
2155
  }
1492
- if (issues.length === 0) {
2156
+ if (report.issueCount === 0) {
1493
2157
  return {
1494
2158
  exitCode: 0,
1495
- output: `\u2713 Yumia Lint: All ${presentation.slides.length} slide(s) passed with 0 issues.`
2159
+ output: `\u2713 Yumia Lint: All ${report.totalSlides} slide(s) passed with 0 issues.`
1496
2160
  };
1497
2161
  } else {
1498
- const formatted = issues.map((i) => `\u26A0 Slide ${i.slide}: ${i.message}`).join("\n");
2162
+ const issueLines = [];
2163
+ for (const e of report.errors) {
2164
+ issueLines.push(` \u2717 [${e.code}] Slide ${e.slide}: ${e.message}`);
2165
+ }
2166
+ for (const w of report.warnings) {
2167
+ issueLines.push(` \u26A0 [${w.code}] Slide ${w.slide}: ${w.message}`);
2168
+ }
2169
+ for (const info of report.infos) {
2170
+ issueLines.push(` \u2139 [${info.code}] Slide ${info.slide}: ${info.message}`);
2171
+ }
2172
+ const summary = `Found ${report.errors.length} error(s), ${report.warnings.length} warning(s).`;
2173
+ const exitCode = report.passed ? 0 : 1;
1499
2174
  return {
1500
- exitCode: 0,
2175
+ exitCode,
1501
2176
  output: `Yumia Lint Report for '${target}':
1502
2177
 
1503
- ${formatted}
2178
+ ${issueLines.join("\n")}
1504
2179
 
1505
- Found ${issues.length} warning(s).`
2180
+ ${summary}${isStrict && report.warnings.length > 0 ? " (failed due to --strict)" : ""}`
1506
2181
  };
1507
2182
  }
1508
2183
  } catch (err) {
@@ -1614,6 +2289,7 @@ export {
1614
2289
  createList,
1615
2290
  createImage,
1616
2291
  createCard,
2292
+ createMetric,
1617
2293
  createCode,
1618
2294
  createQuote,
1619
2295
  createTable,
@@ -1627,8 +2303,17 @@ export {
1627
2303
  VIEWPORT_PRESETS,
1628
2304
  DefaultLayoutEngine,
1629
2305
  defaultTheme,
2306
+ cyberpunkTheme,
2307
+ minimalTheme,
2308
+ corporateTheme,
2309
+ terminalTheme,
2310
+ academicTheme,
2311
+ THEME_REGISTRY,
2312
+ resolveTheme,
1630
2313
  createTheme,
2314
+ YumiaLinter,
1631
2315
  YumiaCompiler,
2316
+ parseInlineMarkdown,
1632
2317
  PptxRenderer,
1633
2318
  VERSION,
1634
2319
  printHelp,
package/dist/index.d.ts CHANGED
@@ -6,7 +6,7 @@ export * from '@yumiamd/layout';
6
6
  export * from '@yumiamd/renderer';
7
7
  export * from '@yumiamd/renderer-pptx';
8
8
 
9
- declare const VERSION = "0.1.6";
9
+ declare const VERSION = "0.1.8";
10
10
  declare function printHelp(): string;
11
11
  declare function runCli(argv: string[]): Promise<{
12
12
  exitCode: number;
package/dist/index.js CHANGED
@@ -3,9 +3,13 @@ import {
3
3
  DefaultLayoutEngine,
4
4
  DefaultYumiaParser,
5
5
  PptxRenderer,
6
+ THEME_REGISTRY,
6
7
  VERSION,
7
8
  VIEWPORT_PRESETS,
8
9
  YumiaCompiler,
10
+ YumiaLinter,
11
+ academicTheme,
12
+ corporateTheme,
9
13
  createCard,
10
14
  createCode,
11
15
  createColumn,
@@ -15,25 +19,35 @@ import {
15
19
  createImage,
16
20
  createLayoutDirective,
17
21
  createList,
22
+ createMetric,
18
23
  createParagraph,
19
24
  createPresentation,
20
25
  createQuote,
21
26
  createSlide,
22
27
  createTable,
23
28
  createTheme,
29
+ cyberpunkTheme,
24
30
  defaultTheme,
31
+ minimalTheme,
32
+ parseInlineMarkdown,
25
33
  parseYumia,
26
34
  printHelp,
27
- runCli
28
- } from "./chunk-FHM6V4JH.js";
35
+ resolveTheme,
36
+ runCli,
37
+ terminalTheme
38
+ } from "./chunk-WUJKHJJU.js";
29
39
  export {
30
40
  DEFAULT_VIEWPORT,
31
41
  DefaultLayoutEngine,
32
42
  DefaultYumiaParser,
33
43
  PptxRenderer,
44
+ THEME_REGISTRY,
34
45
  VERSION,
35
46
  VIEWPORT_PRESETS,
36
47
  YumiaCompiler,
48
+ YumiaLinter,
49
+ academicTheme,
50
+ corporateTheme,
37
51
  createCard,
38
52
  createCode,
39
53
  createColumn,
@@ -43,14 +57,20 @@ export {
43
57
  createImage,
44
58
  createLayoutDirective,
45
59
  createList,
60
+ createMetric,
46
61
  createParagraph,
47
62
  createPresentation,
48
63
  createQuote,
49
64
  createSlide,
50
65
  createTable,
51
66
  createTheme,
67
+ cyberpunkTheme,
52
68
  defaultTheme,
69
+ minimalTheme,
70
+ parseInlineMarkdown,
53
71
  parseYumia,
54
72
  printHelp,
55
- runCli
73
+ resolveTheme,
74
+ runCli,
75
+ terminalTheme
56
76
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yumiamd",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "Command line interface and compiler for YumiaMD presentations",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -20,13 +20,13 @@
20
20
  },
21
21
  "devDependencies": {
22
22
  "tsup": "^8.5.1",
23
- "@yumiamd/ast": "0.1.6",
24
- "@yumiamd/parser": "0.1.6",
25
- "@yumiamd/core": "0.1.6",
26
- "@yumiamd/layout": "0.1.6",
27
- "@yumiamd/renderer": "0.1.6",
28
- "@yumiamd/renderer-pptx": "0.1.6",
29
- "@yumiamd/theme": "0.1.6"
23
+ "@yumiamd/ast": "0.1.8",
24
+ "@yumiamd/parser": "0.1.8",
25
+ "@yumiamd/core": "0.1.8",
26
+ "@yumiamd/renderer": "0.1.8",
27
+ "@yumiamd/layout": "0.1.8",
28
+ "@yumiamd/renderer-pptx": "0.1.8",
29
+ "@yumiamd/theme": "0.1.8"
30
30
  },
31
31
  "keywords": [
32
32
  "yumia",