create-marp-presentation 1.1.0 → 1.2.0

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/README.md CHANGED
@@ -1,64 +1,72 @@
1
1
  # Create Marp Presentation
2
2
 
3
- Create a new Marp presentation with a single command.
3
+ Create beautiful presentations in Markdown. Zero setup.
4
4
 
5
- ## Installation
5
+ ## Quick Start
6
6
 
7
7
  ```bash
8
8
  npx create-marp-presentation my-presentation
9
+ cd my-presentation
10
+ npm run dev
9
11
  ```
10
12
 
11
- ## Usage
13
+ That's it! Edit `presentation.md` and see changes live at `http://localhost:8080`.
12
14
 
13
- ```bash
14
- cd my-presentation
15
- npm run dev # Live preview
16
- npm run build:all # Build all formats
17
- ```
15
+ ## VSCode Setup
18
16
 
19
- ## Features
17
+ For the best editing experience, install [Marp for VSCode](https://marketplace.visualstudio.com/items?itemName=marp-team.marp-vscode):
20
18
 
21
- - 🚀 One-command setup
22
- - 📝 Markdown slides
23
- - 🎨 Marp themes
24
- - 📦 HTML, PDF, PPTX export
25
- - 📁 Static files support
26
- - 🔥 Live preview
19
+ 1. Open VSCode
20
+ 2. Press `Ctrl+Shift+X` (or `Cmd+Shift+X` on Mac)
21
+ 3. Search "Marp for VSCode"
22
+ 4. Click Install
27
23
 
28
- ## Local Testing
24
+ After installing, open any `.md` file and click the **Marp: Preview** button in the top-right corner.
29
25
 
30
- To test project generation without publishing to npm:
26
+ ## What You Get
31
27
 
32
- ```bash
33
- # Clone the repository
34
- git clone https://github.com/echernyshev/marp-presentation-template.git
35
- cd marp-presentation-template
28
+ - **Markdown slides** - Write presentations in plain text
29
+ - **Live preview** - See changes instantly in browser
30
+ - **Export anywhere** - HTML, PDF, PPTX with one command
31
+ - **Themes** - Beautiful built-in themes, add more anytime
32
+ - **Static files** - Images and assets copied automatically
36
33
 
37
- # Install dependencies for tests
38
- npm install
34
+ ## Commands
39
35
 
40
- # Run tests
41
- npm test
36
+ | Command | What it does |
37
+ |---------|--------------|
38
+ | `npm run dev` | Live preview at http://localhost:8080 |
39
+ | `npm run build:all` | Export to HTML, PDF, and PPTX |
40
+ | `npm run clean` | Remove output folder |
42
41
 
43
- # Create a test project (interactive mode)
44
- node index.js test-project
42
+ ## Theme Management
45
43
 
46
- # Check the contents
47
- cd test-project
48
- ls -la
49
- npm run dev
50
- ```
44
+ Add and switch themes anytime.
51
45
 
52
- Other example commands:
46
+ ### Quick Theme Commands
53
47
 
54
48
  ```bash
55
- # Create a test project without examples
56
- echo "n" | node index.js test-project-minimal
49
+ npm run theme:add # Interactive: select themes to add
50
+ npm run theme:add beam # Add specific themes
51
+ npm run theme:list # See what's available
52
+ npm run theme:set marpx # Change active theme
57
53
  ```
58
54
 
59
- ## Documentation
55
+ When you create a project, you'll be prompted to add themes. Add more later with `npm run theme:add`.
56
+
57
+ [See full theme documentation →](docs/theme-management.md)
58
+
59
+ ## Local Development
60
60
 
61
- After creating a project, read the README in the project folder.
61
+ To test without publishing to npm:
62
+
63
+ ```bash
64
+ git clone https://github.com/echernyshev/marp-presentation-template.git
65
+ cd marp-presentation-template
66
+ npm install
67
+ npm test
68
+ node index.js test-project
69
+ ```
62
70
 
63
71
  ## License
64
72
 
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
+ });