@pep/term-deck 1.0.13 → 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.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
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for CLI help text
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { describe, test, expect } from 'bun:test';
|
|
6
|
-
import { Command } from 'commander';
|
|
7
|
-
import { presentCommand } from '../commands/present.js';
|
|
8
|
-
import { exportCommand } from '../commands/export.js';
|
|
9
|
-
import { initCommand } from '../commands/init.js';
|
|
10
|
-
|
|
11
|
-
describe('CLI help text', () => {
|
|
12
|
-
test('present command has description', () => {
|
|
13
|
-
expect(presentCommand.description()).toBeTruthy();
|
|
14
|
-
expect(presentCommand.description()).toContain('present');
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
test('present command has all options', () => {
|
|
18
|
-
const options = presentCommand.options;
|
|
19
|
-
|
|
20
|
-
const optionNames = options.map((opt) => opt.long);
|
|
21
|
-
expect(optionNames).toContain('--start');
|
|
22
|
-
expect(optionNames).toContain('--notes');
|
|
23
|
-
expect(optionNames).toContain('--notes-tty');
|
|
24
|
-
expect(optionNames).toContain('--loop');
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
test('present command has short options', () => {
|
|
28
|
-
const options = presentCommand.options;
|
|
29
|
-
|
|
30
|
-
const shortNames = options.map((opt) => opt.short).filter(Boolean);
|
|
31
|
-
expect(shortNames).toContain('-s');
|
|
32
|
-
expect(shortNames).toContain('-n');
|
|
33
|
-
expect(shortNames).toContain('-l');
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
test('present command requires dir argument', () => {
|
|
37
|
-
const args = presentCommand.registeredArguments;
|
|
38
|
-
expect(args.length).toBeGreaterThan(0);
|
|
39
|
-
expect(args[0].name()).toBe('dir');
|
|
40
|
-
expect(args[0].required).toBe(true);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
test('export command has description', () => {
|
|
44
|
-
expect(exportCommand.description()).toBeTruthy();
|
|
45
|
-
expect(exportCommand.description().toLowerCase()).toContain('export');
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
test('export command has all options', () => {
|
|
49
|
-
const options = exportCommand.options;
|
|
50
|
-
|
|
51
|
-
const optionNames = options.map((opt) => opt.long);
|
|
52
|
-
expect(optionNames).toContain('--output');
|
|
53
|
-
expect(optionNames).toContain('--width');
|
|
54
|
-
expect(optionNames).toContain('--height');
|
|
55
|
-
expect(optionNames).toContain('--fps');
|
|
56
|
-
expect(optionNames).toContain('--slide-time');
|
|
57
|
-
expect(optionNames).toContain('--quality');
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
test('export command has short options', () => {
|
|
61
|
-
const options = exportCommand.options;
|
|
62
|
-
|
|
63
|
-
const shortNames = options.map((opt) => opt.short).filter(Boolean);
|
|
64
|
-
expect(shortNames).toContain('-o');
|
|
65
|
-
expect(shortNames).toContain('-w');
|
|
66
|
-
expect(shortNames).toContain('-h');
|
|
67
|
-
expect(shortNames).toContain('-t');
|
|
68
|
-
expect(shortNames).toContain('-q');
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
test('export command requires dir argument', () => {
|
|
72
|
-
const args = exportCommand.registeredArguments;
|
|
73
|
-
expect(args.length).toBeGreaterThan(0);
|
|
74
|
-
expect(args[0].name()).toBe('dir');
|
|
75
|
-
expect(args[0].required).toBe(true);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
test('export command requires output option', () => {
|
|
79
|
-
const outputOption = exportCommand.options.find((opt) => opt.long === '--output');
|
|
80
|
-
expect(outputOption).toBeTruthy();
|
|
81
|
-
expect(outputOption?.required).toBe(true);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
test('init command has description', () => {
|
|
85
|
-
expect(initCommand.description()).toBeTruthy();
|
|
86
|
-
expect(initCommand.description()).toContain('presentation deck');
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
test('init command has theme option', () => {
|
|
90
|
-
const options = initCommand.options;
|
|
91
|
-
|
|
92
|
-
const optionNames = options.map((opt) => opt.long);
|
|
93
|
-
expect(optionNames).toContain('--theme');
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
test('init command has short theme option', () => {
|
|
97
|
-
const options = initCommand.options;
|
|
98
|
-
|
|
99
|
-
const shortNames = options.map((opt) => opt.short).filter(Boolean);
|
|
100
|
-
expect(shortNames).toContain('-t');
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
test('init command requires name argument', () => {
|
|
104
|
-
const args = initCommand.registeredArguments;
|
|
105
|
-
expect(args.length).toBeGreaterThan(0);
|
|
106
|
-
expect(args[0].name()).toBe('name');
|
|
107
|
-
expect(args[0].required).toBe(true);
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
test('main program would have version', () => {
|
|
111
|
-
// Test that version can be imported from package.json
|
|
112
|
-
const pkg = require('../../../package.json');
|
|
113
|
-
expect(pkg.version).toBeTruthy();
|
|
114
|
-
expect(typeof pkg.version).toBe('string');
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
test('main program would have name', () => {
|
|
118
|
-
const pkg = require('../../../package.json');
|
|
119
|
-
expect(pkg.name).toBeTruthy();
|
|
120
|
-
expect(pkg.name).toBe('term-deck');
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
test('all commands have help text', () => {
|
|
124
|
-
const commands = [presentCommand, exportCommand, initCommand];
|
|
125
|
-
|
|
126
|
-
for (const cmd of commands) {
|
|
127
|
-
// Commander automatically generates help
|
|
128
|
-
const helpInfo = cmd.helpInformation();
|
|
129
|
-
expect(helpInfo).toBeTruthy();
|
|
130
|
-
expect(helpInfo.length).toBeGreaterThan(0);
|
|
131
|
-
}
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
test('present command help includes options descriptions', () => {
|
|
135
|
-
const helpInfo = presentCommand.helpInformation();
|
|
136
|
-
|
|
137
|
-
expect(helpInfo).toContain('start');
|
|
138
|
-
expect(helpInfo).toContain('notes');
|
|
139
|
-
expect(helpInfo).toContain('loop');
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
test('export command help includes options descriptions', () => {
|
|
143
|
-
const helpInfo = exportCommand.helpInformation();
|
|
144
|
-
|
|
145
|
-
expect(helpInfo).toContain('output');
|
|
146
|
-
expect(helpInfo).toContain('width');
|
|
147
|
-
expect(helpInfo).toContain('height');
|
|
148
|
-
expect(helpInfo).toContain('fps');
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
test('init command help includes options descriptions', () => {
|
|
152
|
-
const helpInfo = initCommand.helpInformation();
|
|
153
|
-
|
|
154
|
-
expect(helpInfo).toContain('theme');
|
|
155
|
-
expect(helpInfo).toContain('name');
|
|
156
|
-
});
|
|
157
|
-
});
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for init command
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
|
|
6
|
-
import { rmSync, existsSync } from 'node:fs';
|
|
7
|
-
import { join } from 'node:path';
|
|
8
|
-
import { initDeck } from '../commands/init.js';
|
|
9
|
-
|
|
10
|
-
const TEST_DECK_NAME = 'test-deck-init';
|
|
11
|
-
const TEST_DECK_PATH = join(process.cwd(), TEST_DECK_NAME);
|
|
12
|
-
|
|
13
|
-
describe('initDeck', () => {
|
|
14
|
-
beforeEach(() => {
|
|
15
|
-
// Clean up if test directory exists
|
|
16
|
-
if (existsSync(TEST_DECK_PATH)) {
|
|
17
|
-
rmSync(TEST_DECK_PATH, { recursive: true, force: true });
|
|
18
|
-
}
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
afterEach(() => {
|
|
22
|
-
// Clean up after test
|
|
23
|
-
if (existsSync(TEST_DECK_PATH)) {
|
|
24
|
-
rmSync(TEST_DECK_PATH, { recursive: true, force: true });
|
|
25
|
-
}
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
test('creates deck directory structure', async () => {
|
|
29
|
-
await initDeck(TEST_DECK_NAME, 'matrix');
|
|
30
|
-
|
|
31
|
-
// Check directories
|
|
32
|
-
expect(existsSync(TEST_DECK_PATH)).toBe(true);
|
|
33
|
-
expect(existsSync(join(TEST_DECK_PATH, 'slides'))).toBe(true);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
test('creates deck.config.ts', async () => {
|
|
37
|
-
await initDeck(TEST_DECK_NAME, 'matrix');
|
|
38
|
-
|
|
39
|
-
const configPath = join(TEST_DECK_PATH, 'slides', 'deck.config.ts');
|
|
40
|
-
expect(existsSync(configPath)).toBe(true);
|
|
41
|
-
|
|
42
|
-
const content = await Bun.file(configPath).text();
|
|
43
|
-
expect(content).toContain('import { defineConfig }');
|
|
44
|
-
expect(content).toContain('import matrix from');
|
|
45
|
-
expect(content).toContain(`title: '${TEST_DECK_NAME}'`);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
test('creates sample slides', async () => {
|
|
49
|
-
await initDeck(TEST_DECK_NAME, 'matrix');
|
|
50
|
-
|
|
51
|
-
const slidesDir = join(TEST_DECK_PATH, 'slides');
|
|
52
|
-
expect(existsSync(join(slidesDir, '01-intro.md'))).toBe(true);
|
|
53
|
-
expect(existsSync(join(slidesDir, '02-content.md'))).toBe(true);
|
|
54
|
-
expect(existsSync(join(slidesDir, '03-end.md'))).toBe(true);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
test('slides have valid frontmatter', async () => {
|
|
58
|
-
await initDeck(TEST_DECK_NAME, 'matrix');
|
|
59
|
-
|
|
60
|
-
const slidesDir = join(TEST_DECK_PATH, 'slides');
|
|
61
|
-
const slide1 = await Bun.file(join(slidesDir, '01-intro.md')).text();
|
|
62
|
-
const slide2 = await Bun.file(join(slidesDir, '02-content.md')).text();
|
|
63
|
-
const slide3 = await Bun.file(join(slidesDir, '03-end.md')).text();
|
|
64
|
-
|
|
65
|
-
// Check frontmatter structure
|
|
66
|
-
expect(slide1).toMatch(/^---\s*\ntitle:/);
|
|
67
|
-
expect(slide1).toContain('bigText:');
|
|
68
|
-
expect(slide1).toContain('gradient:');
|
|
69
|
-
|
|
70
|
-
expect(slide2).toMatch(/^---\s*\ntitle:/);
|
|
71
|
-
expect(slide2).toContain('bigText:');
|
|
72
|
-
expect(slide2).toContain('gradient:');
|
|
73
|
-
|
|
74
|
-
expect(slide3).toMatch(/^---\s*\ntitle:/);
|
|
75
|
-
expect(slide3).toContain('bigText:');
|
|
76
|
-
expect(slide3).toContain('gradient:');
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
test('slide 2 includes presenter notes', async () => {
|
|
80
|
-
await initDeck(TEST_DECK_NAME, 'matrix');
|
|
81
|
-
|
|
82
|
-
const slidesDir = join(TEST_DECK_PATH, 'slides');
|
|
83
|
-
const slide2 = await Bun.file(join(slidesDir, '02-content.md')).text();
|
|
84
|
-
|
|
85
|
-
expect(slide2).toContain('<!-- notes -->');
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
test('creates README.md', async () => {
|
|
89
|
-
await initDeck(TEST_DECK_NAME, 'matrix');
|
|
90
|
-
|
|
91
|
-
const readmePath = join(TEST_DECK_PATH, 'README.md');
|
|
92
|
-
expect(existsSync(readmePath)).toBe(true);
|
|
93
|
-
|
|
94
|
-
const content = await Bun.file(readmePath).text();
|
|
95
|
-
expect(content).toContain(`# ${TEST_DECK_NAME}`);
|
|
96
|
-
expect(content).toContain('term-deck present');
|
|
97
|
-
expect(content).toContain('term-deck export');
|
|
98
|
-
expect(content).toContain('Hotkeys');
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
test('uses deck name in slide titles', async () => {
|
|
102
|
-
await initDeck(TEST_DECK_NAME, 'matrix');
|
|
103
|
-
|
|
104
|
-
const slide1 = await Bun.file(
|
|
105
|
-
join(TEST_DECK_PATH, 'slides', '01-intro.md'),
|
|
106
|
-
).text();
|
|
107
|
-
|
|
108
|
-
expect(slide1).toContain(TEST_DECK_NAME.toUpperCase());
|
|
109
|
-
});
|
|
110
|
-
});
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Export Command
|
|
3
|
-
*
|
|
4
|
-
* Exports a presentation to GIF or MP4 format.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { Command } from 'commander';
|
|
8
|
-
import { exportPresentation } from '../../export/recorder.js';
|
|
9
|
-
import { handleError } from '../errors.js';
|
|
10
|
-
|
|
11
|
-
export const exportCommand = new Command('export')
|
|
12
|
-
.description('Export presentation to GIF or MP4')
|
|
13
|
-
.argument('<dir>', 'Slides directory')
|
|
14
|
-
.requiredOption('-o, --output <file>', 'Output file (.mp4 or .gif)')
|
|
15
|
-
.option('-w, --width <n>', 'Terminal width in characters', '120')
|
|
16
|
-
.option('-h, --height <n>', 'Terminal height in characters', '40')
|
|
17
|
-
.option('--fps <n>', 'Frames per second', '30')
|
|
18
|
-
.option('-t, --slide-time <n>', 'Seconds per slide', '3')
|
|
19
|
-
.option('-q, --quality <n>', 'Quality 1-100 (video only)', '80')
|
|
20
|
-
.action(async (dir, options) => {
|
|
21
|
-
try {
|
|
22
|
-
await exportPresentation(dir, {
|
|
23
|
-
output: options.output,
|
|
24
|
-
width: Number.parseInt(options.width, 10),
|
|
25
|
-
height: Number.parseInt(options.height, 10),
|
|
26
|
-
fps: Number.parseInt(options.fps, 10),
|
|
27
|
-
slideTime: Number.parseFloat(options.slideTime),
|
|
28
|
-
quality: Number.parseInt(options.quality, 10),
|
|
29
|
-
});
|
|
30
|
-
} catch (error) {
|
|
31
|
-
handleError(error);
|
|
32
|
-
}
|
|
33
|
-
});
|
package/src/cli/commands/init.ts
DELETED
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Init Command
|
|
3
|
-
*
|
|
4
|
-
* Creates a new presentation deck with sample slides and configuration.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { Command } from 'commander';
|
|
8
|
-
import { join } from 'node:path';
|
|
9
|
-
import { handleError } from '../errors.js';
|
|
10
|
-
|
|
11
|
-
export const initCommand = new Command('init')
|
|
12
|
-
.description('Create a new presentation deck')
|
|
13
|
-
.argument('<name>', 'Deck name (will create directory)')
|
|
14
|
-
.option('-t, --theme <name>', 'Theme to use', 'matrix')
|
|
15
|
-
.action(async (name, options) => {
|
|
16
|
-
try {
|
|
17
|
-
await initDeck(name, options.theme);
|
|
18
|
-
console.log(`Created deck: ${name}/`);
|
|
19
|
-
console.log('\nNext steps:');
|
|
20
|
-
console.log(` cd ${name}/slides`);
|
|
21
|
-
console.log(' term-deck present .');
|
|
22
|
-
} catch (error) {
|
|
23
|
-
handleError(error);
|
|
24
|
-
}
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Initialize a new deck directory
|
|
29
|
-
*
|
|
30
|
-
* Creates the directory structure, configuration file, sample slides,
|
|
31
|
-
* and README for a new presentation deck.
|
|
32
|
-
*/
|
|
33
|
-
export async function initDeck(name: string, theme: string): Promise<void> {
|
|
34
|
-
const deckDir = join(process.cwd(), name);
|
|
35
|
-
const slidesDir = join(deckDir, 'slides');
|
|
36
|
-
|
|
37
|
-
// Create directories
|
|
38
|
-
await Bun.write(join(slidesDir, '.gitkeep'), '');
|
|
39
|
-
|
|
40
|
-
// Create deck.config.ts
|
|
41
|
-
const configContent = `import { defineConfig } from 'term-deck'
|
|
42
|
-
import matrix from '@term-deck/theme-matrix'
|
|
43
|
-
|
|
44
|
-
export default defineConfig({
|
|
45
|
-
title: '${name}',
|
|
46
|
-
theme: matrix,
|
|
47
|
-
})
|
|
48
|
-
`;
|
|
49
|
-
await Bun.write(join(slidesDir, 'deck.config.ts'), configContent);
|
|
50
|
-
|
|
51
|
-
// Create sample slides
|
|
52
|
-
const slide1 = `---
|
|
53
|
-
title: ${name.toUpperCase()}
|
|
54
|
-
bigText: ${name.toUpperCase()}
|
|
55
|
-
gradient: fire
|
|
56
|
-
---
|
|
57
|
-
|
|
58
|
-
{GREEN}Welcome to your presentation{/}
|
|
59
|
-
|
|
60
|
-
Press {CYAN}Space{/} or {CYAN}→{/} to advance
|
|
61
|
-
`;
|
|
62
|
-
|
|
63
|
-
const slide2 = `---
|
|
64
|
-
title: SLIDE TWO
|
|
65
|
-
bigText: HELLO
|
|
66
|
-
gradient: cool
|
|
67
|
-
---
|
|
68
|
-
|
|
69
|
-
{WHITE}This is the second slide{/}
|
|
70
|
-
|
|
71
|
-
- Point one
|
|
72
|
-
- Point two
|
|
73
|
-
- Point three
|
|
74
|
-
|
|
75
|
-
<!-- notes -->
|
|
76
|
-
Remember to explain each point clearly.
|
|
77
|
-
`;
|
|
78
|
-
|
|
79
|
-
const slide3 = `---
|
|
80
|
-
title: THE END
|
|
81
|
-
bigText: FIN
|
|
82
|
-
gradient: pink
|
|
83
|
-
---
|
|
84
|
-
|
|
85
|
-
{ORANGE}Thank you!{/}
|
|
86
|
-
|
|
87
|
-
Press {CYAN}q{/} to exit
|
|
88
|
-
`;
|
|
89
|
-
|
|
90
|
-
await Bun.write(join(slidesDir, '01-intro.md'), slide1);
|
|
91
|
-
await Bun.write(join(slidesDir, '02-content.md'), slide2);
|
|
92
|
-
await Bun.write(join(slidesDir, '03-end.md'), slide3);
|
|
93
|
-
|
|
94
|
-
// Create README
|
|
95
|
-
const readme = `# ${name}
|
|
96
|
-
|
|
97
|
-
A term-deck presentation.
|
|
98
|
-
|
|
99
|
-
## Usage
|
|
100
|
-
|
|
101
|
-
\`\`\`bash
|
|
102
|
-
cd slides
|
|
103
|
-
term-deck present .
|
|
104
|
-
\`\`\`
|
|
105
|
-
|
|
106
|
-
## Export
|
|
107
|
-
|
|
108
|
-
\`\`\`bash
|
|
109
|
-
term-deck export slides/ -o ${name}.mp4
|
|
110
|
-
term-deck export slides/ -o ${name}.gif
|
|
111
|
-
\`\`\`
|
|
112
|
-
|
|
113
|
-
## Hotkeys
|
|
114
|
-
|
|
115
|
-
| Key | Action |
|
|
116
|
-
|-----|--------|
|
|
117
|
-
| Space / → | Next slide |
|
|
118
|
-
| ← | Previous slide |
|
|
119
|
-
| 0-9 | Jump to slide |
|
|
120
|
-
| l | Show slide list |
|
|
121
|
-
| q | Quit |
|
|
122
|
-
`;
|
|
123
|
-
|
|
124
|
-
await Bun.write(join(deckDir, 'README.md'), readme);
|
|
125
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Present Command
|
|
3
|
-
*
|
|
4
|
-
* Starts a presentation with the given options.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { Command } from 'commander';
|
|
8
|
-
import { present } from '../../presenter/main.js';
|
|
9
|
-
import { handleError } from '../errors.js';
|
|
10
|
-
|
|
11
|
-
export const presentCommand = new Command('present')
|
|
12
|
-
.description('Start a presentation')
|
|
13
|
-
.argument('<dir>', 'Slides directory')
|
|
14
|
-
.option('-s, --start <n>', 'Start at slide number', '0')
|
|
15
|
-
.option('-n, --notes', 'Show presenter notes in separate terminal')
|
|
16
|
-
.option('--notes-tty <path>', 'TTY device for notes window (e.g., /dev/ttys001)')
|
|
17
|
-
.option('-l, --loop', 'Loop back to first slide after last')
|
|
18
|
-
.action(async (dir, options) => {
|
|
19
|
-
try {
|
|
20
|
-
await present(dir, {
|
|
21
|
-
startSlide: Number.parseInt(options.start, 10),
|
|
22
|
-
showNotes: options.notes,
|
|
23
|
-
notesTty: options.notesTty,
|
|
24
|
-
loop: options.loop,
|
|
25
|
-
});
|
|
26
|
-
} catch (error) {
|
|
27
|
-
handleError(error);
|
|
28
|
-
}
|
|
29
|
-
});
|
package/src/cli/errors.ts
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CLI Error Handling
|
|
3
|
-
*
|
|
4
|
-
* Provides user-friendly error messages for various error types
|
|
5
|
-
* that can occur during CLI operations.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { ValidationError } from '../schemas/validation.js';
|
|
9
|
-
import { SlideParseError, DeckLoadError } from '../core/slide.js';
|
|
10
|
-
import { ThemeError } from '../core/theme.js';
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Handle CLI errors with user-friendly messages
|
|
14
|
-
*
|
|
15
|
-
* Converts various error types into readable console output
|
|
16
|
-
* and exits with appropriate status code.
|
|
17
|
-
*/
|
|
18
|
-
export function handleError(error: unknown): never {
|
|
19
|
-
if (error instanceof ValidationError) {
|
|
20
|
-
console.error(`\n${error.message}`);
|
|
21
|
-
process.exit(1);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
if (error instanceof SlideParseError) {
|
|
25
|
-
console.error(`\nSlide error in ${error.filePath}:`);
|
|
26
|
-
console.error(` ${error.message}`);
|
|
27
|
-
if (error.cause) {
|
|
28
|
-
const causeMessage = error.cause instanceof Error ? error.cause.message : String(error.cause);
|
|
29
|
-
console.error(` Caused by: ${causeMessage}`);
|
|
30
|
-
}
|
|
31
|
-
process.exit(1);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
if (error instanceof DeckLoadError) {
|
|
35
|
-
console.error(`\nFailed to load deck from ${error.slidesDir}:`);
|
|
36
|
-
console.error(` ${error.message}`);
|
|
37
|
-
process.exit(1);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
if (error instanceof ThemeError) {
|
|
41
|
-
console.error('\nTheme error:');
|
|
42
|
-
console.error(` ${error.message}`);
|
|
43
|
-
process.exit(1);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (error instanceof Error) {
|
|
47
|
-
// Check for common issues
|
|
48
|
-
if (error.message.includes('ENOENT')) {
|
|
49
|
-
console.error('\nFile or directory not found.');
|
|
50
|
-
console.error(` ${error.message}`);
|
|
51
|
-
process.exit(1);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
if (error.message.includes('ffmpeg')) {
|
|
55
|
-
console.error('\nffmpeg error:');
|
|
56
|
-
console.error(` ${error.message}`);
|
|
57
|
-
console.error('\nMake sure ffmpeg is installed:');
|
|
58
|
-
console.error(' macOS: brew install ffmpeg');
|
|
59
|
-
console.error(' Ubuntu: sudo apt install ffmpeg');
|
|
60
|
-
process.exit(1);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Generic error
|
|
64
|
-
console.error(`\nError: ${error.message}`);
|
|
65
|
-
|
|
66
|
-
if (process.env.DEBUG) {
|
|
67
|
-
console.error(error.stack);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
process.exit(1);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Unknown error
|
|
74
|
-
console.error('\nUnknown error occurred');
|
|
75
|
-
console.error(error);
|
|
76
|
-
process.exit(1);
|
|
77
|
-
}
|