create-marp-presentation 1.2.1 → 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,62 @@
1
+ # Conflict Resolution Bug Fix Design
2
+
3
+ **Date:** 2026-02-28
4
+ **Status:** Designed
5
+ **Issue:** Dialog repeats when resolving single theme conflict
6
+
7
+ ## Problem Description
8
+
9
+ When adding themes to an existing project, if a user selects an already existing theme, a conflict dialog appears. However, when the user chooses an action (Skip/Overwrite), the dialog repeats instead of resolving the conflict.
10
+
11
+ ## Root Cause
12
+
13
+ In `lib/add-themes-command.js`, the `_resolveConflictsInteractive` method doesn't handle the return values `'skip'` and `'overwrite'` from `promptConflictResolution`.
14
+
15
+ When there's a single conflict:
16
+ 1. `promptConflictResolution` returns `'skip'`, `'overwrite'`, or `'cancel'`
17
+ 2. The code only checks for `'skip-all'`, `'overwrite-all'`, and `'cancel'`
18
+ 3. When result is `'skip'` or `'overwrite'`, none of the conditions match
19
+ 4. Code falls through to the `for` loop
20
+ 5. User gets prompted again → **dialog repeats**
21
+
22
+ ## Solution
23
+
24
+ Add explicit handling for `'skip'` and `'overwrite'` values in `_resolveConflictsInteractive`.
25
+
26
+ ### Code Changes
27
+
28
+ **File:** `lib/add-themes-command.js`
29
+ **Method:** `_resolveConflictsInteractive` (lines 303-333)
30
+
31
+ Add after line 318 (after `if (result === 'cancel')`):
32
+ ```javascript
33
+ // Handle single conflict responses
34
+ if (result === 'skip') return { overwrite: [] };
35
+ if (result === 'overwrite') return { overwrite: conflicts };
36
+ ```
37
+
38
+ ### Updated Logic Flow
39
+
40
+ | Return Value | Behavior |
41
+ |--------------|----------|
42
+ | `skip-all` | Skip all conflicts |
43
+ | `overwrite-all` | Overwrite all conflicts |
44
+ | `cancel` | Cancel operation |
45
+ | `skip` | Skip single conflict |
46
+ | `overwrite` | Overwrite single conflict |
47
+ | `choose-each` | Prompt for each conflict individually |
48
+
49
+ ## Test Scenarios
50
+
51
+ 1. **Single conflict - Skip**: Theme skipped, no dialog repeat
52
+ 2. **Single conflict - Overwrite**: Theme overwritten, no dialog repeat
53
+ 3. **Single conflict - Cancel**: Operation cancelled
54
+ 4. **Multiple conflicts - choose-each**: Individual prompts for each
55
+ 5. **Multiple conflicts - skip-all/overwrite-all**: Existing behavior verified
56
+
57
+ ## Backward Compatibility
58
+
59
+ - No changes to public API
60
+ - Return values remain consistent
61
+ - Existing tests should pass without modification
62
+ - Custom prompt handlers still work via `this.options.prompts?.resolveConflicts`
@@ -319,7 +319,11 @@ class AddThemesCommand {
319
319
  if (result === 'overwrite-all') return { overwrite: conflicts };
320
320
  if (result === 'cancel') return { overwrite: [] };
321
321
 
322
- // Handle 'choose-each' or individual conflicts
322
+ // Handle single conflict responses
323
+ if (result === 'skip') return { overwrite: [] };
324
+ if (result === 'overwrite') return { overwrite: conflicts };
325
+
326
+ // Handle 'choose-each' - prompt for each conflict individually
323
327
  const overwrite = [];
324
328
  for (const conflict of conflicts) {
325
329
  const choice = await Prompts.promptSingleConflict(conflict);
package/lib/prompts.js CHANGED
@@ -17,11 +17,16 @@ class Prompts {
17
17
  return [];
18
18
  }
19
19
 
20
- const choices = availableThemes.map(theme => ({
21
- name: theme.name,
22
- value: theme.name,
23
- checked: false
24
- }));
20
+ const choices = availableThemes.map(theme => {
21
+ const displayName = theme.description
22
+ ? `${theme.name} - ${theme.description}`
23
+ : theme.name;
24
+ return {
25
+ name: displayName,
26
+ value: theme.name,
27
+ checked: false
28
+ };
29
+ });
25
30
 
26
31
  return await inquirer.checkbox({
27
32
  message: 'Select themes to add:',
@@ -152,6 +157,18 @@ class Prompts {
152
157
  });
153
158
  }
154
159
 
160
+ /**
161
+ * Prompt user for new theme description
162
+ *
163
+ * @returns {Promise<string|null>} Theme description or null if skipped
164
+ */
165
+ static async promptNewThemeDescription() {
166
+ return await inquirer.input({
167
+ message: 'Theme description (optional - press Enter to skip):',
168
+ default: ''
169
+ });
170
+ }
171
+
155
172
  /**
156
173
  * Prompt user for conflict resolution
157
174
  *
@@ -4,6 +4,7 @@ const path = require('path');
4
4
  const { ThemeResolver } = require('./theme-resolver');
5
5
  const { VSCodeIntegration } = require('./vscode-integration');
6
6
  const { Frontmatter } = require('./frontmatter');
7
+ const { Prompts } = require('./prompts');
7
8
  const {
8
9
  ThemeNotFoundError,
9
10
  ThemeAlreadyExistsError,
@@ -173,10 +174,11 @@ class ThemeManager {
173
174
  * @param {string|null} parent - Parent theme name or null
174
175
  * @param {string} location - 'root', 'existing' folder name, or 'new'
175
176
  * @param {string} newFolderName - Required if location is 'new'
177
+ * @param {string|null} description - Optional theme description
176
178
  * @throws {ThemeAlreadyExistsError} If theme with same name already exists
177
179
  * @throws {Error} If location is 'new' but newFolderName not provided
178
180
  */
179
- createTheme(name, parent, location, newFolderName = null) {
181
+ createTheme(name, parent, location, newFolderName = null, description = null) {
180
182
  // Check for duplicate
181
183
  if (this.getTheme(name)) {
182
184
  throw new ThemeAlreadyExistsError(name);
@@ -198,7 +200,11 @@ class ThemeManager {
198
200
  }
199
201
 
200
202
  // Generate CSS content
201
- let css = `/* @theme ${name} */\n\n`;
203
+ let css = `/* @theme ${name}`;
204
+ if (description && description.trim()) {
205
+ css += `\n * @description ${description.trim()}`;
206
+ }
207
+ css += ` */\n\n`;
202
208
 
203
209
  if (parent) {
204
210
  css += `@import "${parent}";\n\n`;
@@ -7,11 +7,12 @@ const path = require('path');
7
7
  * Represents a Marp theme
8
8
  */
9
9
  class Theme {
10
- constructor(name, cssPath, css, dependencies = []) {
10
+ constructor(name, cssPath, css, dependencies = [], description = null) {
11
11
  this.name = name;
12
12
  this.path = cssPath;
13
13
  this.css = css;
14
14
  this.dependencies = dependencies;
15
+ this.description = description;
15
16
  this.isSystem = ['default', 'gaia', 'uncover'].includes(name);
16
17
  }
17
18
  }
@@ -81,6 +82,22 @@ class ThemeResolver {
81
82
  return dependencies;
82
83
  }
83
84
 
85
+ /**
86
+ * Extract @description from CSS comment directive
87
+ * Pattern: /* @description text *\/
88
+ *
89
+ * @param {string} cssContent - CSS file content
90
+ * @returns {string|null} Description text or null if not found
91
+ */
92
+ static extractDescription(cssContent) {
93
+ // Match /* @description text */ - supports multi-line comments
94
+ // The [\s\S]*? allows matching across newlines (non-greedy)
95
+ const descRegex = /\/\*[\s\S]*?@description\s+([^\r\n*]+)[\s\S]*?\*\//;
96
+ const match = cssContent.match(descRegex);
97
+
98
+ return match ? match[1].trim() : null;
99
+ }
100
+
84
101
  /**
85
102
  * Resolve theme from a CSS file
86
103
  *
@@ -97,8 +114,9 @@ class ThemeResolver {
97
114
  const filename = path.basename(cssPath);
98
115
  const name = this.extractThemeName(css, filename) || filename;
99
116
  const dependencies = this.extractDependencies(css);
117
+ const description = this.extractDescription(css);
100
118
 
101
- return new Theme(name, cssPath, css, dependencies);
119
+ return new Theme(name, cssPath, css, dependencies, description);
102
120
  }
103
121
 
104
122
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-marp-presentation",
3
- "version": "1.2.1",
3
+ "version": "1.2.2",
4
4
  "description": "Create a new Marp presentation project with one command. Manage themes quickly and easily with the CLI.",
5
5
  "bin": {
6
6
  "create-marp-presentation": "index.js"
@@ -1,4 +1,5 @@
1
1
  /* @theme beam */
2
+ /* @description Beamer-inspired theme with clean headers and footer bars */
2
3
  /* Author: rnd195 https://github.com/rnd195/ */
3
4
  /* beam license - GNU GPLv3 https://github.com/rnd195/my-marp-themes/blob/live/licenses/LICENSE_beam */
4
5
  /* License of beamer which inspired this theme - GNU GPLv2 https://github.com/rnd195/my-marp-themes/blob/live/licenses/LICENSE_GPLv2 */
@@ -1,5 +1,5 @@
1
1
  /* @theme default-clean */
2
- /* A clean, minimal theme based on default */
2
+ /* @description A clean, minimal theme based on default */
3
3
 
4
4
  @import "default";
5
5
 
@@ -1,5 +1,5 @@
1
1
  /* @theme gaia-dark */
2
- /* A dark variant of the gaia theme */
2
+ /* @description A dark variant of the gaia theme */
3
3
 
4
4
  @import "gaia";
5
5
 
@@ -31,8 +31,9 @@
31
31
  *
32
32
  * ================================================================================ */
33
33
 
34
- /*
34
+ /*
35
35
  * @theme marpx
36
+ * @description Modern multi-format theme with auto-scaling support
36
37
  *
37
38
  * @auto-scaling true
38
39
  * @size 16:9 1280px 720px
@@ -1,4 +1,5 @@
1
1
  /* @theme socrates */
2
+ /* @description A classic teaching theme based on marpx */
2
3
 
3
4
  @import "marpx";
4
5
 
@@ -1,5 +1,5 @@
1
1
  /* @theme uncover-minimal */
2
- /* A minimal variant of the uncover theme */
2
+ /* @description A minimal variant of the uncover theme */
3
3
 
4
4
  @import "uncover";
5
5