create-marp-presentation 1.2.0 → 1.2.2

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.
@@ -0,0 +1,172 @@
1
+ # Theme Addition Refactoring Design
2
+
3
+ **Date:** 2026-02-26
4
+ **Status:** Approved
5
+ **Author:** Claude Code
6
+
7
+ ## Problem
8
+
9
+ The theme addition code is duplicated between `create-project.js` (~80 lines) and `add-themes-cli.js` (~120 lines):
10
+ - Identical conflict resolution logic (~30 lines)
11
+ - Similar theme selection patterns
12
+ - VSCode settings handling in both files
13
+ - Active theme selection duplicated
14
+
15
+ This creates maintenance burden and potential for inconsistencies.
16
+
17
+ ## Solution
18
+
19
+ Centralize all theme addition logic in `AddThemesCommand` class, making CLI files simple wrappers.
20
+
21
+ ## Architecture
22
+
23
+ ### Responsibilities
24
+
25
+ | Component | Responsibilities |
26
+ |-----------|------------------|
27
+ | **AddThemesCommand** | Theme selection prompt, conflict detection/resolution, theme copying, VSCode sync |
28
+ | **create-project.js** | Call AddThemesCommand, select active theme from copied themes |
29
+ | **add-themes-cli.js** | Call AddThemesCommand, show summary (no active theme logic) |
30
+
31
+ ### Why This Split
32
+
33
+ - **New project**: User expects to choose which theme to use → create-project.js handles active theme
34
+ - **Existing project**: Adding themes shouldn't change active theme → add-themes-cli.js doesn't touch it
35
+
36
+ ## AddThemesCommand API
37
+
38
+ ### Constructor
39
+
40
+ ```javascript
41
+ new AddThemesCommand({
42
+ templatePath, // Path to themes library
43
+ interactive: true // Always true for theme selection
44
+ })
45
+ ```
46
+
47
+ ### Execute Method
48
+
49
+ ```javascript
50
+ async execute(projectPath, options = {}) {
51
+ // Options:
52
+ // - themes: string[] (optional - skip selection prompt if provided)
53
+ // - skipConflictCheck: boolean (default: false)
54
+
55
+ // Returns:
56
+ return {
57
+ copied: [Theme, ...], // Theme objects that were copied
58
+ skipped: ['theme1'], // Theme names that were skipped
59
+ conflicts: ['theme2'] // Theme names with conflicts
60
+ }
61
+ }
62
+ ```
63
+
64
+ ### Internal Flow
65
+
66
+ 1. Scan available themes from `templatePath`
67
+ 2. If `themes` not provided: prompt user to select themes (using `Prompts.promptThemes()`)
68
+ 3. Resolve dependencies via `ThemeResolver.resolveDependencies()`
69
+ 4. Check for file conflicts (always runs, finds nothing in new projects)
70
+ 5. If conflicts found: prompt for resolution (using `Prompts.promptConflictResolution()`)
71
+ 6. Copy themes to project
72
+ 7. Update VSCode settings.json with theme paths
73
+ 8. Return result
74
+
75
+ ## CLI File Usage
76
+
77
+ ### create-project.js
78
+
79
+ ```javascript
80
+ const command = new AddThemesCommand({
81
+ templatePath: themesLibraryPath
82
+ });
83
+
84
+ const { copied } = await command.execute(projectPath);
85
+
86
+ // Select active theme from copied themes
87
+ const themeNames = copied.map(t => t.name);
88
+ const activeTheme = await Prompts.promptActiveTheme(themeNames);
89
+ await ThemeManager.setActiveTheme(projectPath, activeTheme);
90
+ ```
91
+
92
+ ### add-themes-cli.js
93
+
94
+ ```javascript
95
+ const command = new AddThemesCommand({
96
+ templatePath: themesLibraryPath
97
+ });
98
+
99
+ const { copied, skipped, conflicts } = await command.execute(projectPath);
100
+
101
+ // Show summary
102
+ console.log(`Copied: ${copied.map(t => t.name).join(', ')}`);
103
+ if (skipped.length) console.log(`Skipped: ${skipped.join(', ')}`);
104
+ if (conflicts.length) console.log(`Conflicts: ${conflicts.join(', ')}`);
105
+ ```
106
+
107
+ ## Files to Modify
108
+
109
+ 1. **lib/add-themes-command.js** - Add built-in prompts, return Theme objects
110
+ 2. **cli/commands/create-project.js** - Remove duplicated code, simplify to ~10 lines
111
+ 3. **cli/commands/add-themes-cli.js** - Remove duplicated code and active theme logic
112
+
113
+ ## Key Changes
114
+
115
+ ### AddThemesCommand
116
+
117
+ - ✅ Built-in theme selection (uses `Prompts.promptThemes()`)
118
+ - ✅ Built-in conflict resolution (uses `Prompts.promptConflictResolution()`)
119
+ - ✅ Always runs conflict checking (harmless in new projects)
120
+ - ✅ Returns Theme objects (not names)
121
+ - ✅ Handles VSCode settings internally
122
+
123
+ ### create-project.js
124
+
125
+ - ❌ Remove `askAddThemes()` prompt
126
+ - ❌ Remove `addThemesToProject()` function
127
+ - ❌ Remove VSCode settings update
128
+ - ✅ Keep active theme selection
129
+
130
+ ### add-themes-cli.js
131
+
132
+ - ❌ Remove theme selection prompt logic
133
+ - ❌ Remove active theme prompt and setting
134
+ - ❌ Remove VSCode settings update
135
+ - ✅ Keep summary output
136
+
137
+ ## Error Handling
138
+
139
+ | Scenario | Behavior |
140
+ |----------|----------|
141
+ | No themes available | Show message, return empty result |
142
+ | User cancels selection | Return empty result |
143
+ | User cancels conflict resolution | Stop, return what was copied so far |
144
+ | Copy errors | Collect and continue, report at end |
145
+
146
+ ## Testing
147
+
148
+ ### Unit Tests (AddThemesCommand)
149
+
150
+ - Theme selection prompt is called when themes not provided
151
+ - Conflict resolution prompt is called when conflicts exist
152
+ - Returns correct Theme objects
153
+ - VSCode settings updated correctly
154
+ - Empty result returned when user cancels
155
+
156
+ ### Integration Tests
157
+
158
+ - create-project: themes added, active theme set correctly
159
+ - add-themes: themes added, active theme NOT changed
160
+ - Both: conflict resolution works correctly
161
+
162
+ ## User Impact
163
+
164
+ **Zero breaking changes** - CLI behavior remains identical from user perspective.
165
+
166
+ ## Code Reduction
167
+
168
+ | File | Before | After |
169
+ |------|--------|-------|
170
+ | create-project.js | ~80 lines | ~10 lines |
171
+ | add-themes-cli.js | ~120 lines | ~10 lines |
172
+ | **Total duplication** | ~30 lines | 0 lines |
@@ -0,0 +1,456 @@
1
+ # Theme Addition Refactoring Implementation Plan
2
+
3
+ > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
4
+
5
+ **Goal:** Refactor theme addition code to eliminate duplication by centralizing logic in AddThemesCommand class.
6
+
7
+ **Architecture:** AddThemesCommand becomes single source of truth for theme selection, conflict resolution, and copying. CLI files become simple wrappers (~10 lines). create-project.js handles active theme selection; add-themes-cli.js does not.
8
+
9
+ **Tech Stack:** Node.js, inquirer for prompts, jest for testing
10
+
11
+ **Reference design:** `docs/plans/2026-02-26-theme-addition-refactoring-design.md`
12
+
13
+ ---
14
+
15
+ ## Task 1: Add theme selection prompt to AddThemesCommand
16
+
17
+ **Files:**
18
+ - Modify: `lib/add-themes-command.js`
19
+
20
+ **Step 1: Read current AddThemesCommand implementation**
21
+
22
+ Read the file to understand current structure, especially:
23
+ - How `selectThemes` prompt is currently handled
24
+ - The `execute()` method signature
25
+ - How themes are returned
26
+
27
+ **Step 2: Modify `_promptThemes` method to use Prompts class**
28
+
29
+ Update the method to use `Prompts.promptThemes()` directly:
30
+
31
+ ```javascript
32
+ _promptThemes(availableThemes) {
33
+ if (this.options.themes && this.options.themes.length > 0) {
34
+ return this.options.themes;
35
+ }
36
+ return Prompts.promptThemes(availableThemes);
37
+ }
38
+ ```
39
+
40
+ **Step 3: Update constructor to remove custom selectThemes prompt option**
41
+
42
+ Remove `prompts.selectThemes` from the constructor options. Theme selection is now built-in.
43
+
44
+ **Step 4: Add Prompts import**
45
+
46
+ Add at top of file:
47
+ ```javascript
48
+ const { Prompts } = require('./prompts');
49
+ ```
50
+
51
+ **Step 5: Run tests to verify no regressions**
52
+
53
+ Run: `npm test -- tests/add-themes-command.test.js`
54
+ Expected: All existing tests pass
55
+
56
+ **Step 6: Commit**
57
+
58
+ ```bash
59
+ git add lib/add-themes-command.js
60
+ git commit -m "refactor: AddThemesCommand uses built-in theme selection prompt"
61
+ ```
62
+
63
+ ---
64
+
65
+ ## Task 2: Add conflict resolution prompt to AddThemesCommand
66
+
67
+ **Files:**
68
+ - Modify: `lib/add-themes-command.js`
69
+
70
+ **Step 1: Create `_resolveConflictsInteractive` method**
71
+
72
+ Add method to handle conflict resolution using Prompts class:
73
+
74
+ ```javascript
75
+ async _resolveConflictsInteractive(conflicts) {
76
+ const result = await Prompts.promptConflictResolution(
77
+ conflicts.map(c => ({ name: c }))
78
+ );
79
+
80
+ if (result === 'skip-all') return { overwrite: [] };
81
+ if (result === 'overwrite-all') return { overwrite: conflicts };
82
+ if (result === 'cancel') return { overwrite: [] };
83
+
84
+ // Handle 'choose-each' or individual conflicts
85
+ const overwrite = [];
86
+ for (const conflict of conflicts) {
87
+ const choice = await Prompts.promptSingleConflict(conflict);
88
+ if (choice === 'overwrite') {
89
+ overwrite.push(conflict);
90
+ } else if (choice === 'cancel') {
91
+ break;
92
+ }
93
+ }
94
+ return { overwrite };
95
+ }
96
+ ```
97
+
98
+ **Step 2: Update `_resolveConflicts` to use built-in prompt**
99
+
100
+ Modify to call `_resolveConflictsInteractive` when no custom prompt provided:
101
+
102
+ ```javascript
103
+ async _resolveConflicts(conflicts) {
104
+ if (this.options.prompts?.resolveConflicts) {
105
+ return this.options.prompts.resolveConflicts(conflicts);
106
+ }
107
+ return this._resolveConflictsInteractive(conflicts);
108
+ }
109
+ ```
110
+
111
+ **Step 3: Update constructor default prompts**
112
+
113
+ Remove requirement for `resolveConflicts` in prompts object - make it optional.
114
+
115
+ **Step 4: Run tests**
116
+
117
+ Run: `npm test -- tests/add-themes-command.test.js`
118
+ Expected: All existing tests pass
119
+
120
+ **Step 5: Commit**
121
+
122
+ ```bash
123
+ git add lib/add-themes-command.js
124
+ git commit -m "refactor: AddThemesCommand uses built-in conflict resolution prompt"
125
+ ```
126
+
127
+ ---
128
+
129
+ ## Task 3: Ensure AddThemesCommand returns Theme objects
130
+
131
+ **Files:**
132
+ - Modify: `lib/add-themes-command.js`
133
+ - Test: `tests/add-themes-command.test.js`
134
+
135
+ **Step 1: Read execute() method return value**
136
+
137
+ Check what is currently returned - ensure `copied` array contains Theme objects with `name` property.
138
+
139
+ **Step 2: Verify Theme object structure**
140
+
141
+ Ensure copied themes have:
142
+ - `name` property
143
+ - Other necessary properties for downstream use
144
+
145
+ **Step 3: Add test for Theme object return**
146
+
147
+ ```javascript
148
+ test('execute returns Theme objects with name property', async () => {
149
+ const command = new AddThemesCommand({
150
+ templatePath: themesPath,
151
+ interactive: false
152
+ });
153
+
154
+ const result = await command.execute(projectPath, {
155
+ themes: ['beam']
156
+ });
157
+
158
+ expect(result.copied).toHaveLength(1);
159
+ expect(result.copied[0]).toHaveProperty('name');
160
+ expect(result.copied[0].name).toBe('beam');
161
+ });
162
+ ```
163
+
164
+ **Step 4: Run test to verify it passes**
165
+
166
+ Run: `npm test -- tests/add-themes-command.test.js -t "returns Theme objects"`
167
+ Expected: PASS
168
+
169
+ **Step 5: Commit**
170
+
171
+ ```bash
172
+ git add lib/add-themes-command.js tests/add-themes-command.test.js
173
+ git commit -m "test: verify AddThemesCommand returns Theme objects"
174
+ ```
175
+
176
+ ---
177
+
178
+ ## Task 4: Ensure AddThemesCommand always checks conflicts
179
+
180
+ **Files:**
181
+ - Modify: `lib/add-themes-command.js`
182
+
183
+ **Step 1: Find _findConflicts usage**
184
+
185
+ Check where conflict checking happens in execute() method.
186
+
187
+ **Step 2: Ensure conflict check always runs**
188
+
189
+ Remove any conditional that skips conflict checking. The method should always run:
190
+ - In new projects: finds nothing (harmless)
191
+ - In existing projects: finds actual conflicts
192
+
193
+ **Step 3: Add test for conflict check in empty project**
194
+
195
+ ```javascript
196
+ test('execute checks conflicts even in new projects', async () => {
197
+ const command = new AddThemesCommand({
198
+ templatePath: themesPath,
199
+ interactive: false
200
+ });
201
+
202
+ // Create empty project (no existing themes)
203
+ const emptyProject = await createTempProject();
204
+
205
+ const result = await command.execute(emptyProject, {
206
+ themes: ['beam']
207
+ });
208
+
209
+ // Should succeed with no conflicts found
210
+ expect(result.conflicts).toEqual([]);
211
+ expect(result.copied).toHaveLength(1);
212
+ });
213
+ ```
214
+
215
+ **Step 4: Run test**
216
+
217
+ Run: `npm test -- tests/add-themes-command.test.js -t "checks conflicts"`
218
+ Expected: PASS
219
+
220
+ **Step 5: Commit**
221
+
222
+ ```bash
223
+ git add lib/add-themes-command.js tests/add-themes-command.test.js
224
+ git commit -m "refactor: AddThemesCommand always checks for conflicts"
225
+ ```
226
+
227
+ ---
228
+
229
+ ## Task 5: Simplify create-project.js theme addition
230
+
231
+ **Files:**
232
+ - Modify: `cli/commands/create-project.js`
233
+ - Test: `tests/cli.test.js`
234
+
235
+ **Step 1: Read current create-project.js theme code**
236
+
237
+ Locate `askAddThemes()` and `addThemesToProject()` functions.
238
+
239
+ **Step 2: Replace theme addition flow**
240
+
241
+ Replace entire theme addition section with:
242
+
243
+ ```javascript
244
+ // Add themes to project
245
+ const command = new AddThemesCommand({
246
+ templatePath: themesLibraryPath
247
+ });
248
+
249
+ const { copied } = await command.execute(projectPath, {
250
+ skipConflictCheck: false
251
+ });
252
+
253
+ // Select active theme from copied themes
254
+ const themeNames = copied.map(t => t.name);
255
+ const activeTheme = await Prompts.promptActiveTheme(themeNames);
256
+ await ThemeManager.setActiveTheme(projectPath, activeTheme);
257
+ ```
258
+
259
+ **Step 3: Remove askAddThemes function**
260
+
261
+ Delete the `askAddThemes()` function - no longer needed.
262
+
263
+ **Step 4: Remove addThemesToProject function**
264
+
265
+ Delete the `addThemesToProject()` function - logic now in AddThemesCommand.
266
+
267
+ **Step 5: Update imports**
268
+
269
+ Ensure `Prompts` and `ThemeManager` are imported.
270
+
271
+ **Step 6: Run tests**
272
+
273
+ Run: `npm test -- tests/cli.test.js`
274
+ Expected: All tests pass
275
+
276
+ **Step 7: Manual test**
277
+
278
+ Run: `node index.js test-project --path /tmp`
279
+ Verify: Theme selection appears, active theme is prompted
280
+
281
+ **Step 8: Commit**
282
+
283
+ ```bash
284
+ git add cli/commands/create-project.js
285
+ git commit -m "refactor: simplify create-project theme addition using AddThemesCommand"
286
+ ```
287
+
288
+ ---
289
+
290
+ ## Task 6: Simplify add-themes-cli.js
291
+
292
+ **Files:**
293
+ - Modify: `cli/commands/add-themes-cli.js`
294
+ - Test: `tests/cli.test.js`
295
+
296
+ **Step 1: Read current add-themes-cli.js**
297
+
298
+ Locate theme selection and active theme logic.
299
+
300
+ **Step 2: Replace theme addition flow**
301
+
302
+ Replace entire theme section with:
303
+
304
+ ```javascript
305
+ const command = new AddThemesCommand({
306
+ templatePath: themesLibraryPath
307
+ });
308
+
309
+ const { copied, skipped, conflicts } = await command.execute(projectPath, {
310
+ themes: themeNames || undefined // Use args if provided
311
+ });
312
+
313
+ // Show summary
314
+ console.log(`\nCopied themes: ${copied.map(t => t.name).join(', ') || 'none'}`);
315
+ if (skipped.length) console.log(`Skipped: ${skipped.join(', ')}`);
316
+ if (conflicts.length) console.log(`Conflicts: ${conflicts.join(', ')}`);
317
+ ```
318
+
319
+ **Step 3: Remove active theme prompt**
320
+
321
+ Delete all code related to `promptActiveTheme` and `ThemeManager.setActiveTheme`.
322
+
323
+ **Step 4: Remove VSCode settings update**
324
+
325
+ Delete VSCode settings code - now handled by AddThemesCommand.
326
+
327
+ **Step 5: Run tests**
328
+
329
+ Run: `npm test -- tests/cli.test.js`
330
+ Expected: All tests pass
331
+
332
+ **Step 6: Manual test**
333
+
334
+ ```bash
335
+ cd /tmp/existing-project
336
+ node /stg/git/marp-presentation-template/index.js add-themes beam
337
+ ```
338
+ Verify: Themes added, active theme NOT changed
339
+
340
+ **Step 7: Commit**
341
+
342
+ ```bash
343
+ git add cli/commands/add-themes-cli.js
344
+ git commit -m "refactor: simplify add-themes-cli using AddThemesCommand, remove active theme logic"
345
+ ```
346
+
347
+ ---
348
+
349
+ ## Task 7: Update integration tests
350
+
351
+ **Files:**
352
+ - Modify: `tests/cli.test.js`
353
+
354
+ **Step 1: Review existing CLI tests**
355
+
356
+ Check which tests are affected by the refactoring.
357
+
358
+ **Step 2: Update test expectations**
359
+
360
+ Ensure tests expect:
361
+ - Theme selection to be prompted
362
+ - Active theme to be set in create-project
363
+ - Active theme NOT to change in add-themes-cli
364
+
365
+ **Step 3: Add test for active theme preservation**
366
+
367
+ ```javascript
368
+ test('add-themes command does not change active theme', async () => {
369
+ const project = await createProjectWithThemes(['beam']);
370
+ await ThemeManager.setActiveTheme(project.path, 'beam');
371
+
372
+ // Add more themes
373
+ await addThemesToProject(project.path, ['gaia-dark']);
374
+
375
+ const active = await ThemeManager.getActiveTheme(project.path);
376
+ expect(active).toBe('beam'); // Still beam, not changed
377
+ });
378
+ ```
379
+
380
+ **Step 4: Run all tests**
381
+
382
+ Run: `npm test`
383
+ Expected: All 175+ tests pass
384
+
385
+ **Step 5: Commit**
386
+
387
+ ```bash
388
+ git add tests/cli.test.js
389
+ git commit -m "test: update CLI tests for refactored theme addition"
390
+ ```
391
+
392
+ ---
393
+
394
+ ## Task 8: Final verification and documentation
395
+
396
+ **Files:**
397
+ - Modify: `CLAUDE.md` (if needed)
398
+
399
+ **Step 1: Run full test suite**
400
+
401
+ Run: `npm test`
402
+ Expected: 100% pass rate
403
+
404
+ **Step 2: Manual integration test**
405
+
406
+ ```bash
407
+ # Test new project creation
408
+ node index.js test-new --path /tmp
409
+ cd /tmp/test-new
410
+ cat .vscode/settings.json # Verify themes listed
411
+ grep "marp.themeSet" marp.config.js # Verify active theme
412
+
413
+ # Test adding themes to existing project
414
+ node /stg/git/marp-presentation-template/index.js add-themes uncover-minimal
415
+ cat .vscode/settings.json # Verify new theme added
416
+ grep "marp.themeSet" marp.config.js # Verify active theme unchanged
417
+ ```
418
+
419
+ **Step 3: Update CLAUDE.md if needed**
420
+
421
+ Document any API changes to AddThemesCommand.
422
+
423
+ **Step 4: Final commit**
424
+
425
+ ```bash
426
+ git add CLAUDE.md
427
+ git commit -m "docs: update AddThemesCommand API documentation"
428
+ ```
429
+
430
+ **Step 5: Create summary PR commit**
431
+
432
+ ```bash
433
+ git commit --allow-empty -m "refactor: complete theme addition refactoring
434
+
435
+ - AddThemesCommand now handles all theme addition logic
436
+ - create-project.js simplified from ~80 to ~10 lines
437
+ - add-themes-cli.js simplified from ~120 to ~10 lines
438
+ - Zero code duplication in theme addition
439
+ - Active theme selection only in create-project.js
440
+ - Conflict resolution always runs (harmless in new projects)
441
+ "
442
+ ```
443
+
444
+ ---
445
+
446
+ ## Verification Checklist
447
+
448
+ After completing all tasks:
449
+
450
+ - [ ] All unit tests pass (`npm test`)
451
+ - [ ] All integration tests pass
452
+ - [ ] Manual test: new project creation works with theme selection
453
+ - [ ] Manual test: add-themes adds themes without changing active theme
454
+ - [ ] VSCode settings updated correctly
455
+ - [ ] No duplicate code in CLI files
456
+ - [ ] Code review shows clean, maintainable structure