create-marp-presentation 1.1.0 → 1.2.1

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 (36) hide show
  1. package/README.md +45 -37
  2. package/cli/commands/add-themes-cli.js +85 -0
  3. package/cli/commands/create-project.js +199 -0
  4. package/cli/utils/file-utils.js +106 -0
  5. package/cli/utils/prompt-utils.js +89 -0
  6. package/docs/plans/2025-02-19-marp-template-design.md +207 -0
  7. package/docs/plans/2025-02-19-marp-template-implementation.md +848 -0
  8. package/docs/plans/2026-02-20-example-slides-design.md +179 -0
  9. package/docs/plans/2026-02-20-example-slides-implementation.md +811 -0
  10. package/docs/plans/2026-02-23-theme-management-design.md +836 -0
  11. package/docs/plans/2026-02-23-theme-management-implementation.md +3585 -0
  12. package/docs/plans/2026-02-26-theme-addition-refactoring-design.md +172 -0
  13. package/docs/plans/2026-02-26-theme-addition-refactoring.md +456 -0
  14. package/docs/plans/2026-02-27-theme-add-fix-design.md +136 -0
  15. package/docs/plans/2026-02-27-theme-add-fix.md +353 -0
  16. package/docs/plans/TODO.md +5 -0
  17. package/docs/reqs/themes-requirements.md +49 -0
  18. package/docs/theme-management.md +261 -0
  19. package/index.js +111 -164
  20. package/lib/add-themes-command.js +381 -0
  21. package/lib/errors.js +62 -0
  22. package/lib/frontmatter.js +71 -0
  23. package/lib/prompts.js +222 -0
  24. package/lib/theme-manager.js +238 -0
  25. package/lib/theme-resolver.js +227 -0
  26. package/lib/vscode-integration.js +198 -0
  27. package/package.json +15 -5
  28. package/template/README.md +28 -17
  29. package/template/package.json +13 -1
  30. package/template/scripts/theme-cli.js +248 -0
  31. package/themes/beam/beam.css +141 -0
  32. package/themes/default-clean/default-clean.css +57 -0
  33. package/themes/gaia-dark/gaia-dark.css +27 -0
  34. package/themes/marpx/marpx.css +1735 -0
  35. package/themes/marpx/socrates.css +105 -0
  36. package/themes/uncover-minimal/uncover-minimal.css +22 -0
@@ -0,0 +1,836 @@
1
+ # Theme Management System Design
2
+
3
+ **Date:** 2026-02-23
4
+ **Author:** Design session with user
5
+ **Status:** Approved
6
+
7
+ ## Overview
8
+
9
+ The theme management system enables users to create, manage, and switch between Marp themes in their presentation projects. It provides CLI commands for theme operations and integrates with VSCode for live preview.
10
+
11
+ ## Requirements
12
+
13
+ See [docs/reqs/themes-requirements.md](../reqs/themes-requirements.md) for detailed requirements.
14
+
15
+ ### Key Requirements Summary
16
+
17
+ - **System themes:** `default`, `gaia`, `uncover` (built into Marp)
18
+ - **User themes:** CSS files that can import other themes via `@import`
19
+ - **Theme identification:** via `/* @theme name */` directive in CSS
20
+ - **Interactive CLI:** using `@inquirer/prompts` (Inquirer.js v3+)
21
+ - **VSCode integration:** dynamic updates to `.vscode/settings.json`
22
+ - **Testing:** full coverage with Jest
23
+
24
+ ## Architecture
25
+
26
+ ### Monolithic Approach
27
+
28
+ Selected approach: **Monolithic ThemeManager** with focused helper classes.
29
+
30
+ ### File Structure
31
+
32
+ ```
33
+ create-marp-presentation/ # Мета-пакет (npm published)
34
+ ├── index.js # CLI entry point
35
+ ├── lib/ # Библиотечный код
36
+ │ ├── add-themes-command.js # addThemesCommand функция
37
+ │ ├── theme-manager.js # Main ThemeManager class
38
+ │ ├── theme-resolver.js # Parse CSS for @theme and @import
39
+ │ ├── prompts.js # Wrappers around @inquirer/prompts
40
+ │ ├── vscode-integration.js # Update .vscode/settings.json
41
+ │ ├── frontmatter.js # Parse/edit markdown frontmatter
42
+ │ └── errors.js # Custom error classes
43
+
44
+ ├── template/ # Копируется в создаваемый проект
45
+ │ ├── themes/ # Flat structure: themes/{name}/
46
+ │ │ ├── beam/
47
+ │ │ │ └── beam.css
48
+ │ │ ├── marpx/
49
+ │ │ │ ├── marpx.css
50
+ │ │ │ └── socrates.css
51
+ │ │ ├── gaia-dark/
52
+ │ │ │ └── dark.css
53
+ │ │ ├── uncover-minimal/
54
+ │ │ │ └── minimal.css
55
+ │ │ └── default-clean/
56
+ │ │ └── clean.css
57
+ │ │
58
+ │ └── scripts/ # Копируется в project/scripts/
59
+ │ ├── theme-cli.js # CLI entry point for npm scripts
60
+ │ └── lib/ # Копия lib/ для theme-cli.js
61
+ │ ├── theme-manager.js
62
+ │ ├── theme-resolver.js
63
+ │ ├── prompts.js
64
+ │ ├── vscode-integration.js
65
+ │ ├── frontmatter.js
66
+ │ └── errors.js
67
+
68
+ └── tests/
69
+ ├── unit/
70
+ │ ├── theme-resolver.test.js
71
+ │ ├── vscode-integration.test.js
72
+ │ ├── theme-manager.test.js
73
+ │ └── add-themes-command.test.js
74
+ ├── integration/
75
+ │ └── theme-cli.test.js
76
+ └── fixtures/
77
+ ├── themes/
78
+ └── presentations/
79
+ ```
80
+
81
+ ### Key Architectural Decisions
82
+
83
+ 1. **lib/ копируется в project/scripts/lib/** — theme-cli.js в созданном проекте использует локальную копию модулей
84
+
85
+ 2. **addThemesCommand в мета-пакете** — функция находится в create-marp-presentation/lib/add-themes-command.js, используется:
86
+ - index.js при создании проекта
87
+ - CLI команда `add-themes` для theme:add-from-template
88
+
89
+ 3. **npx для theme:add-from-template** — вызывается через `npx create-marp-presentation add-themes`, код выполняется из установленного пакета
90
+
91
+ ## Components
92
+
93
+ ### ThemeResolver
94
+
95
+ Parses CSS files to extract theme metadata.
96
+
97
+ ```javascript
98
+ class ThemeResolver {
99
+ /**
100
+ * Extract theme name from CSS: /* @theme beam *\/
101
+ * Falls back to filename if directive not found
102
+ */
103
+ static extractThemeName(cssContent) → string
104
+
105
+ /**
106
+ * Extract @import dependencies: @import "gaia"
107
+ * Ignores url() imports
108
+ */
109
+ static extractDependencies(cssContent) → string[]
110
+
111
+ /**
112
+ * Full theme resolution from single CSS file
113
+ */
114
+ static resolveTheme(cssPath) → Theme
115
+
116
+ /**
117
+ * Scan directory recursively for all themes
118
+ * Returns flat array of Theme objects
119
+ * Used by: index.js (project creation) and theme:list
120
+ */
121
+ static scanDirectory(themesPath) → Theme[]
122
+
123
+ /**
124
+ * Resolve all dependencies for selected themes
125
+ * Returns complete set of themes to copy (including parents)
126
+ * Excludes system themes (default, gaia, uncover)
127
+ *
128
+ * @param {Theme[]} selectedThemes - themes user selected
129
+ * @param {Theme[]} allThemes - all available themes in template
130
+ * @returns {Theme[]} - themes to copy (selected + dependencies)
131
+ *
132
+ * Example:
133
+ * selected: [socrates]
134
+ * socrates.dependencies: [marpx]
135
+ * marpx.dependencies: [default] ← system, not copied
136
+ * returns: [socrates, marpx]
137
+ */
138
+ static resolveDependencies(selectedThemes, allThemes) → Theme[]
139
+ }
140
+ ```
141
+
142
+ ### Theme
143
+
144
+ ```javascript
145
+ class Theme {
146
+ constructor(name, path, css, dependencies = [])
147
+
148
+ name: string // From /* @theme */ or filename
149
+ path: string // Full path to CSS file
150
+ css: string // CSS content
151
+ dependencies: string[] // Parent themes
152
+ isSystem: boolean // false for user themes
153
+ }
154
+ ```
155
+
156
+ ### VSCodeIntegration
157
+
158
+ Manages VSCode settings for Marp extension.
159
+
160
+ ```javascript
161
+ class VSCodeIntegration {
162
+ constructor(projectPath)
163
+
164
+ /**
165
+ * Sync themes to markdown.marp.themes in settings.json
166
+ * Creates settings.json if not exists
167
+ * Backs up corrupted JSON and recreates
168
+ */
169
+ syncThemes(themes: Theme[]) → void
170
+
171
+ readSettings() → object
172
+ writeSettings(settings: object) → void
173
+ }
174
+ ```
175
+
176
+ **VSCode settings format:**
177
+ ```json
178
+ {
179
+ "markdown.marp.themes": [
180
+ "themes/beam/beam.css",
181
+ "themes/marpx/marpx.css"
182
+ ]
183
+ }
184
+ ```
185
+
186
+ ### ThemeManager
187
+
188
+ Main class for theme operations. **Delegates scanning to ThemeResolver.**
189
+
190
+ ```javascript
191
+ class ThemeManager {
192
+ constructor(projectPath)
193
+
194
+ // Properties
195
+ get themesPath() → string // path/to/project/themes
196
+
197
+ // Reading
198
+ /**
199
+ * Scan themes in project
200
+ * IMPLEMENTATION: delegates to ThemeResolver.scanDirectory(this.themesPath)
201
+ */
202
+ scanThemes() → Theme[]
203
+ getTheme(name) → Theme | null
204
+ getActiveTheme() → string | null
205
+
206
+ // Modification
207
+ createTheme(name, parent, directory) → void
208
+ setActiveTheme(themeName) → void
209
+
210
+ // Integration
211
+ updateVSCodeSettings() → void
212
+ }
213
+ ```
214
+
215
+ **Single Source of Truth:** `ThemeResolver.scanDirectory()` — единственная реализация сканирования тем.
216
+ - `ThemeManager.scanThemes()` → вызывает `ThemeResolver.scanDirectory(this.themesPath)`
217
+ - `index.js` (project creation) → вызывает `ThemeResolver.scanDirectory(template/themes/)`
218
+ - Это обеспечивает консистентность логики сканирования везде.
219
+ ```
220
+
221
+ ### Prompts
222
+
223
+ Interactive prompts using `@inquirer/prompts`.
224
+
225
+ ```javascript
226
+ const inquirer = require('@inquirer/prompts');
227
+
228
+ async function promptThemes(availableThemes) → string[]
229
+ async function promptActiveTheme(selectedThemes) → string
230
+ async function promptNewThemeName() → string
231
+ async function promptParentTheme(availableThemes) → string | null
232
+ async function promptDirectoryLocation(existingDirs) → string
233
+ async function promptNewFolderName() → string
234
+ ```
235
+
236
+ ## CLI Commands
237
+
238
+ ### create-marp-presentation Commands
239
+
240
+ #### `npx create-marp-presentation add-themes <target-dir>`
241
+
242
+ **Общая команда для добавления тем из шаблона.** Используется:
243
+ - При создании проекта (Flow 1)
244
+ - В theme:add-from-template (вызывается через npx)
245
+
246
+ ```
247
+ # Интерактивный режим
248
+ $ npx create-marp-presentation add-themes ./my-project
249
+
250
+ ? Select themes to add:
251
+ ☁ beam
252
+ ☁ socrates
253
+ ☐ gaia-dark
254
+
255
+ ✓ Added 2 themes with dependencies
256
+ - beam (themes/beam/)
257
+ - socrates (themes/marpx/socrates.css)
258
+ - marpx (dependency of socrates)
259
+ ✓ VSCode settings updated
260
+
261
+ # Неинтерактивный режим (CI/CD)
262
+ $ npx create-marp-presentation add-themes ./my-project --themes=beam,socrates
263
+
264
+ ✓ Added themes: beam, socrates (with dependencies)
265
+ ```
266
+
267
+ **Аргументы:**
268
+ - `<target-dir>` — путь к директории проекта (должен существовать)
269
+ - `--themes=name1,name2` — список тем (опционально, для CI/CD)
270
+
271
+ **Логика:**
272
+ 1. `ThemeResolver.scanDirectory(template/themes/)` → все доступные темы
273
+ 2. Если `--themes` не указан → `@inquirer/checkbox` для выбора
274
+ 3. `ThemeResolver.resolveDependencies()` → темы + родители
275
+ 4. Копирование в `<target-dir>/themes/`
276
+ 5. Обновление `<target-dir>/.vscode/settings.json`
277
+
278
+ ---
279
+
280
+ ### Project CLI Commands (npm scripts)
281
+
282
+ | Command | Description |
283
+ |---------|-------------|
284
+ | `theme:list` | List all themes in project |
285
+ | `theme:add` | Create new theme with parent selection |
286
+ | `theme:add-from-template` | Add themes from create-marp-presentation template |
287
+ | `theme:switch -- <name>` | Switch active theme in presentation.md |
288
+
289
+ ### npm scripts
290
+
291
+ ```json
292
+ {
293
+ "scripts": {
294
+ "theme:list": "node scripts/theme-cli.js list",
295
+ "theme:add": "node scripts/theme-cli.js add",
296
+ "theme:add-from-template": "node scripts/theme-cli.js add-from-template",
297
+ "theme:switch": "node scripts/theme-cli.js switch"
298
+ }
299
+ }
300
+ ```
301
+
302
+ ### Command Details
303
+
304
+ #### theme:list
305
+
306
+ Shows all themes in project with dependencies.
307
+
308
+ ```
309
+ $ npm run theme:list
310
+
311
+ Themes in project:
312
+ ✓ beam (themes/beam/beam.css) extends: default
313
+ ✓ marpx (themes/marpx/marpx.css) extends: default
314
+ ✓ socrates (themes/marpx/socrates.css) extends: marpx
315
+
316
+ Active theme: beam
317
+ ```
318
+
319
+ #### theme:add
320
+
321
+ Creates new theme with parent and directory selection.
322
+
323
+ ```
324
+ $ npm run theme:add
325
+
326
+ ? Theme name: my-dark
327
+ ? Parent theme:
328
+ ❯ none (create from scratch)
329
+ default (system built-in)
330
+ gaia (system built-in)
331
+ marpx (custom)
332
+
333
+ ? Where to create the theme CSS file?
334
+ ❯ In root (themes/my-dark.css)
335
+ In existing folder: themes/marpx/
336
+ In new folder (enter name)
337
+
338
+ ? Folder name: gaia-variants
339
+
340
+ ✓ Created themes/gaia-variants/my-dark.css
341
+ ✓ Theme registered in VSCode settings
342
+ ```
343
+
344
+ #### theme:add-from-template
345
+
346
+ **Делегирует к `npx create-marp-presentation add-themes`**
347
+
348
+ ```javascript
349
+ // template/scripts/theme-cli.js
350
+ case 'add-from-template':
351
+ // Просто вызываем общую команду через npx
352
+ const { execSync } = require('child_process');
353
+ execSync(`npx create-marp-presentation add-themes ${process.cwd()}`, {
354
+ stdio: 'inherit'
355
+ });
356
+ break;
357
+ ```
358
+
359
+ ```
360
+ $ npm run theme:add-from-template
361
+ # → npx create-marp-presentation add-themes $(pwd)
362
+
363
+ ? Select themes to add:
364
+ ☁ beam
365
+ ☁ marpx
366
+ ☐ socrates
367
+
368
+ ✓ Added 2 themes with dependencies
369
+ ```
370
+
371
+ **Преимущества:**
372
+ - Код добавления тем в одном месте (create-marp-presentation)
373
+ - Обновления автоматически доступны во всех проектах
374
+ - Консистентное поведение при создании проекта и добавлении тем позже
375
+
376
+ #### theme:switch
377
+
378
+ Switch active theme in presentation.md.
379
+
380
+ ```
381
+ $ npm run theme:switch beam
382
+
383
+ ✓ Active theme changed to 'beam'
384
+ Updated presentation.md frontmatter
385
+ ```
386
+
387
+ Interactive mode (no argument):
388
+ ```
389
+ $ npm run theme:switch
390
+
391
+ ? Select active theme:
392
+ ❯ beam
393
+ marpx
394
+ socrates
395
+
396
+ ✓ Active theme changed to 'beam'
397
+ ```
398
+
399
+ **IMPORTANT:** Only edits `theme` attribute in frontmatter, preserves all other content.
400
+
401
+ ## Data Flows
402
+
403
+ ### Flow 1: Project Creation with Theme Selection
404
+
405
+ ```
406
+ npx create-marp-presentation my-project
407
+
408
+ [index.js]
409
+
410
+ 1. Создать директорию проекта
411
+ 2. Скопировать template/* → my-project/
412
+ (кроме template/themes/)
413
+
414
+ 3. Вызвать addThemesCommand(projectPath)
415
+ ┌─────────────────────────────────────────────────────────┐
416
+ │ ThemeResolver.scanDirectory(template/themes/) │
417
+ │ ├── Найти все *.css файлы рекурсивно │
418
+ │ ├── extractThemeName() + extractDependencies() │
419
+ │ └── Вернуть плоский список Theme[] │
420
+ │ │
421
+ │ @inquirer/checkbox: Select themes to add │
422
+ │ Пример: │
423
+ │ ☐ beam │
424
+ │ ☐ marpx │
425
+ │ ☁ socrates ← выбрал пользователь │
426
+ │ ☐ gaia-dark │
427
+ │ │
428
+ │ ThemeResolver.resolveDependencies(selected, all) │
429
+ │ └── socrates → marpx → [socrates, marpx] │
430
+ │ │
431
+ │ Copy themes to project/themes/ │
432
+ │ ├── marpx/* → project/themes/marpx/ │
433
+ │ └── (default НЕ копируется - системная) │
434
+ │ │
435
+ │ VSCodeIntegration.syncThemes() │
436
+ │ └── Обновить .vscode/settings.json │
437
+ └─────────────────────────────────────────────────────────┘
438
+
439
+ 4. @inquirer/select: Select active theme
440
+ Choices: только скопированные темы
441
+
442
+ 5. ThemeManager.setActiveTheme(themeName)
443
+ └── Обновить presentation.md frontmatter
444
+
445
+ 6. npm install
446
+
447
+ Done
448
+ ```
449
+
450
+ **Важно:**
451
+ - Шаг 3 — это та же логика, что в `add-themes` команде
452
+ - `addThemesCommand()` экспортируется и используется:
453
+ - index.js (создание проекта)
454
+ - CLI команда `add-themes` (для theme:add-from-template)
455
+ - **Single Source of Truth** для логики добавления тем
456
+
457
+ ### Общая функция: addThemesCommand
458
+
459
+ Используется в Flow 1 (создание проекта) и Flow 3 (theme:add-from-template).
460
+
461
+ ```
462
+ addThemesCommand(targetPath, options = {})
463
+
464
+ ThemeResolver.scanDirectory(template/themes/) → Theme[]
465
+
466
+ [Если options.themes не задан]
467
+ @inquirer/checkbox: Select themes → selectedThemes[]
468
+ [Иначе]
469
+ selectedThemes = options.themes
470
+
471
+ ThemeResolver.resolveDependencies(selectedThemes, allThemes) → Theme[]
472
+
473
+ [Проверка конфликтов]
474
+ existingThemes = ThemeResolver.scanDirectory(targetPath/themes/)
475
+ conflicts = findConflicts(themesToCopy, existingThemes)
476
+
477
+ [Если есть конфликты]
478
+ @inquirer/select: "Theme X already exists. Action?"
479
+ - Skip (пропустить)
480
+ - Overwrite (перезаписать)
481
+ - Cancel (отменить всё)
482
+
483
+ Copy to targetPath/themes/
484
+ ├── Исключить системные темы (default, gaia, uncover)
485
+ ├── Исключить skipped темы
486
+ └── Сохранить структуру папок
487
+
488
+ VSCodeIntegration.syncThemes(themesToCopy)
489
+
490
+ Return: { added: Theme[], skipped: Theme[], dependencies: Theme[] }
491
+ ```
492
+
493
+ ### Conflict Handling
494
+
495
+ When a theme already exists in the project:
496
+
497
+ ```
498
+ ? Theme "beam" already exists at themes/beam/beam.css
499
+ What would you like to do?
500
+
501
+ ❯ Skip (keep existing)
502
+ Overwrite (replace with template version)
503
+ Cancel (stop adding themes)
504
+ ```
505
+
506
+ **Options:**
507
+ - **Skip:** Keep existing theme, don't copy from template
508
+ - **Overwrite:** Replace existing theme with template version
509
+ - **Cancel:** Stop the entire operation
510
+
511
+ **For multiple conflicts:**
512
+ ```
513
+ ? 3 themes already exist in your project:
514
+ - beam (themes/beam/)
515
+ - marpx (themes/marpx/)
516
+ - socrates (themes/marpx/socrates.css)
517
+
518
+ ? Apply to all conflicts?
519
+ ❯ Skip all
520
+ Overwrite all
521
+ Choose for each
522
+ Cancel
523
+ ```
524
+
525
+ ### Flow 2: theme:list
526
+
527
+ ```
528
+ npm run theme:list
529
+
530
+ ThemeManager.scanThemes()
531
+
532
+ Parse all CSS files in themes/
533
+
534
+ Display theme list with dependencies
535
+
536
+ Show active theme from presentation.md
537
+ ```
538
+
539
+ ### Flow 3: theme:add-from-template
540
+
541
+ ```
542
+ npm run theme:add-from-template
543
+
544
+ theme-cli.js
545
+
546
+ execSync('npx create-marp-presentation add-themes $(pwd)')
547
+
548
+ [addThemesCommand выполняется]
549
+
550
+ Done
551
+ ```
552
+
553
+ **Реализация в theme-cli.js:**
554
+ ```javascript
555
+ case 'add-from-template':
556
+ const { execSync } = require('child_process');
557
+ const projectPath = process.cwd();
558
+ execSync(`npx create-marp-presentation add-themes "${projectPath}"`, {
559
+ stdio: 'inherit'
560
+ });
561
+ break;
562
+ ```
563
+
564
+ ### Flow 4: theme:add
565
+
566
+ ```
567
+ npm run theme:add
568
+
569
+ promptNewThemeName()
570
+
571
+ ThemeManager.scanThemes() → existing themes
572
+
573
+ promptParentTheme(existing themes)
574
+
575
+ promptDirectoryLocation()
576
+
577
+ [If new folder] promptNewFolderName()
578
+
579
+ Create directory if needed
580
+
581
+ Generate CSS template with @import if parent selected
582
+
583
+ VSCodeIntegration.syncThemes()
584
+
585
+ Done
586
+ ```
587
+
588
+ ### Flow 5: theme:switch
589
+
590
+ ```
591
+ npm run theme:switch [theme-name]
592
+
593
+ ThemeManager.scanThemes() → available themes
594
+
595
+ [If no arg] @inquirer/select: Choose theme
596
+
597
+ Validate theme exists
598
+
599
+ ThemeManager.setThemeInFrontmatter(themeName)
600
+
601
+ Parse presentation.md frontmatter
602
+
603
+ Find/replace only: theme: old → theme: new
604
+
605
+ Write updated presentation.md
606
+
607
+ Done
608
+ ```
609
+
610
+ ## Error Handling
611
+
612
+ ### Error Classes
613
+
614
+ ```javascript
615
+ class ThemeError extends Error
616
+
617
+ class ThemeNotFoundError extends ThemeError
618
+ class ThemeAlreadyExistsError extends ThemeError
619
+ class PresentationNotFoundError extends ThemeError
620
+ class InvalidCSSError extends ThemeError
621
+ class VSCodeIntegrationError extends ThemeError
622
+ ```
623
+
624
+ ### Error Handling Strategy
625
+
626
+ | Component | Error Handling |
627
+ |-----------|----------------|
628
+ | ThemeResolver | Fallback to filename if no /* @theme */ |
629
+ | VSCodeIntegration | Create if missing, backup + recreate if corrupted |
630
+ | ThemeManager | Validate before operations, clear error messages |
631
+ | CLI | User-friendly messages with actionable suggestions |
632
+
633
+ ## Testing
634
+
635
+ ### Test Structure
636
+
637
+ ```
638
+ tests/
639
+ ├── unit/
640
+ │ ├── theme-resolver.test.js
641
+ │ ├── vscode-integration.test.js
642
+ │ └── theme-manager.test.js
643
+ ├── integration/
644
+ │ └── theme-cli.test.js
645
+ └── fixtures/
646
+ ├── themes/
647
+ └── presentations/
648
+ ```
649
+
650
+ ### Coverage Targets
651
+
652
+ | Component | Target |
653
+ |-----------|--------|
654
+ | ThemeResolver | 90%+ |
655
+ | VSCodeIntegration | 85%+ |
656
+ | ThemeManager | 90%+ |
657
+ | theme-cli | 80%+ |
658
+ | **Overall** | **85%+** |
659
+
660
+ ### Key Test Cases
661
+
662
+ 1. **ThemeResolver**
663
+ - Extract theme name from CSS comment
664
+ - Fallback to filename
665
+ - Extract @import dependencies
666
+ - Ignore url() imports
667
+
668
+ 2. **VSCodeIntegration**
669
+ - Create settings.json if not exists
670
+ - Merge with existing settings
671
+ - Backup corrupted JSON
672
+
673
+ 3. **ThemeManager**
674
+ - Scan all themes in directory
675
+ - Set active theme in frontmatter
676
+ - Create frontmatter if not exists
677
+ - Preserve content when updating theme
678
+
679
+ 4. **CLI**
680
+ - All commands execute correctly
681
+ - Interactive mode works
682
+ - Proper error messages
683
+
684
+ 5. **addThemesCommand**
685
+ - Select themes from template
686
+ - Resolve dependencies
687
+ - Handle conflicts (skip/overwrite/cancel)
688
+ - Copy themes to project
689
+ - Update VSCode settings
690
+
691
+ 6. **Conflict Resolution**
692
+ - Detect existing themes
693
+ - Prompt for action
694
+ - Skip selected themes
695
+ - Overwrite selected themes
696
+ - Cancel operation
697
+
698
+ ## Implementation Notes
699
+
700
+ ### Dependencies
701
+
702
+ **Root package.json (create-marp-presentation):**
703
+ ```json
704
+ {
705
+ "dependencies": {
706
+ "@inquirer/prompts": "^7.0.0"
707
+ },
708
+ "devDependencies": {
709
+ "jest": "^29.7.0"
710
+ }
711
+ }
712
+ ```
713
+
714
+ **template/package.json (generated project):**
715
+ ```json
716
+ {
717
+ "dependencies": {
718
+ "@inquirer/prompts": "^7.0.0",
719
+ "gray-matter": "^4.0.3"
720
+ },
721
+ "devDependencies": {
722
+ "@marp-team/marp-cli": "^4.1.2"
723
+ }
724
+ }
725
+ ```
726
+
727
+ ### File Copying Mechanism
728
+
729
+ Use Node.js built-in `fs.cpSync()` for recursive directory copying:
730
+
731
+ ```javascript
732
+ import fs from 'fs';
733
+ import path from 'path';
734
+
735
+ function copyThemeToProject(theme, projectPath) {
736
+ const sourceDir = path.dirname(theme.path);
737
+ const themeDirName = path.basename(sourceDir);
738
+ const targetDir = path.join(projectPath, 'themes', themeDirName);
739
+
740
+ // Copy entire theme directory
741
+ fs.cpSync(sourceDir, targetDir, { recursive: true });
742
+ }
743
+ ```
744
+
745
+ **For lib/ copying during project creation:**
746
+ ```javascript
747
+ function copyLibToProject(projectPath) {
748
+ const libSource = path.join(__dirname, 'lib');
749
+ const libTarget = path.join(projectPath, 'scripts', 'lib');
750
+ fs.cpSync(libSource, libTarget, { recursive: true });
751
+ }
752
+ ```
753
+
754
+ ### Finding Template Themes Path
755
+
756
+ ```javascript
757
+ // In addThemesCommand
758
+ function getTemplateThemesPath() {
759
+ // When running from index.js
760
+ return path.join(__dirname, '..', 'template', 'themes');
761
+
762
+ // When installed as npm package and run via npx
763
+ // __dirname points to lib/ directory in installed package
764
+ }
765
+ ```
766
+
767
+ ### Theme Template CSS
768
+
769
+ **No parent (from scratch):**
770
+ ```css
771
+ /* @theme my-theme */
772
+
773
+ :root {
774
+ /* Your theme variables */
775
+ }
776
+ ```
777
+
778
+ **With parent:**
779
+ ```css
780
+ /* @theme my-theme */
781
+
782
+ @import "default";
783
+
784
+ :root {
785
+ /* Your overrides */
786
+ }
787
+ ```
788
+
789
+ **Custom parent:**
790
+ ```css
791
+ /* @theme my-theme */
792
+
793
+ @import "../marpx/marpx.css";
794
+
795
+ :root {
796
+ /* Your overrides */
797
+ }
798
+ ```
799
+
800
+ ### Theme Sources
801
+
802
+ Initial themes from `docs/reqs/theme-templates/`:
803
+ - `beam/beam.css` - Beamer-inspired theme
804
+ - `marpx/marpx.css` - Academic theme base
805
+ - `marpx/socrates.css` - Socrates theme extending marpx
806
+
807
+ Plus one theme per system theme:
808
+ - `gaia-dark/dark.css` - Dark variant of gaia
809
+ - `uncover-minimal/minimal.css` - Minimal variant of uncover
810
+ - `default-clean/clean.css` - Clean variant of default
811
+
812
+ ## Manual Operations
813
+
814
+ Users can manually:
815
+
816
+ **Add CSS file to theme:**
817
+ ```bash
818
+ cp ~/my-styles.css themes/marpx/
819
+ # Then manually add @import to appropriate CSS file
820
+ ```
821
+
822
+ **Remove theme:**
823
+ ```bash
824
+ rm -rf themes/beam
825
+ ```
826
+
827
+ ## Next Steps
828
+
829
+ 1. Create implementation plan via `writing-plans` skill
830
+ 2. Implement ThemeResolver
831
+ 3. Implement VSCodeIntegration
832
+ 4. Implement ThemeManager
833
+ 5. Implement prompts
834
+ 6. Implement theme-cli
835
+ 7. Add tests
836
+ 8. Update main index.js for project creation flow