md-pdf-studio 1.1.0 → 2.1.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/bin/cli.js CHANGED
@@ -1,186 +1,188 @@
1
- #!/usr/bin/env node
2
-
3
- const { Command } = require('commander');
4
- const path = require('path');
5
- const fs = require('fs');
6
- const { convertMarkdownToPdf } = require('../src/core/index');
7
- const packageJson = require('../package.json');
8
-
9
- const program = new Command();
10
-
11
- program
12
- .name('md-pdf-studio')
13
- .alias('md2pdf')
14
- .description('High-quality Markdown to PDF converter CLI with Mermaid, Math, TOC, Custom CSS, and Batch conversion.')
15
- .version(packageJson.version, '-v, --version', 'Output tool version')
16
- .argument('<inputs...>', 'Input Markdown file(s) or wildcard pattern (.md)')
17
- .option('-o, --output <path>', 'Output PDF/HTML file path or destination directory')
18
- .option('-f, --format <format>', 'Output format: pdf (default), html', 'pdf')
19
- .option('-w, --watch', 'Watch input file(s) and recompile automatically on changes', false)
20
- .option('-t, --theme <theme>', 'Theme name: modern (default), dark, academic, minimal, github, ebook, cyberpunk', 'modern')
21
- .option('--font <font>', 'Font family: Inter (default), Roboto, Lora, Merriweather, JetBrains Mono, Fira Code', 'Inter')
22
- .option('--cover', 'Auto-generate elegant document Cover Page', false)
23
- .option('-p, --page-size <size>', 'Paper size: A4, Letter, A3, Legal', 'A4')
24
- .option('-l, --landscape', 'Use landscape orientation', false)
25
- .option('-m, --margin <margin>', 'Page margin (e.g. 14mm 12mm 16mm 12mm)', '14mm 12mm 16mm 12mm')
26
- .option('--toc', 'Auto-generate Table of Contents (TOC)', false)
27
- .option('-c, --css <path>', 'Custom CSS stylesheet file path to inject')
28
- .option('-k, --keep-html', 'Keep temporary HTML file after conversion', false)
29
- .option('--no-mermaid', 'Disable Mermaid diagram rendering')
30
- .option('--no-katex', 'Disable KaTeX math formula rendering')
31
- .option('-b, --browser <path>', 'Custom Chrome/Edge executable path')
32
- .action(async (inputArgs, options) => {
33
- try {
34
- const runConversion = async () => {
35
- const startTime = Date.now();
36
- const inputFiles = expandInputFiles(inputArgs);
37
-
38
- if (inputFiles.length === 0) {
39
- console.error('\x1b[31m✖ Error:\x1b[0m No valid Markdown files found matching input.');
40
- if (!options.watch) process.exit(1);
41
- return;
42
- }
43
-
44
- const targetFormat = (options.format || 'pdf').toLowerCase();
45
- console.log(`\n\x1b[36m🚀 Converting ${inputFiles.length} file(s) to ${targetFormat.toUpperCase()}...\x1b[0m`);
46
- console.log(` Theme: ${options.theme}`);
47
- console.log(` Font: ${options.font}`);
48
- console.log(` Paper: ${options.pageSize} (${options.landscape ? 'Landscape' : 'Portrait'})`);
49
- if (options.cover) console.log(` Cover: Auto-generated Cover Page Enabled`);
50
- if (options.toc) console.log(` TOC: Auto-generated Table of Contents Enabled`);
51
- if (options.css) console.log(` CSS: ${path.resolve(options.css)}`);
52
-
53
- let targetOutputDir = null;
54
- if (options.output) {
55
- const resolvedOut = path.resolve(options.output);
56
- if (inputFiles.length === 1 && (resolvedOut.endsWith('.pdf') || resolvedOut.endsWith('.html'))) {
57
- targetOutputDir = null;
58
- } else {
59
- targetOutputDir = resolvedOut;
60
- if (!fs.existsSync(targetOutputDir)) {
61
- fs.mkdirSync(targetOutputDir, { recursive: true });
62
- }
63
- }
64
- }
65
-
66
- let count = 0;
67
- for (const file of inputFiles) {
68
- const fileBaseName = path.parse(file).name;
69
- let fileOutPath = null;
70
-
71
- if (targetOutputDir) {
72
- fileOutPath = path.join(targetOutputDir, `${fileBaseName}.${targetFormat}`);
73
- } else if (options.output && (options.output.endsWith('.pdf') || options.output.endsWith('.html'))) {
74
- fileOutPath = options.output;
75
- }
76
-
77
- console.log(`[\x1b[33m${count + 1}/${inputFiles.length}\x1b[0m] Processing: ${file}`);
78
-
79
- const result = await convertMarkdownToPdf(file, fileOutPath, {
80
- format: targetFormat,
81
- theme: options.theme,
82
- font: options.font,
83
- pageSize: options.pageSize,
84
- orientation: options.landscape ? 'landscape' : 'portrait',
85
- margin: options.margin,
86
- toc: options.toc,
87
- cover: options.cover,
88
- cssFile: options.css,
89
- keepHtml: options.keepHtml,
90
- mermaid: options.mermaid,
91
- katex: options.katex,
92
- executablePath: options.browser
93
- });
94
-
95
- const outPathDisplay = result.pdfPath || result.htmlPath;
96
- console.log(` \x1b[32m✔ File saved:\x1b[0m ${outPathDisplay}`);
97
- count++;
98
- }
99
-
100
- const duration = ((Date.now() - startTime) / 1000).toFixed(2);
101
- console.log(`\x1b[32m✨ Conversion complete!\x1b[0m ${count} file(s) generated in ${duration}s.`);
102
- };
103
-
104
- // Initial conversion
105
- await runConversion();
106
-
107
- // Watch mode implementation
108
- if (options.watch) {
109
- const inputFiles = expandInputFiles(inputArgs);
110
- console.log('\n\x1b[35m👀 Watch mode enabled. Monitoring files for changes...\x1b[0m');
111
-
112
- let debounceTimer = null;
113
- const triggerRecompile = (filename) => {
114
- if (debounceTimer) clearTimeout(debounceTimer);
115
- debounceTimer = setTimeout(async () => {
116
- console.log(`\n\x1b[33m🔄 Change detected in ${filename}. Recompiling...\x1b[0m`);
117
- try {
118
- await runConversion();
119
- } catch (err) {
120
- console.error(`\x1b[31m✖ Error during watch recompile:\x1b[0m ${err.message}`);
121
- }
122
- }, 300);
123
- };
124
-
125
- inputFiles.forEach(filePath => {
126
- if (fs.existsSync(filePath)) {
127
- fs.watch(filePath, (eventType) => {
128
- if (eventType === 'change') {
129
- triggerRecompile(path.basename(filePath));
130
- }
131
- });
132
- }
133
- });
134
-
135
- if (options.css && fs.existsSync(options.css)) {
136
- fs.watch(options.css, (eventType) => {
137
- if (eventType === 'change') {
138
- triggerRecompile(path.basename(options.css));
139
- }
140
- });
141
- }
142
- }
143
- } catch (err) {
144
- console.error(`\x1b[31m✖ Error:\x1b[0m ${err.message}`);
145
- process.exit(1);
146
- }
147
- });
148
-
149
- function expandInputFiles(args) {
150
- const filesSet = new Set();
151
-
152
- args.forEach(arg => {
153
- // Check if direct file exists
154
- if (fs.existsSync(arg)) {
155
- const stat = fs.statSync(arg);
156
- if (stat.isFile() && (arg.endsWith('.md') || arg.endsWith('.markdown') || arg.endsWith('.txt'))) {
157
- filesSet.add(path.resolve(arg));
158
- } else if (stat.isDirectory()) {
159
- const dirFiles = fs.readdirSync(arg);
160
- dirFiles.forEach(f => {
161
- if (f.endsWith('.md') || f.endsWith('.markdown')) {
162
- filesSet.add(path.resolve(arg, f));
163
- }
164
- });
165
- }
166
- } else {
167
- // Handle simple wildcard pattern like *.md or docs/*.md
168
- const dir = path.dirname(arg) || '.';
169
- const pattern = path.basename(arg);
170
-
171
- if (fs.existsSync(dir)) {
172
- const regexPattern = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$', 'i');
173
- const dirFiles = fs.readdirSync(dir);
174
- dirFiles.forEach(f => {
175
- if (regexPattern.test(f) && (f.endsWith('.md') || f.endsWith('.markdown'))) {
176
- filesSet.add(path.resolve(dir, f));
177
- }
178
- });
179
- }
180
- }
181
- });
182
-
183
- return Array.from(filesSet);
184
- }
185
-
186
- program.parse(process.argv);
1
+ #!/usr/bin/env node
2
+
3
+ const { Command } = require('commander');
4
+ const path = require('path');
5
+ const fs = require('fs');
6
+ const { convertMarkdownToPdf } = require('../src/core/index');
7
+ const packageJson = require('../package.json');
8
+
9
+ const program = new Command();
10
+
11
+ program
12
+ .name('md-pdf-studio')
13
+ .alias('md2pdf')
14
+ .description('High-quality Markdown to PDF converter CLI with Mermaid, Math, TOC, Custom CSS, and Batch conversion.')
15
+ .version(packageJson.version, '-v, --version', 'Output tool version')
16
+ .argument('<inputs...>', 'Input Markdown file(s) or wildcard pattern (.md)')
17
+ .option('-o, --output <path>', 'Output PDF/HTML file path or destination directory')
18
+ .option('-f, --format <format>', 'Output format: pdf (default), html', 'pdf')
19
+ .option('-w, --watch', 'Watch input file(s) and recompile automatically on changes', false)
20
+ .option('-t, --theme <theme>', 'Theme name: modern (default), dark, academic, minimal, github, ebook, cyberpunk', 'modern')
21
+ .option('--font <font>', 'Font family: Inter (default), Roboto, Lora, Merriweather, JetBrains Mono, Fira Code', 'Inter')
22
+ .option('--cover', 'Auto-generate elegant document Cover Page', false)
23
+ .option('-p, --page-size <size>', 'Paper size: A4, Letter, A3, Legal', 'A4')
24
+ .option('-l, --landscape', 'Use landscape orientation', false)
25
+ .option('-m, --margin <margin>', 'Page margin (e.g. 14mm 12mm 16mm 12mm)', '14mm 12mm 16mm 12mm')
26
+ .option('--toc', 'Auto-generate Table of Contents (TOC)', false)
27
+ .option('-c, --css <path>', 'Custom CSS stylesheet file path to inject')
28
+ .option('-k, --keep-html', 'Keep temporary HTML file after conversion', false)
29
+ .option('--no-mermaid', 'Disable Mermaid diagram rendering')
30
+ .option('--no-katex', 'Disable KaTeX math formula rendering')
31
+ .option('--no-highlight', 'Disable syntax highlighting of code blocks')
32
+ .option('-b, --browser <path>', 'Custom Chrome/Edge executable path')
33
+ .action(async (inputArgs, options) => {
34
+ try {
35
+ const runConversion = async () => {
36
+ const startTime = Date.now();
37
+ const inputFiles = expandInputFiles(inputArgs);
38
+
39
+ if (inputFiles.length === 0) {
40
+ console.error('\x1b[31m✖ Error:\x1b[0m No valid Markdown files found matching input.');
41
+ if (!options.watch) process.exit(1);
42
+ return;
43
+ }
44
+
45
+ const targetFormat = (options.format || 'pdf').toLowerCase();
46
+ console.log(`\n\x1b[36m🚀 Converting ${inputFiles.length} file(s) to ${targetFormat.toUpperCase()}...\x1b[0m`);
47
+ console.log(` Theme: ${options.theme}`);
48
+ console.log(` Font: ${options.font}`);
49
+ console.log(` Paper: ${options.pageSize} (${options.landscape ? 'Landscape' : 'Portrait'})`);
50
+ if (options.cover) console.log(` Cover: Auto-generated Cover Page Enabled`);
51
+ if (options.toc) console.log(` TOC: Auto-generated Table of Contents Enabled`);
52
+ if (options.css) console.log(` CSS: ${path.resolve(options.css)}`);
53
+
54
+ let targetOutputDir = null;
55
+ if (options.output) {
56
+ const resolvedOut = path.resolve(options.output);
57
+ if (inputFiles.length === 1 && (resolvedOut.endsWith('.pdf') || resolvedOut.endsWith('.html'))) {
58
+ targetOutputDir = null;
59
+ } else {
60
+ targetOutputDir = resolvedOut;
61
+ if (!fs.existsSync(targetOutputDir)) {
62
+ fs.mkdirSync(targetOutputDir, { recursive: true });
63
+ }
64
+ }
65
+ }
66
+
67
+ let count = 0;
68
+ for (const file of inputFiles) {
69
+ const fileBaseName = path.parse(file).name;
70
+ let fileOutPath = null;
71
+
72
+ if (targetOutputDir) {
73
+ fileOutPath = path.join(targetOutputDir, `${fileBaseName}.${targetFormat}`);
74
+ } else if (options.output && (options.output.endsWith('.pdf') || options.output.endsWith('.html'))) {
75
+ fileOutPath = options.output;
76
+ }
77
+
78
+ console.log(`[\x1b[33m${count + 1}/${inputFiles.length}\x1b[0m] Processing: ${file}`);
79
+
80
+ const result = await convertMarkdownToPdf(file, fileOutPath, {
81
+ format: targetFormat,
82
+ theme: options.theme,
83
+ font: options.font,
84
+ pageSize: options.pageSize,
85
+ orientation: options.landscape ? 'landscape' : 'portrait',
86
+ margin: options.margin,
87
+ toc: options.toc,
88
+ cover: options.cover,
89
+ cssFile: options.css,
90
+ keepHtml: options.keepHtml,
91
+ mermaid: options.mermaid,
92
+ katex: options.katex,
93
+ highlight: options.highlight,
94
+ executablePath: options.browser
95
+ });
96
+
97
+ const outPathDisplay = result.pdfPath || result.htmlPath;
98
+ console.log(` \x1b[32m✔ File saved:\x1b[0m ${outPathDisplay}`);
99
+ count++;
100
+ }
101
+
102
+ const duration = ((Date.now() - startTime) / 1000).toFixed(2);
103
+ console.log(`\x1b[32m✨ Conversion complete!\x1b[0m ${count} file(s) generated in ${duration}s.`);
104
+ };
105
+
106
+ // Initial conversion
107
+ await runConversion();
108
+
109
+ // Watch mode implementation
110
+ if (options.watch) {
111
+ const inputFiles = expandInputFiles(inputArgs);
112
+ console.log('\n\x1b[35m👀 Watch mode enabled. Monitoring files for changes...\x1b[0m');
113
+
114
+ let debounceTimer = null;
115
+ const triggerRecompile = (filename) => {
116
+ if (debounceTimer) clearTimeout(debounceTimer);
117
+ debounceTimer = setTimeout(async () => {
118
+ console.log(`\n\x1b[33m🔄 Change detected in ${filename}. Recompiling...\x1b[0m`);
119
+ try {
120
+ await runConversion();
121
+ } catch (err) {
122
+ console.error(`\x1b[31m✖ Error during watch recompile:\x1b[0m ${err.message}`);
123
+ }
124
+ }, 300);
125
+ };
126
+
127
+ inputFiles.forEach(filePath => {
128
+ if (fs.existsSync(filePath)) {
129
+ fs.watch(filePath, (eventType) => {
130
+ if (eventType === 'change') {
131
+ triggerRecompile(path.basename(filePath));
132
+ }
133
+ });
134
+ }
135
+ });
136
+
137
+ if (options.css && fs.existsSync(options.css)) {
138
+ fs.watch(options.css, (eventType) => {
139
+ if (eventType === 'change') {
140
+ triggerRecompile(path.basename(options.css));
141
+ }
142
+ });
143
+ }
144
+ }
145
+ } catch (err) {
146
+ console.error(`\x1b[31m✖ Error:\x1b[0m ${err.message}`);
147
+ process.exit(1);
148
+ }
149
+ });
150
+
151
+ function expandInputFiles(args) {
152
+ const filesSet = new Set();
153
+
154
+ args.forEach(arg => {
155
+ // Check if direct file exists
156
+ if (fs.existsSync(arg)) {
157
+ const stat = fs.statSync(arg);
158
+ if (stat.isFile() && (arg.endsWith('.md') || arg.endsWith('.markdown') || arg.endsWith('.txt'))) {
159
+ filesSet.add(path.resolve(arg));
160
+ } else if (stat.isDirectory()) {
161
+ const dirFiles = fs.readdirSync(arg);
162
+ dirFiles.forEach(f => {
163
+ if (f.endsWith('.md') || f.endsWith('.markdown')) {
164
+ filesSet.add(path.resolve(arg, f));
165
+ }
166
+ });
167
+ }
168
+ } else {
169
+ // Handle simple wildcard pattern like *.md or docs/*.md
170
+ const dir = path.dirname(arg) || '.';
171
+ const pattern = path.basename(arg);
172
+
173
+ if (fs.existsSync(dir)) {
174
+ const regexPattern = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$', 'i');
175
+ const dirFiles = fs.readdirSync(dir);
176
+ dirFiles.forEach(f => {
177
+ if (regexPattern.test(f) && (f.endsWith('.md') || f.endsWith('.markdown'))) {
178
+ filesSet.add(path.resolve(dir, f));
179
+ }
180
+ });
181
+ }
182
+ }
183
+ });
184
+
185
+ return Array.from(filesSet);
186
+ }
187
+
188
+ program.parse(process.argv);
package/package.json CHANGED
@@ -1,58 +1,62 @@
1
- {
2
- "name": "md-pdf-studio",
3
- "version": "1.1.0",
4
- "description": "A powerful CLI tool and Manifest V3 Browser Extension to convert Markdown files, text, or web pages into beautifully styled PDFs with Mermaid diagrams, LaTeX math, TOC, and themes.",
5
- "main": "src/core/index.js",
6
- "bin": {
7
- "md-pdf-studio": "bin/cli.js",
8
- "md2pdf": "bin/cli.js",
9
- "convert-md-to-pdf": "bin/cli.js"
10
- },
11
- "files": [
12
- "bin/",
13
- "src/"
14
- ],
15
- "scripts": {
16
- "start": "node bin/cli.js",
17
- "build:extension": "node scripts/build-extension.js",
18
- "fetch:libs": "node scripts/fetch-vendor-libs.js",
19
- "pre-commit": "node scripts/pre-commit.js",
20
- "check:commit": "node scripts/check-commit.js",
21
- "release": "node scripts/release.js",
22
- "test": "node tests/index.test.js",
23
- "test:sample": "node bin/cli.js examples/sample.md --toc -c examples/custom.css -o output_test.pdf"
24
- },
25
- "keywords": [
26
- "markdown",
27
- "pdf",
28
- "cli",
29
- "converter",
30
- "chrome-extension",
31
- "mermaid",
32
- "katex",
33
- "md2pdf",
34
- "marked",
35
- "md-pdf-studio",
36
- "markdown-to-pdf"
37
- ],
38
- "author": "vannt-dev",
39
- "license": "MIT",
40
- "repository": {
41
- "type": "git",
42
- "url": "git+https://github.com/vannt-dev/convert-md-to-pdf.git"
43
- },
44
- "homepage": "https://github.com/vannt-dev/convert-md-to-pdf#readme",
45
- "bugs": {
46
- "url": "https://github.com/vannt-dev/convert-md-to-pdf/issues"
47
- },
48
- "dependencies": {
49
- "commander": "^12.1.0",
50
- "marked": "^12.0.2"
51
- },
52
- "engines": {
53
- "node": ">=16.0.0"
54
- },
55
- "devDependencies": {
56
- "adm-zip": "^0.6.0"
57
- }
58
- }
1
+ {
2
+ "name": "md-pdf-studio",
3
+ "version": "2.1.0",
4
+ "description": "A powerful CLI tool and Manifest V3 Browser Extension to convert Markdown files, text, or web pages into beautifully styled PDFs with Mermaid diagrams, LaTeX math, TOC, and themes.",
5
+ "main": "src/core/index.js",
6
+ "bin": {
7
+ "md-pdf-studio": "bin/cli.js",
8
+ "md2pdf": "bin/cli.js",
9
+ "convert-md-to-pdf": "bin/cli.js"
10
+ },
11
+ "files": [
12
+ "bin/",
13
+ "src/"
14
+ ],
15
+ "scripts": {
16
+ "start": "node bin/cli.js",
17
+ "build:extension": "node scripts/build-extension.js",
18
+ "fetch:libs": "node scripts/fetch-vendor-libs.js",
19
+ "pre-commit": "node scripts/pre-commit.js",
20
+ "check:commit": "node scripts/check-commit.js",
21
+ "release": "node scripts/release.js",
22
+ "test": "node tests/index.test.js",
23
+ "test:sample": "node bin/cli.js examples/sample.md --toc -c examples/custom.css -o output_test.pdf"
24
+ },
25
+ "keywords": [
26
+ "markdown",
27
+ "pdf",
28
+ "cli",
29
+ "converter",
30
+ "chrome-extension",
31
+ "mermaid",
32
+ "katex",
33
+ "md2pdf",
34
+ "marked",
35
+ "md-pdf-studio",
36
+ "markdown-to-pdf"
37
+ ],
38
+ "author": "vannt-dev",
39
+ "license": "MIT",
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "git+https://github.com/vannt-dev/convert-md-to-pdf.git"
43
+ },
44
+ "homepage": "https://github.com/vannt-dev/convert-md-to-pdf#readme",
45
+ "bugs": {
46
+ "url": "https://github.com/vannt-dev/convert-md-to-pdf/issues"
47
+ },
48
+ "dependencies": {
49
+ "commander": "^15.0.0",
50
+ "highlight.js": "^11.12.0",
51
+ "marked": "^18.0.14"
52
+ },
53
+ "engines": {
54
+ "node": ">=22.12.0"
55
+ },
56
+ "devDependencies": {
57
+ "@commitlint/cli": "^21.2.3",
58
+ "@commitlint/config-conventional": "^21.2.3",
59
+ "adm-zip": "^0.6.0",
60
+ "lefthook": "^2.1.14"
61
+ }
62
+ }