@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/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
|
-
}
|