@pep/term-deck 1.0.14 → 1.0.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/bin/term-deck.d.ts +1 -0
  2. package/dist/bin/term-deck.js +1916 -0
  3. package/dist/bin/term-deck.js.map +1 -0
  4. package/dist/index.d.ts +670 -0
  5. package/dist/index.js +159 -0
  6. package/dist/index.js.map +1 -0
  7. package/package.json +16 -13
  8. package/bin/term-deck.js +0 -14
  9. package/bin/term-deck.ts +0 -45
  10. package/src/cli/__tests__/errors.test.ts +0 -201
  11. package/src/cli/__tests__/help.test.ts +0 -157
  12. package/src/cli/__tests__/init.test.ts +0 -110
  13. package/src/cli/commands/export.ts +0 -33
  14. package/src/cli/commands/init.ts +0 -125
  15. package/src/cli/commands/present.ts +0 -29
  16. package/src/cli/errors.ts +0 -77
  17. package/src/core/__tests__/slide.test.ts +0 -1759
  18. package/src/core/__tests__/theme.test.ts +0 -1103
  19. package/src/core/slide.ts +0 -509
  20. package/src/core/theme.ts +0 -388
  21. package/src/export/__tests__/recorder.test.ts +0 -566
  22. package/src/export/recorder.ts +0 -639
  23. package/src/index.ts +0 -36
  24. package/src/presenter/__tests__/main.test.ts +0 -244
  25. package/src/presenter/main.ts +0 -658
  26. package/src/renderer/__tests__/screen-extended.test.ts +0 -801
  27. package/src/renderer/__tests__/screen.test.ts +0 -525
  28. package/src/renderer/screen.ts +0 -671
  29. package/src/schemas/__tests__/config.test.ts +0 -429
  30. package/src/schemas/__tests__/slide.test.ts +0 -349
  31. package/src/schemas/__tests__/theme.test.ts +0 -970
  32. package/src/schemas/__tests__/validation.test.ts +0 -256
  33. package/src/schemas/config.ts +0 -58
  34. package/src/schemas/slide.ts +0 -56
  35. package/src/schemas/theme.ts +0 -203
  36. package/src/schemas/validation.ts +0 -64
  37. package/src/themes/matrix/index.ts +0 -53
package/src/core/theme.ts DELETED
@@ -1,388 +0,0 @@
1
- import { parse as parseYaml } from 'yaml'
2
- import deepmerge from 'deepmerge'
3
- import gradient from 'gradient-string'
4
- import type { Theme, PartialTheme } from '../schemas/theme'
5
- import { ThemeSchema } from '../schemas/theme'
6
- import { safeParse, ValidationError } from '../schemas/validation'
7
-
8
- /**
9
- * Error class for theme-related failures.
10
- * Includes optional source information (theme name and file path) for better debugging.
11
- */
12
- export class ThemeError extends Error {
13
- /**
14
- * @param message - The error message
15
- * @param themeName - Optional name of the theme that caused the error
16
- * @param path - Optional path to the theme file or package
17
- */
18
- constructor(
19
- message: string,
20
- public readonly themeName?: string,
21
- public readonly path?: string
22
- ) {
23
- super(message)
24
- this.name = 'ThemeError'
25
- }
26
- }
27
-
28
- /**
29
- * Format any error into a user-friendly ThemeError.
30
- * Handles ValidationError, generic Error, and unknown error types.
31
- *
32
- * @param error - The error to format
33
- * @param source - Description of the theme source (e.g., file path or package name)
34
- * @returns A ThemeError with a user-friendly message
35
- *
36
- * @example
37
- * try {
38
- * await loadThemeFromFile('./theme.yml')
39
- * } catch (error) {
40
- * throw formatThemeError(error, './theme.yml')
41
- * }
42
- */
43
- export function formatThemeError(error: unknown, source: string): ThemeError {
44
- if (error instanceof ValidationError) {
45
- return new ThemeError(
46
- `Invalid theme from ${source}:\n${error.message}`,
47
- undefined,
48
- source
49
- )
50
- }
51
-
52
- if (error instanceof Error) {
53
- return new ThemeError(
54
- `Failed to load theme from ${source}: ${error.message}`,
55
- undefined,
56
- source
57
- )
58
- }
59
-
60
- return new ThemeError(`Unknown error loading theme from ${source}`)
61
- }
62
-
63
- /**
64
- * Theme object with extension capability.
65
- * Extends the base Theme type with an extend() method that allows
66
- * Tailwind-style theme customization.
67
- */
68
- export interface ThemeObject extends Theme {
69
- /**
70
- * Create a new theme by merging overrides into this theme.
71
- * Uses deep merge with array replacement strategy.
72
- *
73
- * @param overrides - Partial theme object with values to override
74
- * @returns A new ThemeObject with the merged values
75
- *
76
- * @example
77
- * const custom = matrix.extend({
78
- * colors: { primary: '#ff0066' }
79
- * })
80
- *
81
- * @example
82
- * // Chained extensions
83
- * const custom = matrix
84
- * .extend({ colors: { primary: '#ff0066' } })
85
- * .extend({ animations: { revealSpeed: 0.5 } })
86
- */
87
- extend(overrides: PartialTheme): ThemeObject
88
- }
89
-
90
- /**
91
- * Theme package structure for npm packages or local theme files.
92
- * Contains the raw YAML source, parsed theme, and package metadata.
93
- */
94
- export interface ThemePackage {
95
- /** Raw YAML content of the theme file */
96
- yaml: string
97
-
98
- /** Parsed and validated theme object */
99
- theme: ThemeObject
100
-
101
- /** Package metadata */
102
- meta: {
103
- /** Package or theme name */
104
- name: string
105
- /** Package version */
106
- version: string
107
- /** Path to the theme file or package */
108
- path: string
109
- }
110
- }
111
-
112
- /**
113
- * Create a ThemeObject from a validated Theme.
114
- * Internal helper that adds the extend() method to a Theme.
115
- *
116
- * @param validated - A validated Theme object
117
- * @returns A ThemeObject with extension capability
118
- */
119
- function createThemeFromMerge(base: Theme, overrides: PartialTheme): ThemeObject {
120
- // Deep merge, with overrides taking precedence
121
- const merged = deepmerge(base, overrides, {
122
- // Arrays should be replaced, not concatenated
123
- arrayMerge: (_, source) => source,
124
- }) as Theme
125
-
126
- // Re-validate the merged result
127
- const validated = safeParse(ThemeSchema, merged, 'merged theme')
128
-
129
- return {
130
- ...validated,
131
- extend(newOverrides: PartialTheme): ThemeObject {
132
- return createThemeFromMerge(validated, newOverrides)
133
- },
134
- }
135
- }
136
-
137
- /**
138
- * Create a theme from a YAML string.
139
- * Parses the YAML, validates it against ThemeSchema, and returns a ThemeObject
140
- * with extension capability.
141
- *
142
- * @param yaml - The YAML string containing the theme definition
143
- * @returns A validated ThemeObject with extend() method
144
- * @throws {Error} If the YAML syntax is invalid
145
- * @throws {ValidationError} If the parsed data doesn't match ThemeSchema
146
- *
147
- * @example
148
- * const theme = createTheme(`
149
- * name: custom
150
- * colors:
151
- * primary: "#ff0066"
152
- * accent: "#00ff66"
153
- * background: "#000000"
154
- * text: "#ffffff"
155
- * muted: "#666666"
156
- * gradients:
157
- * main:
158
- * - "#ff0066"
159
- * - "#00ff66"
160
- * glyphs: "0123456789ABCDEF"
161
- * animations:
162
- * revealSpeed: 1.0
163
- * matrixDensity: 30
164
- * glitchIterations: 3
165
- * lineDelay: 20
166
- * matrixInterval: 100
167
- * `)
168
- */
169
- export function createTheme(yaml: string): ThemeObject {
170
- const parsed = parseYaml(yaml)
171
- const validated = safeParse(ThemeSchema, parsed, 'theme')
172
-
173
- return {
174
- ...validated,
175
- extend(overrides: PartialTheme): ThemeObject {
176
- return createThemeFromMerge(validated, overrides)
177
- },
178
- }
179
- }
180
-
181
- /**
182
- * Load a theme from a YAML file on the filesystem.
183
- * Reads the file contents and passes them to createTheme for parsing and validation.
184
- *
185
- * @param path - The filesystem path to the YAML theme file
186
- * @returns A validated ThemeObject with extend() method
187
- * @throws {Error} If the file cannot be read (e.g., file not found, permission denied)
188
- * @throws {Error} If the YAML syntax is invalid
189
- * @throws {ValidationError} If the parsed data doesn't match ThemeSchema
190
- *
191
- * @example
192
- * const theme = await loadThemeFromFile('./themes/matrix.yml')
193
- * const customTheme = theme.extend({ colors: { primary: '#ff0066' } })
194
- */
195
- export async function loadThemeFromFile(path: string): Promise<ThemeObject> {
196
- const file = Bun.file(path)
197
- const exists = await file.exists()
198
-
199
- if (!exists) {
200
- throw new Error(`Theme file not found: ${path}`)
201
- }
202
-
203
- const content = await file.text()
204
- return createTheme(content)
205
- }
206
-
207
- /**
208
- * Load a theme from an npm package.
209
- * Packages must export a default ThemeObject.
210
- *
211
- * @param name - The npm package name (e.g., '@term-deck/theme-retro')
212
- * @returns A validated ThemeObject with extend() method
213
- * @throws {Error} If the package doesn't export a default theme
214
- * @throws {Error} If the package is not installed (with helpful install message)
215
- * @throws {ValidationError} If the exported theme doesn't match ThemeSchema
216
- *
217
- * @example
218
- * const theme = await loadThemeFromPackage('@term-deck/theme-retro')
219
- * const customTheme = theme.extend({ colors: { primary: '#ff0066' } })
220
- */
221
- export async function loadThemeFromPackage(name: string): Promise<ThemeObject> {
222
- try {
223
- // Dynamic import of npm package
224
- const pkg = await import(name)
225
-
226
- if (!pkg.default) {
227
- throw new Error(`Theme package "${name}" must export a default theme`)
228
- }
229
-
230
- // Validate the exported theme
231
- const validated = safeParse(ThemeSchema, pkg.default, `theme from ${name}`)
232
-
233
- return {
234
- ...validated,
235
- extend(overrides: PartialTheme): ThemeObject {
236
- return createThemeFromMerge(validated, overrides)
237
- },
238
- }
239
- } catch (error) {
240
- // Handle module not found with helpful message
241
- // Bun's module resolution errors are not instanceof Error but have message/code properties
242
- const err = error as { message?: string; code?: string }
243
- const isModuleNotFound =
244
- err.message?.includes('Cannot find package') ||
245
- err.message?.includes('Cannot find module') ||
246
- err.code === 'MODULE_NOT_FOUND' ||
247
- err.code === 'ERR_MODULE_NOT_FOUND'
248
-
249
- if (isModuleNotFound) {
250
- throw new Error(
251
- `Theme package "${name}" not found. Install it with: bun add ${name}`
252
- )
253
- }
254
- throw error
255
- }
256
- }
257
-
258
- /**
259
- * Function type for applying a gradient to text.
260
- * Returns ANSI-colored text with the gradient applied.
261
- */
262
- export interface GradientFunction {
263
- (text: string): string
264
- }
265
-
266
- /**
267
- * Create gradient functions from theme gradients.
268
- * Returns an object mapping gradient names to gradient functions that can be
269
- * applied to text to produce ANSI-colored output.
270
- *
271
- * @param theme - The theme containing gradient definitions
272
- * @returns Record mapping gradient names to gradient functions
273
- *
274
- * @example
275
- * const gradients = createGradients(theme)
276
- * const styledText = gradients.fire('Hello World')
277
- */
278
- export function createGradients(theme: Theme): Record<string, GradientFunction> {
279
- const gradients: Record<string, GradientFunction> = {}
280
-
281
- for (const [name, colors] of Object.entries(theme.gradients)) {
282
- gradients[name] = gradient(colors)
283
- }
284
-
285
- return gradients
286
- }
287
-
288
- /**
289
- * Apply a gradient to text by name.
290
- * Looks up the gradient in the theme and applies it to the text.
291
- * Falls back gracefully if the gradient doesn't exist.
292
- *
293
- * @param text - The text to apply the gradient to
294
- * @param gradientName - The name of the gradient to use
295
- * @param theme - The theme containing gradient definitions
296
- * @returns The text with gradient applied, or unstyled text if gradient not found
297
- *
298
- * @example
299
- * const styledText = applyGradient('Hello World', 'fire', theme)
300
- */
301
- export function applyGradient(
302
- text: string,
303
- gradientName: string,
304
- theme: Theme
305
- ): string {
306
- const colors = theme.gradients[gradientName]
307
-
308
- if (!colors) {
309
- // Fall back gracefully - return unstyled text
310
- return text
311
- }
312
-
313
- return gradient(colors)(text)
314
- }
315
-
316
- /**
317
- * Built-in color mappings for color tokens in slide content.
318
- * These are fixed colors that don't change with the theme.
319
- */
320
- export const BUILTIN_COLORS: Record<string, string> = {
321
- GREEN: '#00cc66',
322
- ORANGE: '#ff6600',
323
- CYAN: '#00ccff',
324
- PINK: '#ff0066',
325
- WHITE: '#ffffff',
326
- GRAY: '#666666',
327
- }
328
-
329
- /**
330
- * Resolve a color token to its hex value.
331
- * Theme colors (PRIMARY, ACCENT, etc.) are resolved from the theme.
332
- * Built-in colors (GREEN, ORANGE, etc.) use fixed values.
333
- *
334
- * @param token - The color token to resolve (e.g., 'PRIMARY', 'GREEN')
335
- * @param theme - The theme to resolve theme-specific tokens from
336
- * @returns The hex color value
337
- *
338
- * @example
339
- * const color = resolveColorToken('PRIMARY', theme) // '#00cc66'
340
- * const color = resolveColorToken('GREEN', theme) // '#00cc66'
341
- */
342
- export function resolveColorToken(token: string, theme: Theme): string {
343
- // Check theme colors first
344
- switch (token) {
345
- case 'PRIMARY':
346
- return theme.colors.primary
347
- case 'SECONDARY':
348
- return theme.colors.secondary ?? theme.colors.primary
349
- case 'ACCENT':
350
- return theme.colors.accent
351
- case 'MUTED':
352
- return theme.colors.muted
353
- case 'TEXT':
354
- return theme.colors.text
355
- case 'BACKGROUND':
356
- return theme.colors.background
357
- }
358
-
359
- // Fall back to built-in colors
360
- return BUILTIN_COLORS[token] ?? theme.colors.text
361
- }
362
-
363
- /**
364
- * Convert color tokens in content to blessed tags.
365
- * Transforms tokens like {GREEN} to blessed color tags like {#00cc66-fg}.
366
- * Preserves closing tags {/} as-is.
367
- *
368
- * @param content - The content with color tokens
369
- * @param theme - The theme to resolve theme-specific tokens from
370
- * @returns Content with color tokens converted to blessed tags
371
- *
372
- * @example
373
- * const content = '{GREEN}Hello{/} {ORANGE}World{/}'
374
- * const result = colorTokensToBlessedTags(content, theme)
375
- * // '{#00cc66-fg}Hello{/} {#ff6600-fg}World{/}'
376
- */
377
- export function colorTokensToBlessedTags(content: string, theme: Theme): string {
378
- return content.replace(
379
- /\{(GREEN|ORANGE|CYAN|PINK|WHITE|GRAY|PRIMARY|SECONDARY|ACCENT|MUTED|TEXT|BACKGROUND|\/)\}/g,
380
- (_, token) => {
381
- if (token === '/') {
382
- return '{/}' // Close tag
383
- }
384
- const color = resolveColorToken(token, theme)
385
- return `{${color}-fg}`
386
- }
387
- )
388
- }