@ncukondo/slide-generation 0.1.0

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/index.js ADDED
@@ -0,0 +1,1469 @@
1
+ // src/core/parser.ts
2
+ import { z } from "zod";
3
+ import { parse as parseYaml } from "yaml";
4
+ import { readFile } from "fs/promises";
5
+ var referencesConfigSchema = z.object({
6
+ enabled: z.boolean().default(true),
7
+ style: z.string().default("author-year-pmid")
8
+ });
9
+ var metaSchema = z.object({
10
+ title: z.string(),
11
+ author: z.string().optional(),
12
+ date: z.string().optional(),
13
+ theme: z.string().default("default"),
14
+ references: referencesConfigSchema.optional()
15
+ });
16
+ var slideSchema = z.object({
17
+ template: z.string(),
18
+ content: z.record(z.unknown()).default({}),
19
+ class: z.string().optional(),
20
+ notes: z.string().optional(),
21
+ raw: z.string().optional()
22
+ });
23
+ var presentationSchema = z.object({
24
+ meta: metaSchema,
25
+ slides: z.array(slideSchema).default([])
26
+ });
27
+ var ParseError = class extends Error {
28
+ constructor(message, details) {
29
+ super(message);
30
+ this.details = details;
31
+ this.name = "ParseError";
32
+ }
33
+ };
34
+ var ValidationError = class extends Error {
35
+ constructor(message, details) {
36
+ super(message);
37
+ this.details = details;
38
+ this.name = "ValidationError";
39
+ }
40
+ };
41
+ var Parser = class {
42
+ parse(yamlContent) {
43
+ let rawData;
44
+ try {
45
+ rawData = parseYaml(yamlContent);
46
+ } catch (error) {
47
+ throw new ParseError("Failed to parse YAML", error);
48
+ }
49
+ const result = presentationSchema.safeParse(rawData);
50
+ if (!result.success) {
51
+ throw new ValidationError(
52
+ "Schema validation failed",
53
+ result.error.format()
54
+ );
55
+ }
56
+ return result.data;
57
+ }
58
+ async parseFile(filePath) {
59
+ let content;
60
+ try {
61
+ content = await readFile(filePath, "utf-8");
62
+ } catch (error) {
63
+ if (error.code === "ENOENT") {
64
+ throw new ParseError(`File not found: ${filePath}`);
65
+ }
66
+ throw new ParseError(`Failed to read file: ${filePath}`, error);
67
+ }
68
+ return this.parse(content);
69
+ }
70
+ };
71
+
72
+ // src/core/transformer.ts
73
+ var ICON_PLACEHOLDER_PREFIX = "___ICON_PLACEHOLDER_";
74
+ var ICON_PLACEHOLDER_SUFFIX = "___";
75
+ var REFS_CITE_PLACEHOLDER_PREFIX = "___REFS_CITE_PLACEHOLDER_";
76
+ var REFS_CITE_PLACEHOLDER_SUFFIX = "___";
77
+ var REFS_EXPAND_PLACEHOLDER_PREFIX = "___REFS_EXPAND_PLACEHOLDER_";
78
+ var REFS_EXPAND_PLACEHOLDER_SUFFIX = "___";
79
+ var TransformError = class extends Error {
80
+ constructor(message, slide, details) {
81
+ super(message);
82
+ this.slide = slide;
83
+ this.details = details;
84
+ this.name = "TransformError";
85
+ }
86
+ };
87
+ var Transformer = class {
88
+ constructor(templateEngine, templateLoader, iconResolver, citationFormatter) {
89
+ this.templateEngine = templateEngine;
90
+ this.templateLoader = templateLoader;
91
+ this.iconResolver = iconResolver;
92
+ this.citationFormatter = citationFormatter;
93
+ }
94
+ /**
95
+ * Transform a single slide using its template
96
+ */
97
+ async transform(slide, context) {
98
+ if (slide.template === "raw") {
99
+ return slide.raw ?? "";
100
+ }
101
+ const template = this.templateLoader.get(slide.template);
102
+ if (!template) {
103
+ throw new TransformError(
104
+ `Template "${slide.template}" not found`,
105
+ slide
106
+ );
107
+ }
108
+ const validationResult = this.templateLoader.validateContent(
109
+ slide.template,
110
+ slide.content
111
+ );
112
+ if (!validationResult.valid) {
113
+ throw new TransformError(
114
+ `Slide content validation failed: ${validationResult.errors?.join(", ")}`,
115
+ slide,
116
+ validationResult.errors
117
+ );
118
+ }
119
+ const pending = {
120
+ icons: /* @__PURE__ */ new Map(),
121
+ cites: /* @__PURE__ */ new Map(),
122
+ expands: /* @__PURE__ */ new Map()
123
+ };
124
+ const templateContext = this.buildTemplateContext(slide, context, pending);
125
+ let output = this.templateEngine.render(template.output, templateContext);
126
+ output = await this.resolvePlaceholders(output, pending);
127
+ if (slide.class) {
128
+ output = `<!-- _class: ${slide.class} -->
129
+ ${output}`;
130
+ }
131
+ return output.trim();
132
+ }
133
+ /**
134
+ * Transform all slides in a presentation
135
+ */
136
+ async transformAll(presentation) {
137
+ const results = [];
138
+ const totalSlides = presentation.slides.length;
139
+ for (let i = 0; i < presentation.slides.length; i++) {
140
+ const slide = presentation.slides[i];
141
+ const context = {
142
+ meta: presentation.meta,
143
+ slideIndex: i,
144
+ totalSlides
145
+ };
146
+ const transformed = await this.transform(slide, context);
147
+ results.push(transformed);
148
+ }
149
+ return results;
150
+ }
151
+ /**
152
+ * Build the full template context with helpers that collect async operations
153
+ */
154
+ buildTemplateContext(slide, context, pending) {
155
+ let iconCounter = 0;
156
+ let citeCounter = 0;
157
+ let expandCounter = 0;
158
+ const icons = {
159
+ render: (name, options) => {
160
+ const id = `${iconCounter++}`;
161
+ const placeholder = `${ICON_PLACEHOLDER_PREFIX}${id}${ICON_PLACEHOLDER_SUFFIX}`;
162
+ pending.icons.set(id, { name, options });
163
+ return placeholder;
164
+ }
165
+ };
166
+ const refs = {
167
+ cite: (id) => {
168
+ const counterId = `${citeCounter++}`;
169
+ const placeholder = `${REFS_CITE_PLACEHOLDER_PREFIX}${counterId}${REFS_CITE_PLACEHOLDER_SUFFIX}`;
170
+ pending.cites.set(counterId, id);
171
+ return placeholder;
172
+ },
173
+ expand: (text) => {
174
+ const counterId = `${expandCounter++}`;
175
+ const placeholder = `${REFS_EXPAND_PLACEHOLDER_PREFIX}${counterId}${REFS_EXPAND_PLACEHOLDER_SUFFIX}`;
176
+ pending.expands.set(counterId, text);
177
+ return placeholder;
178
+ }
179
+ };
180
+ return {
181
+ content: slide.content,
182
+ meta: {
183
+ title: context.meta.title,
184
+ author: context.meta.author,
185
+ theme: context.meta.theme
186
+ },
187
+ slide: {
188
+ index: context.slideIndex,
189
+ total: context.totalSlides
190
+ },
191
+ icons,
192
+ refs
193
+ };
194
+ }
195
+ /**
196
+ * Resolve all placeholders by executing async operations
197
+ */
198
+ async resolvePlaceholders(output, pending) {
199
+ const iconResults = /* @__PURE__ */ new Map();
200
+ for (const [id, { name, options }] of pending.icons) {
201
+ const rendered = await this.iconResolver.render(
202
+ name,
203
+ options
204
+ );
205
+ iconResults.set(id, rendered);
206
+ }
207
+ const citeResults = /* @__PURE__ */ new Map();
208
+ for (const [counterId, id] of pending.cites) {
209
+ const formatted = await this.citationFormatter.formatInline(id);
210
+ citeResults.set(counterId, formatted);
211
+ }
212
+ const expandResults = /* @__PURE__ */ new Map();
213
+ for (const [counterId, text] of pending.expands) {
214
+ const expanded = await this.citationFormatter.expandCitations(text);
215
+ expandResults.set(counterId, expanded);
216
+ }
217
+ let result = output;
218
+ for (const [id, rendered] of iconResults) {
219
+ const placeholder = `${ICON_PLACEHOLDER_PREFIX}${id}${ICON_PLACEHOLDER_SUFFIX}`;
220
+ result = result.replace(placeholder, rendered);
221
+ }
222
+ for (const [counterId, formatted] of citeResults) {
223
+ const placeholder = `${REFS_CITE_PLACEHOLDER_PREFIX}${counterId}${REFS_CITE_PLACEHOLDER_SUFFIX}`;
224
+ result = result.replace(placeholder, formatted);
225
+ }
226
+ for (const [counterId, expanded] of expandResults) {
227
+ const placeholder = `${REFS_EXPAND_PLACEHOLDER_PREFIX}${counterId}${REFS_EXPAND_PLACEHOLDER_SUFFIX}`;
228
+ result = result.replace(placeholder, expanded);
229
+ }
230
+ return result;
231
+ }
232
+ };
233
+
234
+ // src/core/renderer.ts
235
+ var Renderer = class {
236
+ /**
237
+ * Render slides and metadata into final Marp markdown
238
+ */
239
+ render(slides, meta, options) {
240
+ const frontMatter = this.renderFrontMatter(meta, options);
241
+ const slidesContent = this.joinSlides(slides, options?.notes);
242
+ if (slides.length === 0) {
243
+ return frontMatter;
244
+ }
245
+ return `${frontMatter}
246
+
247
+ ${slidesContent}`;
248
+ }
249
+ /**
250
+ * Render the YAML front matter block
251
+ */
252
+ renderFrontMatter(meta, options) {
253
+ const lines = ["---", "marp: true"];
254
+ lines.push(`title: ${meta.title}`);
255
+ if (meta.author) {
256
+ lines.push(`author: ${meta.author}`);
257
+ }
258
+ if (meta.date) {
259
+ lines.push(`date: ${meta.date}`);
260
+ }
261
+ const includeTheme = options?.includeTheme ?? true;
262
+ if (includeTheme && meta.theme) {
263
+ lines.push(`theme: ${meta.theme}`);
264
+ }
265
+ if (options?.additionalFrontMatter) {
266
+ for (const [key, value] of Object.entries(options.additionalFrontMatter)) {
267
+ lines.push(`${key}: ${this.formatFrontMatterValue(value)}`);
268
+ }
269
+ }
270
+ lines.push("---");
271
+ return lines.join("\n");
272
+ }
273
+ /**
274
+ * Format a front matter value for YAML
275
+ */
276
+ formatFrontMatterValue(value) {
277
+ if (typeof value === "boolean") {
278
+ return value ? "true" : "false";
279
+ }
280
+ if (typeof value === "number") {
281
+ return String(value);
282
+ }
283
+ if (typeof value === "string") {
284
+ if (/[:#[\]{}|>]/.test(value)) {
285
+ return `"${value.replace(/"/g, '\\"')}"`;
286
+ }
287
+ return value;
288
+ }
289
+ return String(value);
290
+ }
291
+ /**
292
+ * Join slides with Marp slide separator
293
+ */
294
+ joinSlides(slides, notes) {
295
+ const parts = [];
296
+ for (let i = 0; i < slides.length; i++) {
297
+ let slideContent = slides[i];
298
+ const note = notes?.[i];
299
+ if (note && note.trim()) {
300
+ slideContent = `${slideContent}
301
+
302
+ ${this.renderSpeakerNotes(note)}`;
303
+ }
304
+ parts.push(slideContent);
305
+ }
306
+ return parts.map((slide) => `---
307
+
308
+ ${slide}`).join("\n\n");
309
+ }
310
+ /**
311
+ * Render speaker notes as HTML comment
312
+ */
313
+ renderSpeakerNotes(notes) {
314
+ return `<!--
315
+ ${notes}
316
+ -->`;
317
+ }
318
+ };
319
+
320
+ // src/core/pipeline.ts
321
+ import { writeFile } from "fs/promises";
322
+
323
+ // src/templates/engine.ts
324
+ import nunjucks from "nunjucks";
325
+ var TemplateEngine = class {
326
+ env;
327
+ constructor() {
328
+ this.env = new nunjucks.Environment(null, {
329
+ autoescape: false,
330
+ // HTML output for Marp
331
+ throwOnUndefined: false
332
+ });
333
+ this.registerFilters();
334
+ this.registerGlobals();
335
+ }
336
+ render(template, context) {
337
+ return this.env.renderString(template, context);
338
+ }
339
+ registerFilters() {
340
+ }
341
+ registerGlobals() {
342
+ const icons = {
343
+ render: (name, options) => {
344
+ const size = options?.["size"] ?? "24px";
345
+ const color = options?.["color"] ?? "currentColor";
346
+ return `<span class="icon icon-${name}" style="font-size: ${size}; color: ${color};">[${name}]</span>`;
347
+ }
348
+ };
349
+ this.env.addGlobal("icons", icons);
350
+ const refs = {
351
+ cite: (id) => {
352
+ const cleanId = id.replace("@", "");
353
+ return `(${cleanId})`;
354
+ },
355
+ expand: (text) => {
356
+ return text.replace(/\[@(\w+)\]/g, "($1)");
357
+ }
358
+ };
359
+ this.env.addGlobal("refs", refs);
360
+ }
361
+ };
362
+
363
+ // src/templates/loader.ts
364
+ import { z as z3 } from "zod";
365
+ import * as yaml from "yaml";
366
+ import * as fs from "fs/promises";
367
+ import * as path from "path";
368
+
369
+ // src/templates/validators.ts
370
+ import { z as z2 } from "zod";
371
+ function jsonSchemaToZod(schema) {
372
+ if (schema.oneOf && schema.oneOf.length > 0) {
373
+ const schemas = schema.oneOf.map((s) => jsonSchemaToZod(s));
374
+ if (schemas.length === 1) {
375
+ return schemas[0];
376
+ }
377
+ return z2.union(schemas);
378
+ }
379
+ const type = schema.type ?? "object";
380
+ switch (type) {
381
+ case "string": {
382
+ if (schema.enum && schema.enum.length > 0) {
383
+ const enumValues = schema.enum;
384
+ return z2.enum(enumValues);
385
+ }
386
+ let zodSchema = z2.string();
387
+ if (schema.pattern) {
388
+ zodSchema = zodSchema.regex(new RegExp(schema.pattern));
389
+ }
390
+ return zodSchema;
391
+ }
392
+ case "number":
393
+ return z2.number();
394
+ case "integer":
395
+ return z2.number().int();
396
+ case "boolean":
397
+ return z2.boolean();
398
+ case "array": {
399
+ const itemSchema = schema.items ? jsonSchemaToZod(schema.items) : z2.unknown();
400
+ let arraySchema = z2.array(itemSchema);
401
+ if (schema.minItems !== void 0) {
402
+ arraySchema = arraySchema.min(schema.minItems);
403
+ }
404
+ if (schema.maxItems !== void 0) {
405
+ arraySchema = arraySchema.max(schema.maxItems);
406
+ }
407
+ return arraySchema;
408
+ }
409
+ case "object": {
410
+ if (!schema.properties) {
411
+ return z2.record(z2.unknown());
412
+ }
413
+ const shape = {};
414
+ const required = new Set(schema.required ?? []);
415
+ for (const [key, propSchema] of Object.entries(schema.properties)) {
416
+ const propZod = jsonSchemaToZod(propSchema);
417
+ shape[key] = required.has(key) ? propZod : propZod.optional();
418
+ }
419
+ return z2.object(shape).passthrough();
420
+ }
421
+ default:
422
+ return z2.unknown();
423
+ }
424
+ }
425
+ function validateWithJsonSchema(schema, content) {
426
+ const zodSchema = jsonSchemaToZod(schema);
427
+ const result = zodSchema.safeParse(content);
428
+ if (result.success) {
429
+ return { valid: true, errors: [] };
430
+ }
431
+ const errors = result.error.errors.map((issue) => {
432
+ const path3 = issue.path.join(".");
433
+ return path3 ? `${path3}: ${issue.message}` : issue.message;
434
+ });
435
+ return { valid: false, errors };
436
+ }
437
+
438
+ // src/templates/loader.ts
439
+ var jsonSchemaSchema = z3.record(z3.unknown());
440
+ var templateDefSchema = z3.object({
441
+ name: z3.string().min(1, "Template name is required"),
442
+ description: z3.string(),
443
+ category: z3.string(),
444
+ schema: jsonSchemaSchema,
445
+ example: z3.record(z3.unknown()).optional(),
446
+ output: z3.string().min(1, "Template output is required"),
447
+ css: z3.string().optional()
448
+ });
449
+ var TemplateLoader = class {
450
+ templates;
451
+ constructor() {
452
+ this.templates = /* @__PURE__ */ new Map();
453
+ }
454
+ /**
455
+ * Load a template from a YAML string
456
+ */
457
+ async loadFromString(yamlContent) {
458
+ const parsed = yaml.parse(yamlContent);
459
+ const result = templateDefSchema.safeParse(parsed);
460
+ if (!result.success) {
461
+ const errors = result.error.errors.map((e) => `${e.path.join(".")}: ${e.message}`).join(", ");
462
+ throw new Error(`Invalid template definition: ${errors}`);
463
+ }
464
+ this.templates.set(result.data.name, result.data);
465
+ }
466
+ /**
467
+ * Load a template from a file
468
+ */
469
+ async loadFromFile(filePath) {
470
+ const content = await fs.readFile(filePath, "utf-8");
471
+ await this.loadFromString(content);
472
+ }
473
+ /**
474
+ * Load all templates from a directory (recursively)
475
+ */
476
+ async loadBuiltIn(directory) {
477
+ await this.loadDirectory(directory);
478
+ }
479
+ /**
480
+ * Load custom templates from a directory (can override built-in)
481
+ */
482
+ async loadCustom(directory) {
483
+ await this.loadDirectory(directory);
484
+ }
485
+ /**
486
+ * Internal method to load templates from a directory
487
+ */
488
+ async loadDirectory(directory) {
489
+ const entries = await fs.readdir(directory, { withFileTypes: true });
490
+ for (const entry of entries) {
491
+ const fullPath = path.join(directory, entry.name);
492
+ if (entry.isDirectory()) {
493
+ await this.loadDirectory(fullPath);
494
+ } else if (entry.isFile() && (entry.name.endsWith(".yaml") || entry.name.endsWith(".yml"))) {
495
+ await this.loadFromFile(fullPath);
496
+ }
497
+ }
498
+ }
499
+ /**
500
+ * Get a template by name
501
+ */
502
+ get(name) {
503
+ return this.templates.get(name);
504
+ }
505
+ /**
506
+ * List all loaded templates
507
+ */
508
+ list() {
509
+ return Array.from(this.templates.values());
510
+ }
511
+ /**
512
+ * List templates filtered by category
513
+ */
514
+ listByCategory(category) {
515
+ return this.list().filter((t) => t.category === category);
516
+ }
517
+ /**
518
+ * Validate content against a template's schema
519
+ */
520
+ validateContent(templateName, content) {
521
+ const template = this.get(templateName);
522
+ if (!template) {
523
+ return {
524
+ valid: false,
525
+ errors: [`Template "${templateName}" not found`]
526
+ };
527
+ }
528
+ return validateWithJsonSchema(template.schema, content);
529
+ }
530
+ };
531
+
532
+ // src/icons/registry.ts
533
+ import * as fs2 from "fs/promises";
534
+ import { parse as parseYaml2 } from "yaml";
535
+
536
+ // src/icons/schema.ts
537
+ import { z as z4 } from "zod";
538
+ var iconSourceTypeSchema = z4.enum([
539
+ "web-font",
540
+ "svg-inline",
541
+ "svg-sprite",
542
+ "local-svg"
543
+ ]);
544
+ var iconSourceSchema = z4.object({
545
+ name: z4.string(),
546
+ type: iconSourceTypeSchema,
547
+ prefix: z4.string(),
548
+ url: z4.string().optional(),
549
+ path: z4.string().optional(),
550
+ render: z4.string().optional()
551
+ });
552
+ var iconDefaultsSchema = z4.object({
553
+ size: z4.string().default("24px"),
554
+ color: z4.string().default("currentColor")
555
+ });
556
+ var iconRegistrySchema = z4.object({
557
+ sources: z4.array(iconSourceSchema),
558
+ aliases: z4.record(z4.string()).default({}),
559
+ colors: z4.record(z4.string()).optional(),
560
+ defaults: iconDefaultsSchema.default({})
561
+ });
562
+
563
+ // src/icons/registry.ts
564
+ var IconRegistryLoader = class {
565
+ registry = null;
566
+ sourcesByPrefix = /* @__PURE__ */ new Map();
567
+ aliasMap = /* @__PURE__ */ new Map();
568
+ colorMap = /* @__PURE__ */ new Map();
569
+ /**
570
+ * Load registry from YAML file
571
+ */
572
+ async load(configPath) {
573
+ const content = await fs2.readFile(configPath, "utf-8");
574
+ const parsed = parseYaml2(content);
575
+ const validated = iconRegistrySchema.parse(parsed);
576
+ this.registry = validated;
577
+ this.buildMaps();
578
+ return validated;
579
+ }
580
+ /**
581
+ * Resolve an alias to its icon reference
582
+ * @returns The resolved icon reference or the original name if not an alias
583
+ */
584
+ resolveAlias(nameOrAlias) {
585
+ return this.aliasMap.get(nameOrAlias) ?? nameOrAlias;
586
+ }
587
+ /**
588
+ * Get icon source by prefix
589
+ */
590
+ getSource(prefix) {
591
+ return this.sourcesByPrefix.get(prefix);
592
+ }
593
+ /**
594
+ * Parse an icon reference string (e.g., "mi:home" or "iconify:mdi:account")
595
+ * @returns Parsed reference or null if invalid format
596
+ */
597
+ parseIconReference(reference) {
598
+ const colonIndex = reference.indexOf(":");
599
+ if (colonIndex === -1) {
600
+ return null;
601
+ }
602
+ const prefix = reference.substring(0, colonIndex);
603
+ const name = reference.substring(colonIndex + 1);
604
+ return { prefix, name };
605
+ }
606
+ /**
607
+ * Get registry defaults
608
+ */
609
+ getDefaults() {
610
+ if (!this.registry) {
611
+ return { size: "24px", color: "currentColor" };
612
+ }
613
+ return this.registry.defaults;
614
+ }
615
+ /**
616
+ * Get color by name from color palette
617
+ */
618
+ getColor(name) {
619
+ return this.colorMap.get(name);
620
+ }
621
+ /**
622
+ * Get all sources
623
+ */
624
+ getSources() {
625
+ return this.registry?.sources ?? [];
626
+ }
627
+ /**
628
+ * Get all aliases
629
+ */
630
+ getAliases() {
631
+ return this.registry?.aliases ?? {};
632
+ }
633
+ /**
634
+ * Check if registry is loaded
635
+ */
636
+ isLoaded() {
637
+ return this.registry !== null;
638
+ }
639
+ /**
640
+ * Build internal lookup maps from registry
641
+ */
642
+ buildMaps() {
643
+ this.sourcesByPrefix.clear();
644
+ this.aliasMap.clear();
645
+ this.colorMap.clear();
646
+ if (!this.registry) {
647
+ return;
648
+ }
649
+ for (const source of this.registry.sources) {
650
+ this.sourcesByPrefix.set(source.prefix, source);
651
+ }
652
+ for (const [alias, target] of Object.entries(this.registry.aliases)) {
653
+ this.aliasMap.set(alias, target);
654
+ }
655
+ if (this.registry.colors) {
656
+ for (const [name, color] of Object.entries(this.registry.colors)) {
657
+ this.colorMap.set(name, color);
658
+ }
659
+ }
660
+ }
661
+ };
662
+
663
+ // src/icons/resolver.ts
664
+ import * as fs3 from "fs/promises";
665
+ import * as path2 from "path";
666
+ import nunjucks2 from "nunjucks";
667
+ var IconResolver = class {
668
+ constructor(registry) {
669
+ this.registry = registry;
670
+ this.nunjucksEnv = new nunjucks2.Environment(null, {
671
+ autoescape: false
672
+ });
673
+ }
674
+ nunjucksEnv;
675
+ /**
676
+ * Render an icon by name or alias
677
+ */
678
+ async render(nameOrAlias, options) {
679
+ const resolved = this.registry.resolveAlias(nameOrAlias);
680
+ const parsed = this.registry.parseIconReference(resolved);
681
+ if (!parsed) {
682
+ throw new Error(
683
+ `Invalid icon reference format: "${resolved}". Expected format: "prefix:name"`
684
+ );
685
+ }
686
+ const source = this.registry.getSource(parsed.prefix);
687
+ if (!source) {
688
+ throw new Error(`Unknown icon source prefix: "${parsed.prefix}"`);
689
+ }
690
+ const defaults = this.registry.getDefaults();
691
+ const mergedOptions = {
692
+ size: options?.size ?? defaults.size,
693
+ color: options?.color ?? defaults.color,
694
+ ...options?.class !== void 0 ? { class: options.class } : {}
695
+ };
696
+ switch (source.type) {
697
+ case "web-font":
698
+ return this.renderWebFont(source, parsed.name, mergedOptions);
699
+ case "local-svg":
700
+ return await this.renderLocalSvg(source, parsed.name, mergedOptions);
701
+ case "svg-inline":
702
+ return await this.renderSvgInline(source, parsed.name, mergedOptions);
703
+ case "svg-sprite":
704
+ return this.renderSvgSprite(source, parsed.name, mergedOptions);
705
+ default:
706
+ throw new Error(`Unsupported icon source type: "${source.type}"`);
707
+ }
708
+ }
709
+ /**
710
+ * Render a web-font icon
711
+ */
712
+ renderWebFont(source, name, options) {
713
+ const style = this.buildStyle(options);
714
+ const className = this.buildClassName(name, options);
715
+ if (source.render) {
716
+ return this.nunjucksEnv.renderString(source.render, {
717
+ name,
718
+ style,
719
+ class: className,
720
+ size: options.size,
721
+ color: options.color
722
+ });
723
+ }
724
+ return `<span class="${className}" style="${style}">${name}</span>`;
725
+ }
726
+ /**
727
+ * Render a local SVG icon
728
+ */
729
+ async renderLocalSvg(source, name, options) {
730
+ if (!source.path) {
731
+ throw new Error(`Local SVG source "${source.name}" has no path defined`);
732
+ }
733
+ const svgPath = path2.join(source.path, `${name}.svg`);
734
+ try {
735
+ const svgContent = await fs3.readFile(svgPath, "utf-8");
736
+ return this.processSvg(svgContent, name, options);
737
+ } catch (error) {
738
+ if (error.code === "ENOENT") {
739
+ throw new Error(`Icon file not found: ${svgPath}`);
740
+ }
741
+ throw error;
742
+ }
743
+ }
744
+ /**
745
+ * Render an SVG from external URL (placeholder without cache)
746
+ */
747
+ async renderSvgInline(source, name, options) {
748
+ const className = this.buildClassName(name, options);
749
+ const style = this.buildStyle(options);
750
+ return `<span class="${className}" style="${style}" data-icon-source="${source.name}" data-icon-name="${name}">[${name}]</span>`;
751
+ }
752
+ /**
753
+ * Render an SVG sprite reference
754
+ */
755
+ renderSvgSprite(source, name, options) {
756
+ const className = this.buildClassName(name, options);
757
+ const size = options.size ?? "24px";
758
+ const color = options.color ?? "currentColor";
759
+ const spriteUrl = source.url ?? "";
760
+ return `<svg class="${className}" width="${size}" height="${size}" fill="${color}">
761
+ <use xlink:href="${spriteUrl}#${name}"/>
762
+ </svg>`;
763
+ }
764
+ /**
765
+ * Process SVG content and apply options
766
+ */
767
+ processSvg(svgContent, name, options) {
768
+ const className = this.buildClassName(name, options);
769
+ const size = options.size ?? "24px";
770
+ const color = options.color ?? "currentColor";
771
+ let processed = svgContent.trim();
772
+ if (processed.includes("class=")) {
773
+ processed = processed.replace(/class="[^"]*"/, `class="${className}"`);
774
+ } else {
775
+ processed = processed.replace("<svg", `<svg class="${className}"`);
776
+ }
777
+ if (processed.includes("width=")) {
778
+ processed = processed.replace(/width="[^"]*"/, `width="${size}"`);
779
+ } else {
780
+ processed = processed.replace("<svg", `<svg width="${size}"`);
781
+ }
782
+ if (processed.includes("height=")) {
783
+ processed = processed.replace(/height="[^"]*"/, `height="${size}"`);
784
+ } else {
785
+ processed = processed.replace("<svg", `<svg height="${size}"`);
786
+ }
787
+ if (color !== "currentColor") {
788
+ processed = processed.replace(/fill="currentColor"/g, `fill="${color}"`);
789
+ }
790
+ return processed;
791
+ }
792
+ /**
793
+ * Build CSS style string
794
+ */
795
+ buildStyle(options) {
796
+ const styles = [];
797
+ if (options.size) {
798
+ styles.push(`font-size: ${options.size}`);
799
+ }
800
+ if (options.color) {
801
+ styles.push(`color: ${options.color}`);
802
+ }
803
+ return styles.join("; ");
804
+ }
805
+ /**
806
+ * Build class name string
807
+ */
808
+ buildClassName(name, options) {
809
+ const classes = ["icon", `icon-${name}`];
810
+ if (options.class) {
811
+ classes.push(options.class);
812
+ }
813
+ return classes.join(" ");
814
+ }
815
+ };
816
+
817
+ // src/references/manager.ts
818
+ import { exec } from "child_process";
819
+ var ReferenceManagerError = class extends Error {
820
+ constructor(message, cause) {
821
+ super(message);
822
+ this.cause = cause;
823
+ this.name = "ReferenceManagerError";
824
+ }
825
+ };
826
+ var ReferenceManager = class {
827
+ constructor(command = "ref") {
828
+ this.command = command;
829
+ }
830
+ /**
831
+ * Check if reference-manager CLI is available
832
+ */
833
+ async isAvailable() {
834
+ try {
835
+ await this.execCommand(`${this.command} --version`);
836
+ return true;
837
+ } catch {
838
+ return false;
839
+ }
840
+ }
841
+ /**
842
+ * Get all references from the library
843
+ */
844
+ async getAll() {
845
+ const result = await this.execCommand(`${this.command} list --format json`);
846
+ return this.parseJSON(result);
847
+ }
848
+ /**
849
+ * Get a single reference by ID
850
+ */
851
+ async getById(id) {
852
+ const result = await this.execCommand(
853
+ `${this.command} list --id ${id} --format json`
854
+ );
855
+ const items = this.parseJSON(result);
856
+ return items[0] || null;
857
+ }
858
+ /**
859
+ * Get multiple references by IDs
860
+ */
861
+ async getByIds(ids) {
862
+ if (ids.length === 0) {
863
+ return /* @__PURE__ */ new Map();
864
+ }
865
+ const result = await this.execCommand(`${this.command} list --format json`);
866
+ const allItems = this.parseJSON(result);
867
+ const idSet = new Set(ids);
868
+ const map = /* @__PURE__ */ new Map();
869
+ for (const item of allItems) {
870
+ if (idSet.has(item.id)) {
871
+ map.set(item.id, item);
872
+ }
873
+ }
874
+ return map;
875
+ }
876
+ execCommand(cmd) {
877
+ return new Promise((resolve, reject) => {
878
+ exec(cmd, (error, stdout) => {
879
+ if (error) {
880
+ reject(
881
+ new ReferenceManagerError(`Failed to execute: ${cmd}`, error)
882
+ );
883
+ return;
884
+ }
885
+ resolve(stdout.toString());
886
+ });
887
+ });
888
+ }
889
+ parseJSON(data) {
890
+ try {
891
+ return JSON.parse(data);
892
+ } catch (error) {
893
+ throw new ReferenceManagerError(
894
+ "Failed to parse reference-manager output as JSON",
895
+ error
896
+ );
897
+ }
898
+ }
899
+ };
900
+
901
+ // src/references/extractor.ts
902
+ var SINGLE_CITATION_PATTERN = /@([\w-]+)(?:,\s*([^;\]]+))?/g;
903
+ var CITATION_BRACKET_PATTERN = /\[(@[\w-]+(?:,\s*[^;\]]+)?(?:;\s*@[\w-]+(?:,\s*[^;\]]+)?)*)\]/g;
904
+ var SOURCE_CITATION_PATTERN = /^@([\w-]+)$/;
905
+ var CitationExtractor = class {
906
+ /**
907
+ * Extract citations from a text string
908
+ */
909
+ extract(text) {
910
+ const citations = [];
911
+ let bracketMatch;
912
+ CITATION_BRACKET_PATTERN.lastIndex = 0;
913
+ while ((bracketMatch = CITATION_BRACKET_PATTERN.exec(text)) !== null) {
914
+ const bracketStart = bracketMatch.index;
915
+ const bracketContent = bracketMatch[1];
916
+ SINGLE_CITATION_PATTERN.lastIndex = 0;
917
+ let singleMatch;
918
+ while ((singleMatch = SINGLE_CITATION_PATTERN.exec(bracketContent)) !== null) {
919
+ const id = singleMatch[1];
920
+ const locator = singleMatch[2]?.trim();
921
+ citations.push({
922
+ id,
923
+ locator: locator ?? void 0,
924
+ position: {
925
+ start: bracketStart,
926
+ end: bracketStart + bracketMatch[0].length
927
+ }
928
+ });
929
+ }
930
+ }
931
+ return citations;
932
+ }
933
+ /**
934
+ * Extract citations from a slide's content
935
+ */
936
+ extractFromSlide(slide) {
937
+ const allCitations = [];
938
+ const seenIds = /* @__PURE__ */ new Set();
939
+ const extractFromValue = (value) => {
940
+ if (typeof value === "string") {
941
+ const sourceMatch = SOURCE_CITATION_PATTERN.exec(value);
942
+ if (sourceMatch && sourceMatch[1]) {
943
+ const id = sourceMatch[1];
944
+ if (!seenIds.has(id)) {
945
+ seenIds.add(id);
946
+ allCitations.push({
947
+ id,
948
+ locator: void 0,
949
+ position: { start: 0, end: value.length }
950
+ });
951
+ }
952
+ return;
953
+ }
954
+ const citations = this.extract(value);
955
+ for (const citation of citations) {
956
+ if (!seenIds.has(citation.id)) {
957
+ seenIds.add(citation.id);
958
+ allCitations.push(citation);
959
+ }
960
+ }
961
+ } else if (Array.isArray(value)) {
962
+ for (const item of value) {
963
+ extractFromValue(item);
964
+ }
965
+ } else if (value && typeof value === "object") {
966
+ for (const v of Object.values(value)) {
967
+ extractFromValue(v);
968
+ }
969
+ }
970
+ };
971
+ extractFromValue(slide.content);
972
+ if (slide.notes) {
973
+ const notesCitations = this.extract(slide.notes);
974
+ for (const citation of notesCitations) {
975
+ if (!seenIds.has(citation.id)) {
976
+ seenIds.add(citation.id);
977
+ allCitations.push(citation);
978
+ }
979
+ }
980
+ }
981
+ return allCitations;
982
+ }
983
+ /**
984
+ * Extract citations from all slides in a presentation
985
+ */
986
+ extractFromPresentation(presentation) {
987
+ const allCitations = [];
988
+ const seenIds = /* @__PURE__ */ new Set();
989
+ for (const slide of presentation.slides) {
990
+ const slideCitations = this.extractFromSlide(slide);
991
+ for (const citation of slideCitations) {
992
+ if (!seenIds.has(citation.id)) {
993
+ seenIds.add(citation.id);
994
+ allCitations.push(citation);
995
+ }
996
+ }
997
+ }
998
+ return allCitations;
999
+ }
1000
+ /**
1001
+ * Get unique citation IDs in order of first appearance
1002
+ */
1003
+ getUniqueIds(citations) {
1004
+ const seen = /* @__PURE__ */ new Set();
1005
+ const uniqueIds = [];
1006
+ for (const citation of citations) {
1007
+ if (!seen.has(citation.id)) {
1008
+ seen.add(citation.id);
1009
+ uniqueIds.push(citation.id);
1010
+ }
1011
+ }
1012
+ return uniqueIds;
1013
+ }
1014
+ };
1015
+
1016
+ // src/references/formatter.ts
1017
+ var DEFAULT_CONFIG = {
1018
+ author: {
1019
+ maxAuthors: 2,
1020
+ etAl: "et al.",
1021
+ etAlJa: "\u307B\u304B",
1022
+ separatorJa: "\u30FB"
1023
+ },
1024
+ inline: {
1025
+ authorSep: ", ",
1026
+ identifierSep: "; ",
1027
+ multiSep: "), ("
1028
+ }
1029
+ };
1030
+ var JAPANESE_PATTERN = /[\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FAF]/;
1031
+ var CITATION_BRACKET_PATTERN2 = /\[(@[\w-]+(?:,\s*[^;\]]+)?(?:;\s*@[\w-]+(?:,\s*[^;\]]+)?)*)\]/g;
1032
+ var SINGLE_CITATION_PATTERN2 = /@([\w-]+)(?:,\s*([^;\]]+))?/g;
1033
+ var CitationFormatter = class {
1034
+ constructor(manager, config) {
1035
+ this.manager = manager;
1036
+ this.config = {
1037
+ author: { ...DEFAULT_CONFIG.author, ...config?.author },
1038
+ inline: { ...DEFAULT_CONFIG.inline, ...config?.inline }
1039
+ };
1040
+ }
1041
+ config;
1042
+ /**
1043
+ * Format an inline citation
1044
+ * e.g., "(Smith et al., 2024; PMID: 12345678)"
1045
+ */
1046
+ async formatInline(id) {
1047
+ const item = await this.manager.getById(id);
1048
+ if (!item) {
1049
+ return `[${id}]`;
1050
+ }
1051
+ return this.formatInlineItem(item);
1052
+ }
1053
+ /**
1054
+ * Format a full bibliography citation
1055
+ */
1056
+ async formatFull(id) {
1057
+ const item = await this.manager.getById(id);
1058
+ if (!item) {
1059
+ return `[${id}]`;
1060
+ }
1061
+ return this.formatFullItem(item);
1062
+ }
1063
+ /**
1064
+ * Expand all citations in text
1065
+ * e.g., "[@smith2024]" -> "(Smith et al., 2024; PMID: 12345678)"
1066
+ */
1067
+ async expandCitations(text) {
1068
+ const ids = /* @__PURE__ */ new Set();
1069
+ CITATION_BRACKET_PATTERN2.lastIndex = 0;
1070
+ let match;
1071
+ while ((match = CITATION_BRACKET_PATTERN2.exec(text)) !== null) {
1072
+ const content = match[1];
1073
+ SINGLE_CITATION_PATTERN2.lastIndex = 0;
1074
+ let singleMatch;
1075
+ while ((singleMatch = SINGLE_CITATION_PATTERN2.exec(content)) !== null) {
1076
+ ids.add(singleMatch[1]);
1077
+ }
1078
+ }
1079
+ const items = await this.manager.getByIds([...ids]);
1080
+ CITATION_BRACKET_PATTERN2.lastIndex = 0;
1081
+ let result = text;
1082
+ const matches = [];
1083
+ CITATION_BRACKET_PATTERN2.lastIndex = 0;
1084
+ while ((match = CITATION_BRACKET_PATTERN2.exec(text)) !== null) {
1085
+ const content = match[1];
1086
+ const replacements = [];
1087
+ SINGLE_CITATION_PATTERN2.lastIndex = 0;
1088
+ let singleMatch;
1089
+ while ((singleMatch = SINGLE_CITATION_PATTERN2.exec(content)) !== null) {
1090
+ const id = singleMatch[1];
1091
+ const item = items.get(id);
1092
+ if (item) {
1093
+ replacements.push(this.formatInlineItem(item));
1094
+ } else {
1095
+ replacements.push(`[${id}]`);
1096
+ }
1097
+ }
1098
+ matches.push({
1099
+ start: match.index,
1100
+ end: match.index + match[0].length,
1101
+ replacement: replacements.join(", ")
1102
+ });
1103
+ }
1104
+ for (const m of [...matches].reverse()) {
1105
+ result = result.slice(0, m.start) + m.replacement + result.slice(m.end);
1106
+ }
1107
+ return result;
1108
+ }
1109
+ /**
1110
+ * Generate bibliography entries
1111
+ */
1112
+ async generateBibliography(ids, sort = "citation-order") {
1113
+ if (ids.length === 0) {
1114
+ return [];
1115
+ }
1116
+ const items = await this.manager.getByIds(ids);
1117
+ let sortedItems;
1118
+ if (sort === "citation-order") {
1119
+ sortedItems = ids.map((id) => items.get(id)).filter((item) => item !== void 0);
1120
+ } else if (sort === "author") {
1121
+ sortedItems = [...items.values()].sort((a, b) => {
1122
+ const authorA = this.getFirstAuthorFamily(a);
1123
+ const authorB = this.getFirstAuthorFamily(b);
1124
+ return authorA.localeCompare(authorB);
1125
+ });
1126
+ } else {
1127
+ sortedItems = [...items.values()].sort((a, b) => {
1128
+ const yearA = this.getYear(a);
1129
+ const yearB = this.getYear(b);
1130
+ return yearA - yearB;
1131
+ });
1132
+ }
1133
+ return sortedItems.map((item) => this.formatFullItem(item));
1134
+ }
1135
+ formatInlineItem(item) {
1136
+ const author = this.formatAuthorInline(item.author);
1137
+ const year = this.getYear(item);
1138
+ const identifier = this.getIdentifier(item);
1139
+ if (identifier) {
1140
+ return `(${author}, ${year}; ${identifier})`;
1141
+ }
1142
+ return `(${author}, ${year})`;
1143
+ }
1144
+ formatFullItem(item) {
1145
+ const parts = [];
1146
+ const isJapanese = this.isJapaneseAuthors(item.author);
1147
+ const authors = this.formatAuthorsFull(item.author, isJapanese);
1148
+ parts.push(authors);
1149
+ const year = this.getYear(item);
1150
+ parts.push(`(${year}).`);
1151
+ if (item.title) {
1152
+ parts.push(`${item.title}.`);
1153
+ }
1154
+ if (item["container-title"]) {
1155
+ const journal = isJapanese ? item["container-title"] : `*${item["container-title"]}*`;
1156
+ let location = "";
1157
+ if (item.volume) {
1158
+ location = item.issue ? `${item.volume}(${item.issue})` : item.volume;
1159
+ }
1160
+ if (item.page) {
1161
+ location = location ? `${location}, ${item.page}` : item.page;
1162
+ }
1163
+ parts.push(location ? `${journal}, ${location}.` : `${journal}.`);
1164
+ }
1165
+ const identifier = this.getIdentifier(item);
1166
+ if (identifier) {
1167
+ parts.push(identifier);
1168
+ }
1169
+ return parts.join(" ");
1170
+ }
1171
+ formatAuthorInline(authors) {
1172
+ if (!authors || authors.length === 0) {
1173
+ return "Unknown";
1174
+ }
1175
+ const isJapanese = this.isJapaneseAuthors(authors);
1176
+ const { etAl, etAlJa, separatorJa } = this.config.author;
1177
+ const firstAuthor = authors[0];
1178
+ if (authors.length === 1) {
1179
+ return firstAuthor.family;
1180
+ }
1181
+ if (authors.length === 2) {
1182
+ const separator = isJapanese ? separatorJa : " & ";
1183
+ return `${firstAuthor.family}${separator}${authors[1].family}`;
1184
+ }
1185
+ const suffix = isJapanese ? etAlJa : ` ${etAl}`;
1186
+ return `${firstAuthor.family}${suffix}`;
1187
+ }
1188
+ formatAuthorsFull(authors, isJapanese) {
1189
+ if (!authors || authors.length === 0) {
1190
+ return "Unknown";
1191
+ }
1192
+ if (isJapanese) {
1193
+ return authors.map((a) => `${a.family}${a.given || ""}`).join(", ");
1194
+ }
1195
+ if (authors.length === 1) {
1196
+ const a = authors[0];
1197
+ const initial = a.given ? `${a.given.charAt(0)}.` : "";
1198
+ return `${a.family}, ${initial}`;
1199
+ }
1200
+ const formatted = authors.map((a, i) => {
1201
+ const initial = a.given ? `${a.given.charAt(0)}.` : "";
1202
+ if (i === authors.length - 1) {
1203
+ return `& ${a.family}, ${initial}`;
1204
+ }
1205
+ return `${a.family}, ${initial}`;
1206
+ });
1207
+ return formatted.join(", ").replace(", &", ", &");
1208
+ }
1209
+ isJapaneseAuthors(authors) {
1210
+ if (!authors || authors.length === 0) {
1211
+ return false;
1212
+ }
1213
+ return JAPANESE_PATTERN.test(authors[0].family);
1214
+ }
1215
+ getFirstAuthorFamily(item) {
1216
+ return item.author?.[0]?.family || "";
1217
+ }
1218
+ getYear(item) {
1219
+ const dateParts = item.issued?.["date-parts"];
1220
+ if (dateParts && dateParts[0] && dateParts[0][0]) {
1221
+ return dateParts[0][0];
1222
+ }
1223
+ return 0;
1224
+ }
1225
+ getIdentifier(item) {
1226
+ if (item.PMID) {
1227
+ return `PMID: ${item.PMID}`;
1228
+ }
1229
+ if (item.DOI) {
1230
+ return `DOI: ${item.DOI}`;
1231
+ }
1232
+ return null;
1233
+ }
1234
+ };
1235
+
1236
+ // src/core/pipeline.ts
1237
+ var PipelineError = class extends Error {
1238
+ constructor(message, stage, details) {
1239
+ super(message);
1240
+ this.stage = stage;
1241
+ this.details = details;
1242
+ this.name = "PipelineError";
1243
+ }
1244
+ };
1245
+ var Pipeline = class {
1246
+ constructor(config) {
1247
+ this.config = config;
1248
+ this.parser = new Parser();
1249
+ this.templateEngine = new TemplateEngine();
1250
+ this.templateLoader = new TemplateLoader();
1251
+ this.iconRegistry = new IconRegistryLoader();
1252
+ this.iconResolver = new IconResolver(this.iconRegistry);
1253
+ this.referenceManager = new ReferenceManager(
1254
+ config.references.connection.command
1255
+ );
1256
+ this.citationExtractor = new CitationExtractor();
1257
+ this.citationFormatter = new CitationFormatter(
1258
+ this.referenceManager,
1259
+ {
1260
+ author: {
1261
+ maxAuthors: config.references.format.maxAuthors,
1262
+ etAl: config.references.format.etAl,
1263
+ etAlJa: config.references.format.etAlJa
1264
+ },
1265
+ inline: {
1266
+ authorSep: config.references.format.authorSep,
1267
+ identifierSep: config.references.format.identifierSep
1268
+ }
1269
+ }
1270
+ );
1271
+ this.transformer = new Transformer(
1272
+ this.templateEngine,
1273
+ this.templateLoader,
1274
+ this.iconResolver,
1275
+ this.citationFormatter
1276
+ );
1277
+ this.renderer = new Renderer();
1278
+ }
1279
+ parser;
1280
+ templateEngine;
1281
+ templateLoader;
1282
+ iconRegistry;
1283
+ iconResolver;
1284
+ referenceManager;
1285
+ citationExtractor;
1286
+ citationFormatter;
1287
+ transformer;
1288
+ renderer;
1289
+ warnings = [];
1290
+ /**
1291
+ * Run the full conversion pipeline
1292
+ */
1293
+ async run(inputPath, options) {
1294
+ this.warnings = [];
1295
+ try {
1296
+ const presentation = await this.parseSource(inputPath);
1297
+ const citationIds = this.collectCitations(presentation);
1298
+ await this.resolveReferences(citationIds);
1299
+ const transformedSlides = await this.transformSlides(presentation);
1300
+ const output = this.render(transformedSlides, presentation);
1301
+ if (options?.outputPath) {
1302
+ await writeFile(options.outputPath, output, "utf-8");
1303
+ }
1304
+ return output;
1305
+ } catch (error) {
1306
+ if (error instanceof PipelineError) {
1307
+ throw error;
1308
+ }
1309
+ throw new PipelineError(
1310
+ error instanceof Error ? error.message : "Unknown error",
1311
+ "unknown",
1312
+ error
1313
+ );
1314
+ }
1315
+ }
1316
+ /**
1317
+ * Run the full pipeline with detailed result
1318
+ */
1319
+ async runWithResult(inputPath, options) {
1320
+ this.warnings = [];
1321
+ try {
1322
+ const presentation = await this.parseSource(inputPath);
1323
+ const citationIds = this.collectCitations(presentation);
1324
+ await this.resolveReferences(citationIds);
1325
+ const transformedSlides = await this.transformSlides(presentation);
1326
+ const output = this.render(transformedSlides, presentation);
1327
+ if (options?.outputPath) {
1328
+ await writeFile(options.outputPath, output, "utf-8");
1329
+ }
1330
+ return {
1331
+ output,
1332
+ citations: citationIds,
1333
+ warnings: this.warnings,
1334
+ slideCount: presentation.slides.length
1335
+ };
1336
+ } catch (error) {
1337
+ if (error instanceof PipelineError) {
1338
+ throw error;
1339
+ }
1340
+ throw new PipelineError(
1341
+ error instanceof Error ? error.message : "Unknown error",
1342
+ "unknown",
1343
+ error
1344
+ );
1345
+ }
1346
+ }
1347
+ /**
1348
+ * Initialize the pipeline by loading templates and icon registry
1349
+ */
1350
+ async initialize() {
1351
+ try {
1352
+ await this.templateLoader.loadBuiltIn(this.config.templates.builtin);
1353
+ if (this.config.templates.custom) {
1354
+ await this.templateLoader.loadCustom(this.config.templates.custom);
1355
+ }
1356
+ await this.iconRegistry.load(this.config.icons.registry);
1357
+ } catch (error) {
1358
+ throw new PipelineError(
1359
+ `Failed to initialize pipeline: ${error instanceof Error ? error.message : "Unknown error"}`,
1360
+ "initialize",
1361
+ error
1362
+ );
1363
+ }
1364
+ }
1365
+ /**
1366
+ * Get collected warnings
1367
+ */
1368
+ getWarnings() {
1369
+ return [...this.warnings];
1370
+ }
1371
+ // --- Private Stage Methods ---
1372
+ /**
1373
+ * Stage 1: Parse the YAML source file
1374
+ */
1375
+ async parseSource(inputPath) {
1376
+ try {
1377
+ return await this.parser.parseFile(inputPath);
1378
+ } catch (error) {
1379
+ throw new PipelineError(
1380
+ `Failed to parse source file: ${error instanceof Error ? error.message : "Unknown error"}`,
1381
+ "parse",
1382
+ error
1383
+ );
1384
+ }
1385
+ }
1386
+ /**
1387
+ * Stage 2: Collect all citation IDs from the presentation
1388
+ */
1389
+ collectCitations(presentation) {
1390
+ const citations = this.citationExtractor.extractFromPresentation(presentation);
1391
+ return this.citationExtractor.getUniqueIds(citations);
1392
+ }
1393
+ /**
1394
+ * Stage 3: Resolve references from the reference manager
1395
+ */
1396
+ async resolveReferences(ids) {
1397
+ if (!this.config.references.enabled || ids.length === 0) {
1398
+ return /* @__PURE__ */ new Map();
1399
+ }
1400
+ try {
1401
+ const items = await this.referenceManager.getByIds(ids);
1402
+ for (const id of ids) {
1403
+ if (!items.has(id)) {
1404
+ this.warnings.push(`Reference not found: ${id}`);
1405
+ }
1406
+ }
1407
+ return items;
1408
+ } catch (error) {
1409
+ this.warnings.push(
1410
+ `Failed to resolve references: ${error instanceof Error ? error.message : "Unknown error"}`
1411
+ );
1412
+ return /* @__PURE__ */ new Map();
1413
+ }
1414
+ }
1415
+ /**
1416
+ * Stage 4: Transform all slides using templates
1417
+ */
1418
+ async transformSlides(presentation) {
1419
+ try {
1420
+ return await this.transformer.transformAll(presentation);
1421
+ } catch (error) {
1422
+ throw new PipelineError(
1423
+ `Failed to transform slides: ${error instanceof Error ? error.message : "Unknown error"}`,
1424
+ "transform",
1425
+ error
1426
+ );
1427
+ }
1428
+ }
1429
+ /**
1430
+ * Stage 5: Render the final Marp markdown
1431
+ */
1432
+ render(slides, presentation) {
1433
+ try {
1434
+ const notes = presentation.slides.map((slide) => slide.notes);
1435
+ const renderOptions = {
1436
+ includeTheme: true,
1437
+ notes
1438
+ };
1439
+ return this.renderer.render(slides, presentation.meta, renderOptions);
1440
+ } catch (error) {
1441
+ throw new PipelineError(
1442
+ `Failed to render output: ${error instanceof Error ? error.message : "Unknown error"}`,
1443
+ "render",
1444
+ error
1445
+ );
1446
+ }
1447
+ }
1448
+ };
1449
+
1450
+ // src/index.ts
1451
+ var VERSION = "0.1.0";
1452
+ export {
1453
+ ParseError,
1454
+ Parser,
1455
+ Pipeline,
1456
+ PipelineError,
1457
+ Renderer,
1458
+ TemplateEngine,
1459
+ TemplateLoader,
1460
+ TransformError,
1461
+ Transformer,
1462
+ VERSION,
1463
+ ValidationError,
1464
+ jsonSchemaToZod,
1465
+ presentationSchema,
1466
+ templateDefSchema,
1467
+ validateWithJsonSchema
1468
+ };
1469
+ //# sourceMappingURL=index.js.map