@pep/term-deck 1.0.14 → 1.0.15
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/term-deck.d.ts +1 -0
- package/dist/bin/term-deck.js +1720 -0
- package/dist/bin/term-deck.js.map +1 -0
- package/dist/index.d.ts +670 -0
- package/dist/index.js +159 -0
- package/dist/index.js.map +1 -0
- package/package.json +16 -13
- package/bin/term-deck.js +0 -14
- package/bin/term-deck.ts +0 -45
- package/src/cli/__tests__/errors.test.ts +0 -201
- package/src/cli/__tests__/help.test.ts +0 -157
- package/src/cli/__tests__/init.test.ts +0 -110
- package/src/cli/commands/export.ts +0 -33
- package/src/cli/commands/init.ts +0 -125
- package/src/cli/commands/present.ts +0 -29
- package/src/cli/errors.ts +0 -77
- package/src/core/__tests__/slide.test.ts +0 -1759
- package/src/core/__tests__/theme.test.ts +0 -1103
- package/src/core/slide.ts +0 -509
- package/src/core/theme.ts +0 -388
- package/src/export/__tests__/recorder.test.ts +0 -566
- package/src/export/recorder.ts +0 -639
- package/src/index.ts +0 -36
- package/src/presenter/__tests__/main.test.ts +0 -244
- package/src/presenter/main.ts +0 -658
- package/src/renderer/__tests__/screen-extended.test.ts +0 -801
- package/src/renderer/__tests__/screen.test.ts +0 -525
- package/src/renderer/screen.ts +0 -671
- package/src/schemas/__tests__/config.test.ts +0 -429
- package/src/schemas/__tests__/slide.test.ts +0 -349
- package/src/schemas/__tests__/theme.test.ts +0 -970
- package/src/schemas/__tests__/validation.test.ts +0 -256
- package/src/schemas/config.ts +0 -58
- package/src/schemas/slide.ts +0 -56
- package/src/schemas/theme.ts +0 -203
- package/src/schemas/validation.ts +0 -64
- package/src/themes/matrix/index.ts +0 -53
package/src/core/slide.ts
DELETED
|
@@ -1,509 +0,0 @@
|
|
|
1
|
-
import matter from 'gray-matter'
|
|
2
|
-
import { join } from 'path'
|
|
3
|
-
import { Glob } from 'bun'
|
|
4
|
-
import { mermaidToAscii as convertMermaid } from 'mermaid-ascii'
|
|
5
|
-
import type { Slide } from '../schemas/slide.js'
|
|
6
|
-
import type { DeckConfig } from '../schemas/config.js'
|
|
7
|
-
import type { Theme } from '../schemas/theme.js'
|
|
8
|
-
import { SlideFrontmatterSchema, SlideSchema } from '../schemas/slide.js'
|
|
9
|
-
import { DeckConfigSchema } from '../schemas/config.js'
|
|
10
|
-
import { safeParse } from '../schemas/validation.js'
|
|
11
|
-
import { DEFAULT_THEME } from '../schemas/theme.js'
|
|
12
|
-
import { colorTokensToBlessedTags } from './theme.js'
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Raw parsed slide before validation.
|
|
16
|
-
* This is the intermediate structure after parsing frontmatter
|
|
17
|
-
* but before Zod validation.
|
|
18
|
-
*/
|
|
19
|
-
export interface RawSlide {
|
|
20
|
-
frontmatter: Record<string, unknown>
|
|
21
|
-
body: string
|
|
22
|
-
notes?: string
|
|
23
|
-
sourcePath: string
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Deck structure containing all slides and configuration.
|
|
28
|
-
* Represents a complete presentation loaded from disk.
|
|
29
|
-
*/
|
|
30
|
-
export interface Deck {
|
|
31
|
-
slides: Slide[]
|
|
32
|
-
config: DeckConfig
|
|
33
|
-
basePath: string
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Slide file info for sorting and loading.
|
|
38
|
-
* Used during the slide discovery phase.
|
|
39
|
-
*/
|
|
40
|
-
export interface SlideFile {
|
|
41
|
-
path: string
|
|
42
|
-
name: string
|
|
43
|
-
index: number
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Result of extracting notes from slide content.
|
|
48
|
-
*/
|
|
49
|
-
export interface ExtractedNotes {
|
|
50
|
-
body: string
|
|
51
|
-
notes?: string
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const NOTES_MARKER = '<!-- notes -->'
|
|
55
|
-
const NOTES_END_MARKER = '<!-- /notes -->'
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Pattern to match mermaid code blocks in markdown content.
|
|
59
|
-
* Captures the diagram code inside the block.
|
|
60
|
-
*
|
|
61
|
-
* Matches: ```mermaid\n<content>```
|
|
62
|
-
* Group 1: The mermaid diagram code
|
|
63
|
-
*/
|
|
64
|
-
const MERMAID_BLOCK_PATTERN = /```mermaid\n([\s\S]*?)```/g
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Check if content contains mermaid diagrams.
|
|
68
|
-
*
|
|
69
|
-
* @param content - The markdown content to check
|
|
70
|
-
* @returns true if the content contains at least one mermaid code block
|
|
71
|
-
*/
|
|
72
|
-
export function hasMermaidDiagrams(content: string): boolean {
|
|
73
|
-
// Reset pattern since we use global flag
|
|
74
|
-
MERMAID_BLOCK_PATTERN.lastIndex = 0
|
|
75
|
-
return MERMAID_BLOCK_PATTERN.test(content)
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Extract all mermaid blocks from content.
|
|
80
|
-
*
|
|
81
|
-
* Finds all mermaid code blocks and extracts the diagram code
|
|
82
|
-
* (without the ```mermaid and ``` delimiters).
|
|
83
|
-
*
|
|
84
|
-
* @param content - The markdown content to search
|
|
85
|
-
* @returns Array of mermaid diagram code strings (trimmed)
|
|
86
|
-
*/
|
|
87
|
-
export function extractMermaidBlocks(content: string): string[] {
|
|
88
|
-
const blocks: string[] = []
|
|
89
|
-
let match: RegExpExecArray | null
|
|
90
|
-
|
|
91
|
-
// Reset pattern before use
|
|
92
|
-
MERMAID_BLOCK_PATTERN.lastIndex = 0
|
|
93
|
-
|
|
94
|
-
while ((match = MERMAID_BLOCK_PATTERN.exec(content)) !== null) {
|
|
95
|
-
blocks.push(match[1].trim())
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return blocks
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Format a mermaid parsing error as ASCII.
|
|
103
|
-
*
|
|
104
|
-
* Creates a visually recognizable error block showing the
|
|
105
|
-
* first few lines of the diagram with a border.
|
|
106
|
-
*
|
|
107
|
-
* @param code - The mermaid diagram code that failed to parse
|
|
108
|
-
* @param _error - The error that occurred (unused but kept for signature)
|
|
109
|
-
* @returns ASCII art error block
|
|
110
|
-
*/
|
|
111
|
-
export function formatMermaidError(code: string, _error: unknown): string {
|
|
112
|
-
const lines = [
|
|
113
|
-
'┌─ Diagram (parse error) ─┐',
|
|
114
|
-
'│ │',
|
|
115
|
-
]
|
|
116
|
-
|
|
117
|
-
// Show first few lines of the diagram
|
|
118
|
-
const codeLines = code.split('\n').slice(0, 5)
|
|
119
|
-
for (const line of codeLines) {
|
|
120
|
-
const truncated = line.slice(0, 23).padEnd(23)
|
|
121
|
-
lines.push(`│ ${truncated} │`)
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
if (code.split('\n').length > 5) {
|
|
125
|
-
lines.push('│ ... │')
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
lines.push('│ │')
|
|
129
|
-
lines.push('└─────────────────────────┘')
|
|
130
|
-
|
|
131
|
-
return lines.join('\n')
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Convert mermaid diagram to ASCII art.
|
|
136
|
-
*
|
|
137
|
-
* Uses the mermaid-ascii library to convert mermaid diagram
|
|
138
|
-
* syntax to ASCII art representation. Falls back to an error
|
|
139
|
-
* block if parsing fails.
|
|
140
|
-
*
|
|
141
|
-
* @param mermaidCode - Raw mermaid diagram code
|
|
142
|
-
* @returns ASCII art representation or error block
|
|
143
|
-
*/
|
|
144
|
-
export function mermaidToAscii(mermaidCode: string): string {
|
|
145
|
-
try {
|
|
146
|
-
return convertMermaid(mermaidCode)
|
|
147
|
-
} catch (error) {
|
|
148
|
-
// If parsing fails, return a formatted error block
|
|
149
|
-
return formatMermaidError(mermaidCode, error)
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Escape special regex characters in a string.
|
|
155
|
-
*
|
|
156
|
-
* @param str - String to escape
|
|
157
|
-
* @returns Escaped string safe for use in RegExp
|
|
158
|
-
*/
|
|
159
|
-
function escapeRegex(str: string): string {
|
|
160
|
-
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Process all mermaid blocks in slide content.
|
|
165
|
-
*
|
|
166
|
-
* Finds all mermaid code blocks in the content and replaces them
|
|
167
|
-
* with their ASCII art representation.
|
|
168
|
-
*
|
|
169
|
-
* @param content - The slide content potentially containing mermaid blocks
|
|
170
|
-
* @returns Content with mermaid blocks replaced by ASCII art
|
|
171
|
-
*/
|
|
172
|
-
export function processMermaidDiagrams(content: string): string {
|
|
173
|
-
if (!hasMermaidDiagrams(content)) {
|
|
174
|
-
return content
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// Find all blocks and convert
|
|
178
|
-
let result = content
|
|
179
|
-
const blocks = extractMermaidBlocks(content)
|
|
180
|
-
|
|
181
|
-
for (const block of blocks) {
|
|
182
|
-
const ascii = mermaidToAscii(block)
|
|
183
|
-
|
|
184
|
-
// Replace mermaid block with ASCII
|
|
185
|
-
// The pattern matches the code block including newlines
|
|
186
|
-
result = result.replace(
|
|
187
|
-
new RegExp('```mermaid\\n' + escapeRegex(block) + '\\n?```', 'g'),
|
|
188
|
-
ascii
|
|
189
|
-
)
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
return result
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/**
|
|
196
|
-
* Process slide body content.
|
|
197
|
-
*
|
|
198
|
-
* Applies the full content processing pipeline:
|
|
199
|
-
* 1. Process mermaid diagrams (convert to ASCII)
|
|
200
|
-
* 2. Apply color tokens (convert to blessed tags)
|
|
201
|
-
*
|
|
202
|
-
* The order is important: mermaid diagrams are processed first so that
|
|
203
|
-
* any color tokens they might contain are then converted to blessed tags.
|
|
204
|
-
*
|
|
205
|
-
* @param body - The slide body content to process
|
|
206
|
-
* @param theme - The theme to use for color token resolution
|
|
207
|
-
* @returns Processed content with mermaid converted and color tokens applied
|
|
208
|
-
*
|
|
209
|
-
* @example
|
|
210
|
-
* const processed = await processSlideContent(
|
|
211
|
-
* '{GREEN}Hello{/}\n\n```mermaid\ngraph LR\nA-->B\n```',
|
|
212
|
-
* theme
|
|
213
|
-
* )
|
|
214
|
-
* // Returns: '{#00cc66-fg}Hello{/}\n\n<ascii art>'
|
|
215
|
-
*/
|
|
216
|
-
export async function processSlideContent(
|
|
217
|
-
body: string,
|
|
218
|
-
theme: Theme
|
|
219
|
-
): Promise<string> {
|
|
220
|
-
// Process mermaid diagrams first
|
|
221
|
-
let processed = processMermaidDiagrams(body)
|
|
222
|
-
|
|
223
|
-
// Apply color tokens
|
|
224
|
-
processed = colorTokensToBlessedTags(processed, theme)
|
|
225
|
-
|
|
226
|
-
return processed
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* Extract presenter notes from body content.
|
|
231
|
-
*
|
|
232
|
-
* Notes are delimited by `<!-- notes -->` marker.
|
|
233
|
-
* An optional `<!-- /notes -->` end marker can be used to include
|
|
234
|
-
* content after notes in the body.
|
|
235
|
-
*
|
|
236
|
-
* @param content - Raw body content from markdown file
|
|
237
|
-
* @returns Object containing separated body and notes
|
|
238
|
-
*/
|
|
239
|
-
export function extractNotes(content: string): ExtractedNotes {
|
|
240
|
-
const notesStart = content.indexOf(NOTES_MARKER)
|
|
241
|
-
|
|
242
|
-
if (notesStart === -1) {
|
|
243
|
-
return { body: content }
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
const body = content.slice(0, notesStart).trim()
|
|
247
|
-
|
|
248
|
-
// Check for explicit end marker
|
|
249
|
-
const notesEnd = content.indexOf(NOTES_END_MARKER, notesStart)
|
|
250
|
-
|
|
251
|
-
let notes: string
|
|
252
|
-
if (notesEnd !== -1) {
|
|
253
|
-
notes = content.slice(notesStart + NOTES_MARKER.length, notesEnd).trim()
|
|
254
|
-
} else {
|
|
255
|
-
// Everything after marker is notes
|
|
256
|
-
notes = content.slice(notesStart + NOTES_MARKER.length).trim()
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
return { body, notes: notes || undefined }
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
/**
|
|
263
|
-
* Parse a single slide file.
|
|
264
|
-
*
|
|
265
|
-
* Reads the markdown file, extracts frontmatter using gray-matter,
|
|
266
|
-
* extracts presenter notes, and validates the result against the schema.
|
|
267
|
-
*
|
|
268
|
-
* @param filePath - Path to the markdown slide file
|
|
269
|
-
* @param index - The slide index in the deck (0-indexed)
|
|
270
|
-
* @returns Validated Slide object
|
|
271
|
-
* @throws {ValidationError} If frontmatter or slide validation fails
|
|
272
|
-
*/
|
|
273
|
-
export async function parseSlide(
|
|
274
|
-
filePath: string,
|
|
275
|
-
index: number
|
|
276
|
-
): Promise<Slide> {
|
|
277
|
-
// Read file content
|
|
278
|
-
const content = await Bun.file(filePath).text()
|
|
279
|
-
|
|
280
|
-
// Parse frontmatter with gray-matter
|
|
281
|
-
const { data, content: rawBody } = matter(content)
|
|
282
|
-
|
|
283
|
-
// Extract notes from body
|
|
284
|
-
const { body, notes } = extractNotes(rawBody)
|
|
285
|
-
|
|
286
|
-
// Validate frontmatter
|
|
287
|
-
const frontmatter = safeParse(
|
|
288
|
-
SlideFrontmatterSchema,
|
|
289
|
-
data,
|
|
290
|
-
`frontmatter in ${filePath}`
|
|
291
|
-
)
|
|
292
|
-
|
|
293
|
-
// Build full slide object
|
|
294
|
-
const slide = {
|
|
295
|
-
frontmatter,
|
|
296
|
-
body: body.trim(),
|
|
297
|
-
notes: notes?.trim(),
|
|
298
|
-
sourcePath: filePath,
|
|
299
|
-
index,
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// Validate complete slide and return
|
|
303
|
-
return safeParse(SlideSchema, slide, `slide ${filePath}`)
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
/**
|
|
307
|
-
* Find and sort slide files in a directory.
|
|
308
|
-
*
|
|
309
|
-
* Finds all markdown files (*.md), excludes non-slide files like
|
|
310
|
-
* README.md and files starting with underscore, and sorts them
|
|
311
|
-
* numerically by filename (e.g., 01-intro.md, 02-content.md).
|
|
312
|
-
*
|
|
313
|
-
* @param dir - Directory to search for slide files
|
|
314
|
-
* @returns Array of SlideFile objects sorted by filename
|
|
315
|
-
*/
|
|
316
|
-
export async function findSlideFiles(dir: string): Promise<SlideFile[]> {
|
|
317
|
-
const glob = new Glob('*.md')
|
|
318
|
-
const files: SlideFile[] = []
|
|
319
|
-
|
|
320
|
-
for await (const file of glob.scan(dir)) {
|
|
321
|
-
// Skip non-slide files
|
|
322
|
-
if (file === 'README.md' || file.startsWith('_')) {
|
|
323
|
-
continue
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
files.push({
|
|
327
|
-
path: join(dir, file),
|
|
328
|
-
name: file,
|
|
329
|
-
index: 0, // Will be set after sorting
|
|
330
|
-
})
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
// Sort by filename numerically (01-intro.md, 02-problem.md, etc.)
|
|
334
|
-
files.sort((a, b) => a.name.localeCompare(b.name, undefined, { numeric: true }))
|
|
335
|
-
|
|
336
|
-
// Assign indices after sorting
|
|
337
|
-
files.forEach((file, i) => {
|
|
338
|
-
file.index = i
|
|
339
|
-
})
|
|
340
|
-
|
|
341
|
-
return files
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
/**
|
|
345
|
-
* Load deck configuration from deck.config.ts in slides directory.
|
|
346
|
-
*
|
|
347
|
-
* Looks for a deck.config.ts file in the specified directory.
|
|
348
|
-
* If found, dynamically imports and validates it against DeckConfigSchema.
|
|
349
|
-
* If not found, returns a default config with the DEFAULT_THEME.
|
|
350
|
-
*
|
|
351
|
-
* @param slidesDir - Directory to search for deck.config.ts
|
|
352
|
-
* @returns Validated DeckConfig object
|
|
353
|
-
* @throws {ValidationError} If config file exists but fails validation
|
|
354
|
-
*/
|
|
355
|
-
export async function loadDeckConfig(slidesDir: string): Promise<DeckConfig> {
|
|
356
|
-
const configPath = join(slidesDir, 'deck.config.ts')
|
|
357
|
-
|
|
358
|
-
try {
|
|
359
|
-
// Check if config file exists
|
|
360
|
-
const configFile = Bun.file(configPath)
|
|
361
|
-
if (!(await configFile.exists())) {
|
|
362
|
-
// Return default config with DEFAULT_THEME
|
|
363
|
-
return {
|
|
364
|
-
theme: DEFAULT_THEME,
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
// Dynamic import of TypeScript config
|
|
369
|
-
// Add cache buster to prevent module caching in tests
|
|
370
|
-
const configModule = await import(configPath + '?t=' + Date.now())
|
|
371
|
-
|
|
372
|
-
if (!configModule.default) {
|
|
373
|
-
throw new Error('deck.config.ts must export default config')
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
// Validate config against schema
|
|
377
|
-
return safeParse(DeckConfigSchema, configModule.default, 'deck.config.ts')
|
|
378
|
-
} catch (error) {
|
|
379
|
-
if ((error as NodeJS.ErrnoException).code === 'MODULE_NOT_FOUND') {
|
|
380
|
-
// No config file found, use defaults
|
|
381
|
-
return { theme: DEFAULT_THEME }
|
|
382
|
-
}
|
|
383
|
-
throw error
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
/**
|
|
388
|
-
* Load a complete deck from a directory.
|
|
389
|
-
*
|
|
390
|
-
* Loads the deck configuration, finds all slide files, and parses
|
|
391
|
-
* them in parallel. Returns a Deck object containing all slides,
|
|
392
|
-
* the configuration, and the base path.
|
|
393
|
-
*
|
|
394
|
-
* @param slidesDir - Directory containing slide files and optional deck.config.ts
|
|
395
|
-
* @returns Complete Deck object with slides, config, and basePath
|
|
396
|
-
* @throws {ValidationError} If any slide fails to parse or validate
|
|
397
|
-
*/
|
|
398
|
-
export async function loadDeck(slidesDir: string): Promise<Deck> {
|
|
399
|
-
// Load config first
|
|
400
|
-
const config = await loadDeckConfig(slidesDir)
|
|
401
|
-
|
|
402
|
-
// Find all markdown files
|
|
403
|
-
const slideFiles = await findSlideFiles(slidesDir)
|
|
404
|
-
|
|
405
|
-
// Parse all slides in parallel
|
|
406
|
-
const slides = await Promise.all(
|
|
407
|
-
slideFiles.map((file) => parseSlide(file.path, file.index))
|
|
408
|
-
)
|
|
409
|
-
|
|
410
|
-
return {
|
|
411
|
-
slides,
|
|
412
|
-
config,
|
|
413
|
-
basePath: slidesDir,
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
/**
|
|
418
|
-
* Normalize bigText to array.
|
|
419
|
-
*
|
|
420
|
-
* Converts the bigText frontmatter field to a consistent array format
|
|
421
|
-
* for use by the renderer. Handles:
|
|
422
|
-
* - undefined → empty array
|
|
423
|
-
* - string → single-element array
|
|
424
|
-
* - string[] → pass through unchanged
|
|
425
|
-
*
|
|
426
|
-
* @param bigText - The bigText value from slide frontmatter
|
|
427
|
-
* @returns Array of strings for rendering
|
|
428
|
-
*
|
|
429
|
-
* @example
|
|
430
|
-
* normalizeBigText(undefined) // []
|
|
431
|
-
* normalizeBigText('HELLO') // ['HELLO']
|
|
432
|
-
* normalizeBigText(['A', 'B']) // ['A', 'B']
|
|
433
|
-
*/
|
|
434
|
-
export function normalizeBigText(bigText: string | string[] | undefined): string[] {
|
|
435
|
-
if (!bigText) return []
|
|
436
|
-
return Array.isArray(bigText) ? bigText : [bigText]
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
/**
|
|
440
|
-
* Error class for slide parsing failures.
|
|
441
|
-
* Includes the file path of the slide that failed to parse and
|
|
442
|
-
* optionally the underlying cause for error chaining.
|
|
443
|
-
*/
|
|
444
|
-
export class SlideParseError extends Error {
|
|
445
|
-
/**
|
|
446
|
-
* @param message - The error message describing what went wrong
|
|
447
|
-
* @param filePath - Path to the slide file that failed to parse
|
|
448
|
-
* @param cause - Optional underlying error that caused this failure
|
|
449
|
-
*/
|
|
450
|
-
constructor(
|
|
451
|
-
message: string,
|
|
452
|
-
public readonly filePath: string,
|
|
453
|
-
public override readonly cause?: Error
|
|
454
|
-
) {
|
|
455
|
-
super(message)
|
|
456
|
-
this.name = 'SlideParseError'
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
/**
|
|
461
|
-
* Error class for deck loading failures.
|
|
462
|
-
* Includes the directory path of the slides and optionally
|
|
463
|
-
* the underlying cause for error chaining.
|
|
464
|
-
*/
|
|
465
|
-
export class DeckLoadError extends Error {
|
|
466
|
-
/**
|
|
467
|
-
* @param message - The error message describing what went wrong
|
|
468
|
-
* @param slidesDir - Path to the directory that was being loaded
|
|
469
|
-
* @param cause - Optional underlying error that caused this failure
|
|
470
|
-
*/
|
|
471
|
-
constructor(
|
|
472
|
-
message: string,
|
|
473
|
-
public readonly slidesDir: string,
|
|
474
|
-
public override readonly cause?: Error
|
|
475
|
-
) {
|
|
476
|
-
super(message)
|
|
477
|
-
this.name = 'DeckLoadError'
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
/**
|
|
482
|
-
* Format a slide parse error for user-friendly display.
|
|
483
|
-
* Creates a multi-line message with the file path, error message,
|
|
484
|
-
* and optional cause chain.
|
|
485
|
-
*
|
|
486
|
-
* @param error - The SlideParseError to format
|
|
487
|
-
* @returns A formatted string suitable for console output
|
|
488
|
-
*
|
|
489
|
-
* @example
|
|
490
|
-
* const error = new SlideParseError(
|
|
491
|
-
* 'Missing required field: title',
|
|
492
|
-
* '/slides/01-intro.md',
|
|
493
|
-
* new Error('Validation failed')
|
|
494
|
-
* )
|
|
495
|
-
* console.log(formatSlideError(error))
|
|
496
|
-
* // Error parsing slide: /slides/01-intro.md
|
|
497
|
-
* // Missing required field: title
|
|
498
|
-
* // Caused by: Validation failed
|
|
499
|
-
*/
|
|
500
|
-
export function formatSlideError(error: SlideParseError): string {
|
|
501
|
-
let msg = `Error parsing slide: ${error.filePath}\n`
|
|
502
|
-
msg += ` ${error.message}\n`
|
|
503
|
-
|
|
504
|
-
if (error.cause) {
|
|
505
|
-
msg += ` Caused by: ${error.cause.message}\n`
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
return msg
|
|
509
|
-
}
|