@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/dist/index.js ADDED
@@ -0,0 +1,159 @@
1
+ import { z } from 'zod';
2
+ import { parse } from 'yaml';
3
+ import deepmerge from 'deepmerge';
4
+ import 'gradient-string';
5
+
6
+ // src/schemas/config.ts
7
+ var HexColorSchema = z.string().regex(/^#[0-9a-fA-F]{6}$/, {
8
+ message: "Color must be a valid hex color (e.g., #ff0066)"
9
+ });
10
+ var GradientSchema = z.array(HexColorSchema).min(2, {
11
+ message: "Gradient must have at least 2 colors"
12
+ });
13
+ var ThemeSchema = z.object({
14
+ // Theme metadata
15
+ name: z.string().min(1, { message: "Theme name is required" }),
16
+ description: z.string().optional(),
17
+ author: z.string().optional(),
18
+ version: z.string().optional(),
19
+ // Color palette
20
+ colors: z.object({
21
+ primary: HexColorSchema,
22
+ secondary: HexColorSchema.optional(),
23
+ accent: HexColorSchema,
24
+ background: HexColorSchema,
25
+ text: HexColorSchema,
26
+ muted: HexColorSchema,
27
+ success: HexColorSchema.optional(),
28
+ warning: HexColorSchema.optional(),
29
+ error: HexColorSchema.optional()
30
+ }),
31
+ // Named gradients for bigText
32
+ gradients: z.record(z.string(), GradientSchema).refine(
33
+ (g) => Object.keys(g).length >= 1,
34
+ { message: "At least one gradient must be defined" }
35
+ ),
36
+ // Glyph set for matrix rain background
37
+ glyphs: z.string().min(10, {
38
+ message: "Glyph set must have at least 10 characters"
39
+ }),
40
+ // Animation settings
41
+ animations: z.object({
42
+ // Speed multiplier (1.0 = normal, 0.5 = half speed, 2.0 = double speed)
43
+ revealSpeed: z.number().min(0.1).max(5).default(1),
44
+ // Matrix rain density (number of drops)
45
+ matrixDensity: z.number().min(10).max(200).default(50),
46
+ // Glitch effect iterations
47
+ glitchIterations: z.number().min(1).max(20).default(5),
48
+ // Delay between lines during reveal (ms)
49
+ lineDelay: z.number().min(0).max(500).default(30),
50
+ // Matrix rain update interval (ms)
51
+ matrixInterval: z.number().min(20).max(200).default(80)
52
+ }),
53
+ // Window appearance
54
+ window: z.object({
55
+ // Border style
56
+ borderStyle: z.enum(["line", "double", "rounded", "none"]).default("line"),
57
+ // Shadow effect
58
+ shadow: z.boolean().default(true),
59
+ // Padding inside windows
60
+ padding: z.object({
61
+ top: z.number().min(0).max(5).default(1),
62
+ bottom: z.number().min(0).max(5).default(1),
63
+ left: z.number().min(0).max(10).default(2),
64
+ right: z.number().min(0).max(10).default(2)
65
+ }).optional()
66
+ }).optional()
67
+ });
68
+ ThemeSchema.deepPartial();
69
+
70
+ // src/schemas/config.ts
71
+ var SettingsSchema = z.object({
72
+ // Start slide (0-indexed)
73
+ startSlide: z.number().min(0).default(0),
74
+ // Loop back to first slide after last
75
+ loop: z.boolean().default(false),
76
+ // Auto-advance slides (ms, 0 = disabled)
77
+ autoAdvance: z.number().min(0).default(0),
78
+ // Show slide numbers
79
+ showSlideNumbers: z.boolean().default(false),
80
+ // Show progress bar
81
+ showProgress: z.boolean().default(false)
82
+ });
83
+ var ExportSettingsSchema = z.object({
84
+ // Output width in characters (min 80, max 400)
85
+ width: z.number().min(80).max(400).default(120),
86
+ // Output height in characters (min 24, max 100)
87
+ height: z.number().min(24).max(100).default(40),
88
+ // Frames per second for video (min 10, max 60)
89
+ fps: z.number().min(10).max(60).default(30)
90
+ });
91
+ var DeckConfigSchema = z.object({
92
+ // Presentation metadata
93
+ title: z.string().optional(),
94
+ author: z.string().optional(),
95
+ date: z.string().optional(),
96
+ // Theme (already validated Theme object)
97
+ theme: ThemeSchema,
98
+ // Presentation settings
99
+ settings: SettingsSchema.optional(),
100
+ // Export settings
101
+ export: ExportSettingsSchema.optional()
102
+ });
103
+
104
+ // src/schemas/validation.ts
105
+ var ValidationError = class extends Error {
106
+ constructor(message) {
107
+ super(message);
108
+ this.name = "ValidationError";
109
+ }
110
+ };
111
+ function formatZodError(error, context) {
112
+ const issues = error.issues.map((issue) => {
113
+ const path = issue.path.join(".");
114
+ return ` - ${path ? `${path}: ` : ""}${issue.message}`;
115
+ });
116
+ return `Invalid ${context}:
117
+ ${issues.join("\n")}`;
118
+ }
119
+ function safeParse(schema, data, context) {
120
+ const result = schema.safeParse(data);
121
+ if (!result.success) {
122
+ throw new ValidationError(formatZodError(result.error, context));
123
+ }
124
+ return result.data;
125
+ }
126
+
127
+ // src/core/theme.ts
128
+ function createThemeFromMerge(base, overrides) {
129
+ const merged = deepmerge(base, overrides, {
130
+ // Arrays should be replaced, not concatenated
131
+ arrayMerge: (_, source) => source
132
+ });
133
+ const validated = safeParse(ThemeSchema, merged, "merged theme");
134
+ return {
135
+ ...validated,
136
+ extend(newOverrides) {
137
+ return createThemeFromMerge(validated, newOverrides);
138
+ }
139
+ };
140
+ }
141
+ function createTheme(yaml) {
142
+ const parsed = parse(yaml);
143
+ const validated = safeParse(ThemeSchema, parsed, "theme");
144
+ return {
145
+ ...validated,
146
+ extend(overrides) {
147
+ return createThemeFromMerge(validated, overrides);
148
+ }
149
+ };
150
+ }
151
+
152
+ // src/index.ts
153
+ function defineConfig(config) {
154
+ return DeckConfigSchema.parse(config);
155
+ }
156
+
157
+ export { createTheme, defineConfig };
158
+ //# sourceMappingURL=index.js.map
159
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/schemas/theme.ts","../src/schemas/config.ts","../src/schemas/validation.ts","../src/core/theme.ts","../src/index.ts"],"names":["z","parseYaml"],"mappings":"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","file":"index.js","sourcesContent":["import { z } from 'zod'\n\n/**\n * Schema for validating hex color strings.\n * Accepts 6-digit hex colors with # prefix (e.g., #ff0066)\n */\nexport const HexColorSchema = z.string().regex(/^#[0-9a-fA-F]{6}$/, {\n message: 'Color must be a valid hex color (e.g., #ff0066)',\n})\n\nexport type HexColor = z.infer<typeof HexColorSchema>\n\n/**\n * Schema for validating gradient color arrays.\n * A gradient requires at least 2 hex colors.\n */\nexport const GradientSchema = z.array(HexColorSchema).min(2, {\n message: 'Gradient must have at least 2 colors',\n})\n\nexport type Gradient = z.infer<typeof GradientSchema>\n\n/**\n * Schema for validating theme objects.\n * Defines the visual appearance of the presentation deck.\n */\nexport const ThemeSchema = z.object({\n // Theme metadata\n name: z.string().min(1, { message: 'Theme name is required' }),\n description: z.string().optional(),\n author: z.string().optional(),\n version: z.string().optional(),\n\n // Color palette\n colors: z.object({\n primary: HexColorSchema,\n secondary: HexColorSchema.optional(),\n accent: HexColorSchema,\n background: HexColorSchema,\n text: HexColorSchema,\n muted: HexColorSchema,\n success: HexColorSchema.optional(),\n warning: HexColorSchema.optional(),\n error: HexColorSchema.optional(),\n }),\n\n // Named gradients for bigText\n gradients: z.record(z.string(), GradientSchema).refine(\n (g) => Object.keys(g).length >= 1,\n { message: 'At least one gradient must be defined' }\n ),\n\n // Glyph set for matrix rain background\n glyphs: z.string().min(10, {\n message: 'Glyph set must have at least 10 characters',\n }),\n\n // Animation settings\n animations: z.object({\n // Speed multiplier (1.0 = normal, 0.5 = half speed, 2.0 = double speed)\n revealSpeed: z.number().min(0.1).max(5.0).default(1.0),\n // Matrix rain density (number of drops)\n matrixDensity: z.number().min(10).max(200).default(50),\n // Glitch effect iterations\n glitchIterations: z.number().min(1).max(20).default(5),\n // Delay between lines during reveal (ms)\n lineDelay: z.number().min(0).max(500).default(30),\n // Matrix rain update interval (ms)\n matrixInterval: z.number().min(20).max(200).default(80),\n }),\n\n // Window appearance\n window: z.object({\n // Border style\n borderStyle: z.enum(['line', 'double', 'rounded', 'none']).default('line'),\n // Shadow effect\n shadow: z.boolean().default(true),\n // Padding inside windows\n padding: z.object({\n top: z.number().min(0).max(5).default(1),\n bottom: z.number().min(0).max(5).default(1),\n left: z.number().min(0).max(10).default(2),\n right: z.number().min(0).max(10).default(2),\n }).optional(),\n }).optional(),\n})\n\nexport type Theme = z.infer<typeof ThemeSchema>\n\n// ============================================================================\n// Color Token System\n// ============================================================================\n\n/**\n * Valid color tokens for inline styling in slide body content.\n * Tokens can be either:\n * - Built-in colors: GREEN, ORANGE, CYAN, PINK, WHITE, GRAY\n * - Theme-mapped colors: PRIMARY, SECONDARY, ACCENT, MUTED, TEXT, BACKGROUND\n *\n * Usage in slides: {GREEN}colored text{/}\n */\nexport const ColorTokens = [\n 'GREEN',\n 'ORANGE',\n 'CYAN',\n 'PINK',\n 'WHITE',\n 'GRAY',\n 'PRIMARY', // Maps to theme.colors.primary\n 'SECONDARY', // Maps to theme.colors.secondary\n 'ACCENT', // Maps to theme.colors.accent\n 'MUTED', // Maps to theme.colors.muted\n 'TEXT', // Maps to theme.colors.text\n 'BACKGROUND', // Maps to theme.colors.background\n] as const\n\n/**\n * Type for valid color token names.\n */\nexport type ColorToken = typeof ColorTokens[number]\n\n/**\n * Pattern for matching color tokens in slide content.\n * Matches: {GREEN}, {ORANGE}, {CYAN}, {PINK}, {WHITE}, {GRAY},\n * {PRIMARY}, {SECONDARY}, {ACCENT}, {MUTED}, {TEXT}, {BACKGROUND}, {/}\n *\n * The {/} token closes any open color tag.\n */\nexport const COLOR_TOKEN_PATTERN = /\\{(GREEN|ORANGE|CYAN|PINK|WHITE|GRAY|PRIMARY|SECONDARY|ACCENT|MUTED|TEXT|BACKGROUND|\\/)\\}/g\n\n// ============================================================================\n// Partial Theme for Extension\n// ============================================================================\n\n/**\n * Deep partial utility type that makes all nested properties optional.\n * Used for theme extension where only specific fields need to be overridden.\n */\nexport type DeepPartial<T> = T extends object ? {\n [P in keyof T]?: DeepPartial<T[P]>\n} : T\n\n/**\n * Partial theme type for use with theme.extend() functionality.\n * All fields (including nested) become optional.\n */\nexport type PartialTheme = DeepPartial<Theme>\n\n/**\n * Schema for validating partial theme objects.\n * All fields become optional recursively, allowing partial overrides.\n * Used for theme extension validation.\n */\nexport const PartialThemeSchema = ThemeSchema.deepPartial()\n\n// ============================================================================\n// Default Theme\n// ============================================================================\n\n/**\n * Default matrix/cyberpunk theme.\n * Used when no theme is specified or as a base for theme extension.\n */\nexport const DEFAULT_THEME: Theme = {\n name: 'matrix',\n description: 'Default cyberpunk/matrix theme',\n\n colors: {\n primary: '#00cc66',\n accent: '#ff6600',\n background: '#0a0a0a',\n text: '#ffffff',\n muted: '#666666',\n },\n\n gradients: {\n fire: ['#ff6600', '#ff3300', '#ff0066'],\n cool: ['#00ccff', '#0066ff', '#6600ff'],\n pink: ['#ff0066', '#ff0099', '#cc00ff'],\n hf: ['#99cc00', '#00cc66', '#00cccc'],\n },\n\n glyphs: 'アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン0123456789',\n\n animations: {\n revealSpeed: 1.0,\n matrixDensity: 50,\n glitchIterations: 5,\n lineDelay: 30,\n matrixInterval: 80,\n },\n\n window: {\n borderStyle: 'line',\n shadow: true,\n padding: {\n top: 1,\n bottom: 1,\n left: 2,\n right: 2,\n },\n },\n}\n","import { z } from 'zod'\nimport { ThemeSchema } from './theme'\n\n/**\n * Schema for presentation settings.\n * Controls how the presentation behaves during runtime.\n */\nexport const SettingsSchema = z.object({\n // Start slide (0-indexed)\n startSlide: z.number().min(0).default(0),\n // Loop back to first slide after last\n loop: z.boolean().default(false),\n // Auto-advance slides (ms, 0 = disabled)\n autoAdvance: z.number().min(0).default(0),\n // Show slide numbers\n showSlideNumbers: z.boolean().default(false),\n // Show progress bar\n showProgress: z.boolean().default(false),\n})\n\nexport type Settings = z.infer<typeof SettingsSchema>\n\n/**\n * Schema for export settings.\n * Controls the output dimensions and quality of exported videos/GIFs.\n */\nexport const ExportSettingsSchema = z.object({\n // Output width in characters (min 80, max 400)\n width: z.number().min(80).max(400).default(120),\n // Output height in characters (min 24, max 100)\n height: z.number().min(24).max(100).default(40),\n // Frames per second for video (min 10, max 60)\n fps: z.number().min(10).max(60).default(30),\n})\n\nexport type ExportSettings = z.infer<typeof ExportSettingsSchema>\n\n/**\n * Schema for validating deck configuration (deck.config.ts).\n * Defines the complete configuration for a presentation deck.\n */\nexport const DeckConfigSchema = z.object({\n // Presentation metadata\n title: z.string().optional(),\n author: z.string().optional(),\n date: z.string().optional(),\n\n // Theme (already validated Theme object)\n theme: ThemeSchema,\n\n // Presentation settings\n settings: SettingsSchema.optional(),\n\n // Export settings\n export: ExportSettingsSchema.optional(),\n})\n\nexport type DeckConfig = z.infer<typeof DeckConfigSchema>\n","import { z, ZodError } from 'zod'\n\n/**\n * Custom error class for validation failures.\n * Extends Error with a specific name for easy identification.\n */\nexport class ValidationError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'ValidationError'\n }\n}\n\n/**\n * Format Zod errors into user-friendly messages.\n * Formats each issue with its field path and message.\n *\n * @param error - The ZodError to format\n * @param context - A description of what was being validated (e.g., \"theme\", \"slide frontmatter\")\n * @returns A formatted string with all validation issues\n *\n * @example\n * const error = new ZodError([...])\n * formatZodError(error, 'theme')\n * // Returns:\n * // \"Invalid theme:\n * // - colors.primary: Color must be a valid hex color (e.g., #ff0066)\n * // - name: Theme name is required\"\n */\nexport function formatZodError(error: ZodError, context: string): string {\n const issues = error.issues.map((issue) => {\n const path = issue.path.join('.')\n return ` - ${path ? `${path}: ` : ''}${issue.message}`\n })\n\n return `Invalid ${context}:\\n${issues.join('\\n')}`\n}\n\n/**\n * Parse data with a Zod schema and throw a ValidationError with friendly messages on failure.\n *\n * @param schema - The Zod schema to validate against\n * @param data - The data to validate\n * @param context - A description of what's being validated (e.g., \"theme\", \"slide frontmatter\")\n * @returns The parsed and validated data\n * @throws {ValidationError} If validation fails\n *\n * @example\n * const theme = safeParse(ThemeSchema, rawData, 'theme')\n * // Throws ValidationError with formatted message if invalid\n */\nexport function safeParse<T>(\n schema: z.ZodSchema<T>,\n data: unknown,\n context: string\n): T {\n const result = schema.safeParse(data)\n\n if (!result.success) {\n throw new ValidationError(formatZodError(result.error, context))\n }\n\n return result.data\n}\n","import { parse as parseYaml } from 'yaml'\nimport deepmerge from 'deepmerge'\nimport type { Theme, PartialTheme } from '../schemas/theme'\nimport { ThemeSchema } from '../schemas/theme'\nimport { safeParse } from '../schemas/validation'\n\n/**\n * Theme object with extension capability.\n * Extends the base Theme type with an extend() method that allows\n * Tailwind-style theme customization.\n */\nexport interface ThemeObject extends Theme {\n /**\n * Create a new theme by merging overrides into this theme.\n * Uses deep merge with array replacement strategy.\n *\n * @param overrides - Partial theme object with values to override\n * @returns A new ThemeObject with the merged values\n *\n * @example\n * const custom = matrix.extend({\n * colors: { primary: '#ff0066' }\n * })\n *\n * @example\n * // Chained extensions\n * const custom = matrix\n * .extend({ colors: { primary: '#ff0066' } })\n * .extend({ animations: { revealSpeed: 0.5 } })\n */\n extend(overrides: PartialTheme): ThemeObject\n}\n\n/**\n * Create a ThemeObject from validated Theme and overrides.\n * Internal helper that merges themes and adds the extend() method.\n *\n * @param base - A validated Theme object\n * @param overrides - Partial theme with values to override\n * @returns A ThemeObject with extension capability\n */\nexport function createThemeFromMerge(base: Theme, overrides: PartialTheme): ThemeObject {\n // Deep merge, with overrides taking precedence\n const merged = deepmerge(base, overrides, {\n // Arrays should be replaced, not concatenated\n arrayMerge: (_, source) => source,\n }) as Theme\n\n // Re-validate the merged result\n const validated = safeParse(ThemeSchema, merged, 'merged theme')\n\n return {\n ...validated,\n extend(newOverrides: PartialTheme): ThemeObject {\n return createThemeFromMerge(validated, newOverrides)\n },\n }\n}\n\n/**\n * Create a theme from a YAML string.\n * Parses the YAML, validates it against ThemeSchema, and returns a ThemeObject\n * with extension capability.\n *\n * @param yaml - The YAML string containing the theme definition\n * @returns A validated ThemeObject with extend() method\n * @throws {Error} If the YAML syntax is invalid\n * @throws {ValidationError} If the parsed data doesn't match ThemeSchema\n *\n * @example\n * const theme = createTheme(`\n * name: custom\n * colors:\n * primary: \"#ff0066\"\n * accent: \"#00ff66\"\n * background: \"#000000\"\n * text: \"#ffffff\"\n * muted: \"#666666\"\n * gradients:\n * main:\n * - \"#ff0066\"\n * - \"#00ff66\"\n * glyphs: \"0123456789ABCDEF\"\n * animations:\n * revealSpeed: 1.0\n * matrixDensity: 30\n * glitchIterations: 3\n * lineDelay: 20\n * matrixInterval: 100\n * `)\n */\nexport function createTheme(yaml: string): ThemeObject {\n const parsed = parseYaml(yaml)\n const validated = safeParse(ThemeSchema, parsed, 'theme')\n\n return {\n ...validated,\n extend(overrides: PartialTheme): ThemeObject {\n return createThemeFromMerge(validated, overrides)\n },\n }\n}\n\n// Re-export commonly used functions for convenience\nexport { ThemeError, formatThemeError } from './theme-errors'\nexport {\n createGradients,\n applyGradient,\n resolveColorToken,\n colorTokensToBlessedTags,\n BUILTIN_COLORS,\n} from './theme-colors'\nexport type { GradientFunction } from './theme-colors'\n","/**\n * Public API for term-deck\n *\n * Exports functions and types for use in deck.config.ts files\n * and external integrations.\n */\n\nimport { DeckConfigSchema, type DeckConfig } from './schemas/config.js';\nimport { type Theme } from './schemas/theme.js';\nimport { type Slide, type SlideFrontmatter } from './schemas/slide.js';\nimport { createTheme, type ThemeObject } from './core/theme.js';\n\n/**\n * Define deck configuration with validation\n *\n * Usage in deck.config.ts:\n * ```typescript\n * import { defineConfig } from 'term-deck'\n * import matrix from '@term-deck/theme-matrix'\n *\n * export default defineConfig({\n * title: 'My Presentation',\n * theme: matrix,\n * })\n * ```\n */\nexport function defineConfig(config: DeckConfig): DeckConfig {\n return DeckConfigSchema.parse(config);\n}\n\n// Re-export theme creation\nexport { createTheme };\nexport type { ThemeObject };\n\n// Re-export types for consumers\nexport type { DeckConfig, Theme, Slide, SlideFrontmatter };\n"]}
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@pep/term-deck",
3
- "version": "1.0.14",
3
+ "version": "1.0.16",
4
4
  "description": "Terminal presentation tool with a cyberpunk aesthetic",
5
5
  "type": "module",
6
6
  "bin": {
7
- "term-deck": "./bin/term-deck.js"
7
+ "term-deck": "./dist/bin/term-deck.js"
8
8
  },
9
- "main": "src/index.ts",
10
- "types": "src/index.ts",
9
+ "main": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
11
  "keywords": [
12
12
  "presentation",
13
13
  "terminal",
@@ -32,34 +32,37 @@
32
32
  "node": ">=18.0.0"
33
33
  },
34
34
  "files": [
35
- "src/",
36
- "bin/",
35
+ "dist/",
37
36
  "themes/",
38
37
  "examples/",
39
38
  "LICENSE",
40
39
  "README.md"
41
40
  ],
42
41
  "dependencies": {
42
+ "canvas": "^3.2.1",
43
43
  "commander": "^14.0.2",
44
- "neo-blessed": "^0.2.0",
44
+ "deepmerge": "^4.3.1",
45
+ "execa": "^9.6.1",
46
+ "fast-glob": "^3.3.3",
45
47
  "figlet": "^1.9.4",
46
48
  "gradient-string": "^3.0.0",
47
49
  "gray-matter": "^4.0.3",
48
- "yaml": "^2.7.0",
49
- "zod": "^3.22.4",
50
- "deepmerge": "^4.3.1",
51
50
  "mermaid-ascii": "^1.0.0",
52
- "canvas": "^3.2.1",
53
- "tsx": "^4.19.2"
51
+ "neo-blessed": "^0.2.0",
52
+ "yaml": "^2.7.0",
53
+ "zod": "^3.22.4"
54
54
  },
55
55
  "devDependencies": {
56
- "@types/node": "^22.0.0",
57
56
  "@types/figlet": "^1.5.8",
57
+ "@types/node": "^22.0.0",
58
+ "tsup": "^8.5.1",
59
+ "tsx": "^4.19.2",
58
60
  "typescript": "^5.9.3",
59
61
  "vitest": "^2.1.8"
60
62
  },
61
63
  "scripts": {
62
64
  "dev": "tsx bin/term-deck.ts",
65
+ "build": "tsup",
63
66
  "test": "vitest",
64
67
  "typecheck": "tsc --noEmit"
65
68
  }
package/bin/term-deck.js DELETED
@@ -1,14 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * JavaScript wrapper for term-deck CLI
4
- * Uses tsx to execute the TypeScript entry point
5
- */
6
-
7
- import { register } from 'node:module';
8
- import { pathToFileURL } from 'node:url';
9
-
10
- // Register tsx for TypeScript execution
11
- register('tsx/esm', pathToFileURL('./'));
12
-
13
- // Import and run the TypeScript CLI
14
- await import('./term-deck.ts');
package/bin/term-deck.ts DELETED
@@ -1,45 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * CLI Entry Point for term-deck
4
- *
5
- * Terminal presentation tool with a cyberpunk aesthetic.
6
- * Provides commands for presenting, exporting, and initializing decks.
7
- */
8
-
9
- import { Command } from 'commander';
10
- import { version } from '../package.json';
11
- import { presentCommand } from '../src/cli/commands/present.js';
12
- import { exportCommand } from '../src/cli/commands/export.js';
13
- import { initCommand } from '../src/cli/commands/init.js';
14
- import { handleError } from '../src/cli/errors.js';
15
-
16
- const program = new Command();
17
-
18
- program
19
- .name('term-deck')
20
- .description('Terminal presentation tool with a cyberpunk aesthetic')
21
- .version(version);
22
-
23
- // Register commands
24
- program.addCommand(presentCommand);
25
- program.addCommand(exportCommand);
26
- program.addCommand(initCommand);
27
-
28
- // Default action: present if directory given, else show help
29
- program
30
- .argument('[dir]', 'Slides directory to present')
31
- .action(async (dir) => {
32
- if (dir) {
33
- // Default action: present the deck
34
- try {
35
- const { present } = await import('../src/presenter/main.js');
36
- await present(dir, {});
37
- } catch (error) {
38
- handleError(error);
39
- }
40
- } else {
41
- program.help();
42
- }
43
- });
44
-
45
- program.parse();
@@ -1,201 +0,0 @@
1
- /**
2
- * Tests for CLI error handling
3
- */
4
-
5
- import { describe, test, expect, mock, spyOn, beforeEach, afterEach } from 'bun:test';
6
- import { handleError } from '../errors.js';
7
- import { ValidationError } from '../../schemas/validation.js';
8
- import { SlideParseError, DeckLoadError } from '../../core/slide.js';
9
- import { ThemeError } from '../../core/theme.js';
10
-
11
- describe('handleError', () => {
12
- let consoleErrorSpy: ReturnType<typeof spyOn>;
13
- let processExitSpy: ReturnType<typeof spyOn>;
14
- let originalDebug: string | undefined;
15
-
16
- beforeEach(() => {
17
- consoleErrorSpy = spyOn(console, 'error').mockImplementation(() => {});
18
- processExitSpy = spyOn(process, 'exit').mockImplementation(
19
- (() => {}) as never,
20
- );
21
- originalDebug = process.env.DEBUG;
22
- });
23
-
24
- afterEach(() => {
25
- consoleErrorSpy.mockRestore();
26
- processExitSpy.mockRestore();
27
- process.env.DEBUG = originalDebug;
28
- });
29
-
30
- test('handles ValidationError', () => {
31
- const error = new ValidationError('Invalid theme configuration');
32
-
33
- handleError(error);
34
-
35
- expect(consoleErrorSpy).toHaveBeenCalledWith(
36
- expect.stringContaining('Invalid theme configuration'),
37
- );
38
- expect(processExitSpy).toHaveBeenCalledWith(1);
39
- });
40
-
41
- test('handles SlideParseError', () => {
42
- const cause = new Error('Missing title field');
43
- const error = new SlideParseError('Invalid frontmatter', 'slides/01.md', cause);
44
-
45
- handleError(error);
46
-
47
- expect(consoleErrorSpy).toHaveBeenCalledWith(
48
- expect.stringContaining('slides/01.md'),
49
- );
50
- expect(consoleErrorSpy).toHaveBeenCalledWith(
51
- expect.stringContaining('Invalid frontmatter'),
52
- );
53
- expect(consoleErrorSpy).toHaveBeenCalledWith(
54
- expect.stringContaining('Missing title field'),
55
- );
56
- expect(processExitSpy).toHaveBeenCalledWith(1);
57
- });
58
-
59
- test('handles SlideParseError without cause', () => {
60
- const error = new SlideParseError('Invalid frontmatter', 'slides/02.md');
61
-
62
- handleError(error);
63
-
64
- expect(consoleErrorSpy).toHaveBeenCalledWith(
65
- expect.stringContaining('slides/02.md'),
66
- );
67
- expect(processExitSpy).toHaveBeenCalledWith(1);
68
- });
69
-
70
- test('handles DeckLoadError', () => {
71
- const error = new DeckLoadError('No slides found', './slides');
72
-
73
- handleError(error);
74
-
75
- expect(consoleErrorSpy).toHaveBeenCalledWith(
76
- expect.stringContaining('./slides'),
77
- );
78
- expect(consoleErrorSpy).toHaveBeenCalledWith(
79
- expect.stringContaining('No slides found'),
80
- );
81
- expect(processExitSpy).toHaveBeenCalledWith(1);
82
- });
83
-
84
- test('handles ThemeError', () => {
85
- const error = new ThemeError('Invalid color format', 'custom-theme');
86
-
87
- handleError(error);
88
-
89
- expect(consoleErrorSpy).toHaveBeenCalledWith(
90
- expect.stringContaining('Theme error'),
91
- );
92
- expect(consoleErrorSpy).toHaveBeenCalledWith(
93
- expect.stringContaining('Invalid color format'),
94
- );
95
- expect(processExitSpy).toHaveBeenCalledWith(1);
96
- });
97
-
98
- test('handles ENOENT errors', () => {
99
- const error = new Error('ENOENT: no such file or directory');
100
-
101
- handleError(error);
102
-
103
- expect(consoleErrorSpy).toHaveBeenCalledWith(
104
- expect.stringContaining('File or directory not found'),
105
- );
106
- expect(processExitSpy).toHaveBeenCalledWith(1);
107
- });
108
-
109
- test('handles ffmpeg errors', () => {
110
- const error = new Error('ffmpeg not found in PATH');
111
-
112
- handleError(error);
113
-
114
- expect(consoleErrorSpy).toHaveBeenCalledWith(
115
- expect.stringContaining('ffmpeg error'),
116
- );
117
- expect(consoleErrorSpy).toHaveBeenCalledWith(
118
- expect.stringContaining('Make sure ffmpeg is installed'),
119
- );
120
- expect(consoleErrorSpy).toHaveBeenCalledWith(
121
- expect.stringContaining('brew install ffmpeg'),
122
- );
123
- expect(processExitSpy).toHaveBeenCalledWith(1);
124
- });
125
-
126
- test('handles generic errors', () => {
127
- const error = new Error('Something went wrong');
128
-
129
- handleError(error);
130
-
131
- expect(consoleErrorSpy).toHaveBeenCalledWith(
132
- expect.stringContaining('Something went wrong'),
133
- );
134
- expect(processExitSpy).toHaveBeenCalledWith(1);
135
- });
136
-
137
- test('shows stack trace when DEBUG is set', () => {
138
- process.env.DEBUG = '1';
139
- const error = new Error('Debug error');
140
- error.stack = 'Error: Debug error\n at test.ts:10:20';
141
-
142
- handleError(error);
143
-
144
- expect(consoleErrorSpy).toHaveBeenCalledWith(
145
- expect.stringContaining('at test.ts'),
146
- );
147
- expect(processExitSpy).toHaveBeenCalledWith(1);
148
- });
149
-
150
- test('hides stack trace when DEBUG is not set', () => {
151
- delete process.env.DEBUG;
152
- const error = new Error('Regular error');
153
- error.stack = 'Error: Regular error\n at test.ts:10:20';
154
-
155
- handleError(error);
156
-
157
- // Should show error message
158
- expect(consoleErrorSpy).toHaveBeenCalledWith(
159
- expect.stringContaining('Regular error'),
160
- );
161
-
162
- // Should NOT show stack trace
163
- const calls = consoleErrorSpy.mock.calls;
164
- const hasStackTrace = calls.some((call) =>
165
- call.some((arg) => String(arg).includes('at test.ts')),
166
- );
167
- expect(hasStackTrace).toBe(false);
168
-
169
- expect(processExitSpy).toHaveBeenCalledWith(1);
170
- });
171
-
172
- test('handles unknown errors', () => {
173
- const error = 'string error';
174
-
175
- handleError(error);
176
-
177
- expect(consoleErrorSpy).toHaveBeenCalledWith(
178
- expect.stringContaining('Unknown error'),
179
- );
180
- expect(processExitSpy).toHaveBeenCalledWith(1);
181
- });
182
-
183
- test('exits with code 1 for all errors', () => {
184
- const errors = [
185
- new ValidationError('test'),
186
- new SlideParseError('test', 'test.md'),
187
- new DeckLoadError('test', './slides'),
188
- new ThemeError('test', 'theme'),
189
- new Error('ENOENT'),
190
- new Error('ffmpeg'),
191
- new Error('generic'),
192
- 'unknown',
193
- ];
194
-
195
- for (const error of errors) {
196
- processExitSpy.mockClear();
197
- handleError(error);
198
- expect(processExitSpy).toHaveBeenCalledWith(1);
199
- }
200
- });
201
- });