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,261 @@
1
+ # Theme Management Guide
2
+
3
+ This guide explains how to use the theme management system in your Marp presentation project.
4
+
5
+ ## Overview
6
+
7
+ The theme management system provides a simple way to:
8
+ - Add pre-built themes to your project
9
+ - List available and installed themes
10
+ - Switch between themes
11
+ - Create custom themes
12
+ - Integrate with VSCode for live preview
13
+
14
+ ## Available Themes
15
+
16
+ The following themes are available in the template:
17
+
18
+ - **beam** - Clean, modern theme with progress bar
19
+ - **default-clean** - Minimal variation of default theme
20
+ - **gaia-dark** - Dark version of the Gaia theme
21
+ - **marpx** - Extended version of Marp's default theme
22
+ - **socrates** - Educational theme with clear typography
23
+ - **uncover-minimal** - Minimal reveal-style theme
24
+
25
+ ### System Themes
26
+
27
+ These built-in Marp themes are always available:
28
+ - **default** - Marp's default theme
29
+ - **gaia** - Clean, professional theme
30
+ - **uncover** - Slide-by-slide reveal theme
31
+
32
+ ## Commands
33
+
34
+ ### List Themes
35
+
36
+ ```bash
37
+ npm run theme:list
38
+ ```
39
+
40
+ This shows:
41
+ - Available themes in the theme library
42
+ - Installed themes in your project's `themes/` directory
43
+ - Dependencies for each theme
44
+
45
+ ### Add Themes
46
+
47
+ ```bash
48
+ npm run theme:add
49
+ ```
50
+
51
+ You'll be prompted to:
52
+ 1. Select themes from the available list
53
+ 2. Resolve any conflicts (if themes with the same name exist)
54
+ 3. Confirm the selection
55
+
56
+ The command will:
57
+ - Copy theme files to your `themes/` directory
58
+ - Resolve dependencies automatically
59
+ - Update VSCode settings
60
+ - Skip themes that would conflict (unless using `--force`)
61
+
62
+ **Options:**
63
+ ```bash
64
+ npm run theme add --force # Overwrite existing themes
65
+ npm run theme add --no-vscode # Skip VSCode settings update
66
+ ```
67
+
68
+ ### Set Active Theme
69
+
70
+ ```bash
71
+ npm run theme:set <theme-name>
72
+ ```
73
+
74
+ This will:
75
+ - Update the `theme` value in `presentation.md` frontmatter
76
+ - Add the theme to VSCode settings (for custom themes)
77
+ - Warn if the theme is not installed
78
+
79
+ **Examples:**
80
+ ```bash
81
+ npm run theme:set beam
82
+ npm run theme:set default
83
+ npm run theme:set gaia
84
+ ```
85
+
86
+ ### Create Custom Theme
87
+
88
+ ```bash
89
+ npm run theme:create <theme-name>
90
+ ```
91
+
92
+ You'll be prompted to:
93
+ 1. Select a parent theme to base your theme on
94
+ 2. Choose a directory location (root, subfolder, or new folder)
95
+
96
+ The command will:
97
+ - Create a new CSS file in the appropriate location
98
+ - Add the theme directive and parent import
99
+ - Update VSCode settings if needed
100
+
101
+ **Example:**
102
+ ```bash
103
+ npm run theme:create my-brand
104
+ # Select parent: beam
105
+ # Select location: root
106
+ # Creates: themes/my-brand.css
107
+ ```
108
+
109
+ ## Theme File Structure
110
+
111
+ Themes are organized in the `themes/` directory:
112
+
113
+ ```
114
+ my-presentation/
115
+ ├── themes/
116
+ │ ├── beam.css
117
+ │ ├── marpx.css
118
+ │ └── custom/
119
+ │ └── my-theme.css
120
+ └── presentation.md
121
+ ```
122
+
123
+ ### Theme File Format
124
+
125
+ Each theme CSS file must include:
126
+ 1. A theme directive: `/* @theme theme-name */`
127
+ 2. Optional parent imports: `@import "parent-theme";`
128
+
129
+ **Example:**
130
+ ```css
131
+ /* @theme my-custom */
132
+ @import "beam";
133
+
134
+ :root {
135
+ --color-primary: #3498db;
136
+ --color-secondary: #2ecc71;
137
+ }
138
+
139
+ section {
140
+ background: linear-gradient(135deg, var(--color-primary), var(--color-secondary));
141
+ }
142
+ ```
143
+
144
+ ## VSCode Integration
145
+
146
+ The theme management system automatically updates VSCode settings for live preview support.
147
+
148
+ ### Settings Location
149
+
150
+ `.vscode/settings.json`:
151
+ ```json
152
+ {
153
+ "markdown.marp.themes": [
154
+ "themes/beam/beam.css",
155
+ "themes/marpx/marpx.css"
156
+ ]
157
+ }
158
+ ```
159
+
160
+ ### How It Works
161
+
162
+ - When you add themes, they're added to the settings
163
+ - When you set an active theme, it's added to the settings
164
+ - System themes (default, gaia, uncover) are not added (built-in)
165
+ - Use `--no-vscode` to skip this behavior
166
+
167
+ ## Manual Theme Management
168
+
169
+ You can also manage themes manually:
170
+
171
+ ### Add a Theme Manually
172
+
173
+ 1. Copy your theme CSS file to the `themes/` directory (e.g., `themes/your-theme/your-theme.css`)
174
+ 2. Add the theme directive: `/* @theme your-theme-name */`
175
+ 3. Update `.vscode/settings.json` if needed:
176
+ ```json
177
+ {
178
+ "markdown.marp.themes": ["themes/your-theme/your-theme.css"]
179
+ }
180
+ ```
181
+
182
+ ### Switch Theme Manually
183
+
184
+ Edit `presentation.md`:
185
+ ```yaml
186
+ ---
187
+ marp: true
188
+ theme: your-theme-name
189
+ ---
190
+ ```
191
+
192
+ ## Troubleshooting
193
+
194
+ ### Theme Not Found
195
+
196
+ If you get a "Theme not found" error:
197
+ 1. Check if the theme is installed: `npm run theme:list`
198
+ 2. Add the theme if missing: `npm run theme:add`
199
+ 3. Verify the theme name matches exactly
200
+
201
+ ### Live Preview Not Working
202
+
203
+ If VSCode live preview doesn't show your theme:
204
+ 1. Check `.vscode/settings.json` exists
205
+ 2. Verify the theme path is correct
206
+ 3. Reload the VSCode window
207
+
208
+ ### Dependencies Not Found
209
+
210
+ Warning about missing dependencies is expected for system themes (default, gaia, uncover). These are built into Marp CLI and don't need to be in your project.
211
+
212
+ ## Best Practices
213
+
214
+ 1. **Use Descriptive Names**: Name themes clearly (e.g., `brand-primary`, `presentation-dark`)
215
+ 2. **Organize Themes**: Use subfolders for many themes
216
+ 3. **Document Dependencies**: Include parent theme imports in comments
217
+ 4. **Test Themes**: Always test with `npm run dev` after changing themes
218
+ 5. **Version Control**: Commit your custom themes to git
219
+
220
+ ## Advanced Usage
221
+
222
+ ### Creating Theme Variants
223
+
224
+ Create multiple variants based on the same parent:
225
+
226
+ ```bash
227
+ npm run theme:create brand-light # Parent: beam
228
+ npm run theme:create brand-dark # Parent: beam
229
+ ```
230
+
231
+ Edit the themes to differentiate them:
232
+
233
+ ```css
234
+ /* themes/brand-light.css */
235
+ /* @theme brand-light */
236
+ @import "beam";
237
+ :root { --bg-color: #ffffff; }
238
+
239
+ /* themes/brand-dark.css */
240
+ /* @theme brand-dark */
241
+ @import "beam";
242
+ :root { --bg-color: #1a1a1a; }
243
+ ```
244
+
245
+ ### Conditional Themes
246
+
247
+ Use different themes for different presentations:
248
+
249
+ ```bash
250
+ # For external presentations
251
+ npm run theme:set brand-professional
252
+
253
+ # For internal workshops
254
+ npm run theme:set brand-casual
255
+ ```
256
+
257
+ ## References
258
+
259
+ - [Marp Theme Documentation](https://marp.app/docs/theming)
260
+ - [Marp CLI GitHub](https://github.com/marp-team/marp-cli)
261
+ - [CSS Custom Properties](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties)
package/index.js CHANGED
@@ -1,184 +1,131 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const fs = require('fs');
4
- const path = require('path');
5
- const { spawnSync } = require('child_process');
6
- const readline = require('readline');
7
-
8
- const projectName = process.argv[2];
9
-
10
- if (!projectName) {
11
- console.error('Please provide a project name:');
12
- console.error(' npx create-marp-presentation <project-name>');
13
- process.exit(1);
14
- }
15
-
16
- // Валидация имени проекта
17
- const validName = /^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/;
18
- if (!validName.test(projectName)) {
19
- console.error(`Invalid project name: "${projectName}"`);
20
- console.error('Project name must be lowercase, contain only letters, numbers, and hyphens.');
21
- process.exit(1);
22
- }
23
-
24
- const projectPath = path.join(process.cwd(), projectName);
25
-
26
- // Запрос на создание примеров слайдов
27
- async function askCreateExamples() {
28
- return new Promise((resolve) => {
29
- // Интерактивный режим — спрашиваем пользователя
30
- if (process.stdin.isTTY) {
31
- const rl = readline.createInterface({
32
- input: process.stdin,
33
- output: process.stdout
34
- });
35
-
36
- rl.question('Create example slides file? (Y/n) ', (answer) => {
37
- rl.close();
38
- const normalized = answer.toLowerCase().trim();
39
- resolve(normalized !== 'n' && normalized !== 'no');
40
- });
41
- } else {
42
- // Неинтерактивный режим — читаем из stdin если есть данные
43
- let input = '';
44
- process.stdin.setEncoding('utf8');
45
-
46
- process.stdin.on('readable', () => {
47
- let chunk;
48
- while ((chunk = process.stdin.read()) !== null) {
49
- input += chunk;
50
- }
51
- });
52
-
53
- process.stdin.on('end', () => {
54
- const normalized = input.toLowerCase().trim();
55
- // Если ввод пустой, создаём примеры по умолчанию
56
- if (normalized === '') {
57
- resolve(true);
58
- } else {
59
- resolve(normalized !== 'n' && normalized !== 'no');
60
- }
61
- });
62
-
63
- // Если stdin не имеет данных сразу, завершаем
64
- if (process.stdin.readableLength === 0) {
65
- // Даем небольшое время на появление данных
66
- setTimeout(() => {
67
- if (input === '') {
68
- process.stdin.destroy();
69
- resolve(true);
70
- }
71
- }, 10);
72
- }
73
- }
74
- });
75
- }
3
+ /**
4
+ * create-marp-presentation - CLI entry point
5
+ * Supports dual entry points:
6
+ * 1. npx create-marp-presentation <name> [--path <dir>] - Create new project
7
+ * 2. npx create-marp-presentation theme:add <path> [themes...] - Add themes to existing project
8
+ */
76
9
 
77
- // Validate path to prevent traversal attacks
78
- const resolvedPath = path.resolve(projectPath);
79
- if (!resolvedPath.startsWith(path.resolve(process.cwd()))) {
80
- console.error('Invalid project path: path traversal detected.');
81
- process.exit(1);
82
- }
10
+ const path = require('path');
83
11
 
84
- // Проверка существования папки
85
- if (fs.existsSync(projectPath)) {
86
- console.error(`Directory "${projectName}" already exists.`);
87
- process.exit(1);
88
- }
12
+ const { createProject, validateProjectName, parsePathArg } = require('./cli/commands/create-project');
13
+ const { addThemesToExistingProject } = require('./cli/commands/add-themes-cli');
14
+ const { validateOutputPath } = require('./cli/utils/file-utils');
89
15
 
90
- // Получаем путь к template
16
+ // Paths
91
17
  const templatePath = path.join(__dirname, 'template');
92
-
93
- // Рекурсивное копирование директории
94
- const copyDir = (src, dest) => {
95
- const entries = fs.readdirSync(src, { withFileTypes: true });
96
-
97
- for (const entry of entries) {
98
- const srcPath = path.join(src, entry.name);
99
- const destPath = path.join(dest, entry.name);
100
-
101
- if (entry.isDirectory()) {
102
- fs.mkdirSync(destPath, { recursive: true });
103
- copyDir(srcPath, destPath);
104
- } else {
105
- fs.copyFileSync(srcPath, destPath);
106
- }
107
- }
108
- };
109
-
110
- // Копирование опциональных файлов
111
- const copyOptionalFiles = (destPath) => {
112
- const optionalPath = path.join(__dirname, 'template-optional');
113
-
114
- // Копируем examples.md
115
- const examplesSrc = path.join(optionalPath, 'examples.md');
116
- const examplesDest = path.join(destPath, 'examples.md');
117
- if (fs.existsSync(examplesSrc)) {
118
- fs.copyFileSync(examplesSrc, examplesDest);
18
+ const themesLibraryPath = path.join(__dirname, 'themes');
19
+
20
+ /**
21
+ * Show usage information
22
+ * @param {boolean} isError - If true, write to stderr and exit with error code
23
+ */
24
+ function showUsage(isError = false) {
25
+ const output = isError ? console.error : console.log;
26
+ output('Please provide a project name:');
27
+ output(' npx create-marp-presentation <project-name> [--path <output-dir>]');
28
+ output('');
29
+ output('Or use the theme:add command:');
30
+ output(' npx create-marp-presentation theme:add <project-path> [theme-names...]');
31
+ output('');
32
+ output('Examples:');
33
+ output(' npx create-marp-presentation my-project');
34
+ output(' npx create-marp-presentation my-project --path /tmp');
35
+ output(' npx create-marp-presentation my-project --path ~/projects');
36
+ output(' npx create-marp-presentation theme:add ./my-project');
37
+ output(' npx create-marp-presentation theme:add ./my-project beam marpx');
38
+ output('');
39
+
40
+ if (isError) {
41
+ process.exit(1);
119
42
  }
43
+ }
120
44
 
121
- // Копируем демо-изображение
122
- const demoImageSrc = path.join(optionalPath, 'static', 'demo-image.png');
123
- const demoImageDest = path.join(destPath, 'static', 'demo-image.png');
124
- if (fs.existsSync(demoImageSrc)) {
125
- fs.copyFileSync(demoImageSrc, demoImageDest);
45
+ /**
46
+ * Handle theme:add command
47
+ * @param {string[]} args - Command arguments
48
+ */
49
+ async function handleThemeAdd(args) {
50
+ const targetPath = args[0];
51
+ if (!targetPath) {
52
+ console.error('Usage: npx create-marp-presentation theme:add <project-path> [theme-names...]');
53
+ process.exit(1);
126
54
  }
127
- };
128
55
 
129
- console.log(`Creating Marp presentation: ${projectName}`);
130
- console.log();
56
+ const themeNames = args.slice(1); // Additional arguments are theme names
131
57
 
132
- // Основной async flow
133
- (async () => {
134
58
  try {
135
- // Запрашиваем создание примеров
136
- const createExamples = await askCreateExamples();
137
-
138
- // Создаём папку проекта
139
- fs.mkdirSync(projectPath, { recursive: true });
140
-
141
- // Рекурсивно копируем template
142
- copyDir(templatePath, projectPath);
143
-
144
- console.log('✓ Project created');
145
-
146
- // Копируем опциональные файлы
147
- if (createExamples) {
148
- copyOptionalFiles(projectPath);
149
- console.log('✓ Example slides added');
150
- console.log('✓ Demo image added to static/');
151
- }
152
- console.log();
153
-
154
- // Запускаем npm install
155
- console.log('Installing dependencies...');
156
- const installResult = spawnSync('npm', ['install'], {
157
- cwd: projectPath,
158
- stdio: 'inherit',
59
+ await addThemesToExistingProject(targetPath, {
60
+ themesLibraryPath,
61
+ themeNames: themeNames.length > 0 ? themeNames : null
159
62
  });
63
+ } catch (error) {
64
+ console.error(`Error: ${error.message}`);
65
+ process.exit(1);
66
+ }
67
+ }
160
68
 
161
- if (installResult.status !== 0) {
162
- console.error();
163
- console.error('Failed to install dependencies.');
164
- console.error('Please run "cd ' + projectName + ' && npm install" manually.');
69
+ /**
70
+ * Handle project creation command
71
+ * @param {string} projectName - Name of the project
72
+ * @param {string[]} args - Remaining arguments (e.g., --path)
73
+ */
74
+ async function handleProjectCreation(projectName, args = []) {
75
+ // Parse --path argument if present
76
+ const { pathIndex } = parsePathArg(args);
77
+ let outputPath = process.cwd();
78
+
79
+ if (pathIndex !== null) {
80
+ const pathArg = args[pathIndex + 1];
81
+ const validation = validateOutputPath(pathArg);
82
+ if (!validation.valid) {
83
+ console.error(`Invalid --path: "${pathArg}"`);
84
+ console.error(validation.error);
165
85
  process.exit(1);
166
86
  }
87
+ outputPath = validation.resolvedPath;
88
+ }
167
89
 
168
- console.log();
169
- console.log('✓ Dependencies installed');
170
- console.log();
171
- console.log('Next steps:');
172
- console.log(` cd ${projectName}`);
173
- console.log(' npm run dev # Start live preview');
174
- if (createExamples) {
175
- console.log(' marp examples.md # Preview example slides');
90
+ try {
91
+ await createProject(projectName, {
92
+ outputPath,
93
+ templatePath,
94
+ themesLibraryPath
95
+ });
96
+ } catch (error) {
97
+ if (error.message === 'Invalid project name' || error.message === 'Project directory already exists') {
98
+ process.exit(1);
176
99
  }
177
- console.log(' npm run build:all # Build all formats');
178
- console.log();
179
-
180
- } catch (err) {
181
- console.error('Error creating project:', err.message);
100
+ console.error('Error creating project:', error.message);
182
101
  process.exit(1);
183
102
  }
184
- })();
103
+ }
104
+
105
+ /**
106
+ * Main entry point
107
+ */
108
+ async function main() {
109
+ const [command, ...args] = process.argv.slice(2);
110
+
111
+ switch (command) {
112
+ case 'theme:add':
113
+ await handleThemeAdd(args);
114
+ break;
115
+
116
+ case undefined:
117
+ showUsage(true); // Exit with error code 1
118
+ break;
119
+
120
+ default:
121
+ // Treat as project name (backward compatible)
122
+ await handleProjectCreation(command, args);
123
+ break;
124
+ }
125
+ }
126
+
127
+ // Run
128
+ main().catch(error => {
129
+ console.error('Unexpected error:', error.message);
130
+ process.exit(1);
131
+ });