mdv-live 0.3.0 → 0.3.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.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,26 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.3.2] - 2026-01-31
9
+
10
+ ### Changed
11
+
12
+ - Refactored codebase with extracted helper functions and constants
13
+ - Simplified CSS with consolidated variables (--success, --warning, --danger)
14
+ - Reduced styles.css by 84 lines, app.js by 45 lines
15
+ - Added ARIA attributes for improved accessibility
16
+ - Standardized test helpers and imports
17
+
18
+ ### Fixed
19
+
20
+ - File tree not updating on file structure changes (add/delete/rename)
21
+
22
+ ## [0.3.1] - 2026-01-31
23
+
24
+ ### Changed
25
+
26
+ - Minor code cleanup and organization
27
+
8
28
  ## [0.3.0] - 2026-01-31
9
29
 
10
30
  ### Added
package/README.md CHANGED
@@ -87,7 +87,7 @@ macOSで`.md`ファイルをダブルクリックしてMDVで開けるように
87
87
  which mdv
88
88
 
89
89
  # セットアップスクリプトを実行
90
- curl -fsSL https://raw.githubusercontent.com/panhouse/mdv/main/scripts/setup-macos-app.sh | bash
90
+ curl -fsSL https://raw.githubusercontent.com/panhouse/mdv-live/main/scripts/setup-macos-app.sh | bash
91
91
  ```
92
92
 
93
93
  または、リポジトリをクローンしている場合:
@@ -175,8 +175,8 @@ paginate: true
175
175
 
176
176
  ```bash
177
177
  # Clone repository
178
- git clone https://github.com/panhouse/mdv.git
179
- cd mdv
178
+ git clone https://github.com/panhouse/mdv-live.git
179
+ cd mdv-live
180
180
 
181
181
  # Install dependencies
182
182
  npm install
package/bin/mdv.js CHANGED
@@ -5,18 +5,20 @@
5
5
  * Compatible with the original Python mdv-live CLI
6
6
  */
7
7
 
8
- import { createMdvServer } from '../src/server.js';
8
+ import { execSync } from 'node:child_process';
9
+ import fs from 'node:fs/promises';
10
+ import { createServer as createNetServer } from 'node:net';
11
+ import path from 'node:path';
9
12
  import { parseArgs } from 'node:util';
10
- import { execSync, spawn } from 'child_process';
11
- import { createServer as createNetServer } from 'net';
12
- import path from 'path';
13
- import fs from 'fs/promises';
13
+
14
14
  import open from 'open';
15
15
 
16
+ import { createMdvServer } from '../src/server.js';
17
+
16
18
  const DEFAULT_PORT = 8642;
19
+ const MARP_FRONTMATTER_PATTERN = /^---\s*\n[\s\S]*?marp:\s*true[\s\S]*?\n---/;
17
20
 
18
- // Parse command line arguments
19
- const options = {
21
+ const OPTIONS = {
20
22
  port: {
21
23
  type: 'string',
22
24
  short: 'p',
@@ -60,6 +62,9 @@ const options = {
60
62
  }
61
63
  };
62
64
 
65
+ /**
66
+ * Display help message
67
+ */
63
68
  function showHelp() {
64
69
  console.log(`
65
70
  MDV - Markdown Viewer with file tree + live preview + Marp support
@@ -99,6 +104,7 @@ Examples:
99
104
 
100
105
  /**
101
106
  * Get running MDV server processes
107
+ * @returns {{pid: string, port: string, command: string}[]} Array of process info
102
108
  */
103
109
  function getMdvProcesses() {
104
110
  try {
@@ -139,7 +145,8 @@ function getMdvProcesses() {
139
145
  }
140
146
 
141
147
  /**
142
- * List running MDV servers
148
+ * List running MDV servers to console
149
+ * @returns {number} Exit code (0 = success)
143
150
  */
144
151
  function listServers() {
145
152
  const processes = getMdvProcesses();
@@ -165,6 +172,9 @@ function listServers() {
165
172
 
166
173
  /**
167
174
  * Kill MDV server(s)
175
+ * @param {string|null} target - Specific PID to kill, or null for all
176
+ * @param {boolean} killAll - Whether to kill all servers
177
+ * @returns {number} Exit code (0 = success, 1 = error)
168
178
  */
169
179
  function killServers(target, killAll) {
170
180
  if (target) {
@@ -212,14 +222,27 @@ function killServers(target, killAll) {
212
222
  }
213
223
 
214
224
  /**
215
- * Convert markdown to PDF using marp-cli
225
+ * Check if markdown content is a Marp presentation
226
+ * @param {string} content - Markdown file content
227
+ * @returns {boolean} True if content has Marp frontmatter
228
+ */
229
+ function isMarpFile(content) {
230
+ return MARP_FRONTMATTER_PATTERN.test(content);
231
+ }
232
+
233
+ /**
234
+ * Convert markdown to PDF using appropriate tool
235
+ * - Marp slides: use marp-cli
236
+ * - Regular markdown: use md-to-pdf for A4 document format
237
+ * @param {string} inputPath - Input markdown file path
238
+ * @param {string} [outputPath] - Output PDF file path
239
+ * @returns {Promise<number>} Exit code (0 = success, 1 = error)
216
240
  */
217
241
  async function convertToPdf(inputPath, outputPath) {
218
242
  const resolved = path.resolve(inputPath);
219
243
 
220
- try {
221
- await fs.access(resolved);
222
- } catch {
244
+ const fileExists = await fs.access(resolved).then(() => true).catch(() => false);
245
+ if (!fileExists) {
223
246
  console.error(`Error: File not found: ${inputPath}`);
224
247
  return 1;
225
248
  }
@@ -230,51 +253,98 @@ async function convertToPdf(inputPath, outputPath) {
230
253
  return 1;
231
254
  }
232
255
 
256
+ const content = await fs.readFile(resolved, 'utf-8');
257
+ const isMarp = isMarpFile(content);
233
258
  const defaultOutput = resolved.replace(/\.(md|markdown)$/i, '.pdf');
234
259
  const finalOutput = outputPath ? path.resolve(outputPath) : defaultOutput;
235
260
 
236
261
  console.log(`Converting ${inputPath} to PDF...`);
237
262
 
263
+ if (isMarp) {
264
+ return convertMarpToPdf(resolved, finalOutput);
265
+ }
266
+ return convertMarkdownToPdf(resolved, finalOutput);
267
+ }
268
+
269
+ /**
270
+ * Convert Marp presentation to PDF using marp-cli
271
+ * @param {string} inputPath - Resolved input file path
272
+ * @param {string} outputPath - Resolved output file path
273
+ * @returns {Promise<number>} Exit code
274
+ */
275
+ async function convertMarpToPdf(inputPath, outputPath) {
238
276
  try {
239
- // Use marp-cli for PDF conversion (supports Marp slides)
240
- execSync(`npx @marp-team/marp-cli "${resolved}" --pdf -o "${finalOutput}"`, {
277
+ execSync(`npx @marp-team/marp-cli --no-stdin "${inputPath}" --pdf -o "${outputPath}"`, {
241
278
  encoding: 'utf-8',
242
279
  stdio: 'inherit'
243
280
  });
244
- console.log(`PDF saved: ${finalOutput}`);
281
+ console.log(`PDF saved: ${outputPath}`);
245
282
  return 0;
246
- } catch (err) {
283
+ } catch {
284
+ console.error('Error: PDF conversion failed');
285
+ return 1;
286
+ }
287
+ }
288
+
289
+ /**
290
+ * Convert regular markdown to PDF using md-to-pdf (A4 format)
291
+ * @param {string} inputPath - Resolved input file path
292
+ * @param {string} outputPath - Resolved output file path
293
+ * @returns {Promise<number>} Exit code
294
+ */
295
+ async function convertMarkdownToPdf(inputPath, outputPath) {
296
+ console.log('Converting as document (A4 portrait)...');
297
+
298
+ try {
299
+ const pdfOptions = '{"format":"A4","margin":{"top":"20mm","right":"20mm","bottom":"20mm","left":"20mm"}}';
300
+ execSync(`npx md-to-pdf "${inputPath}" --pdf-options '${pdfOptions}'`, {
301
+ encoding: 'utf-8',
302
+ stdio: 'inherit',
303
+ cwd: path.dirname(inputPath)
304
+ });
305
+
306
+ // md-to-pdf outputs to same directory with .pdf extension
307
+ const generatedPdf = inputPath.replace(/\.(md|markdown)$/i, '.pdf');
308
+ if (generatedPdf !== outputPath) {
309
+ await fs.rename(generatedPdf, outputPath);
310
+ }
311
+
312
+ console.log(`PDF saved: ${outputPath}`);
313
+ return 0;
314
+ } catch {
247
315
  console.error('Error: PDF conversion failed');
248
- console.error('Make sure Node.js and npx are installed');
316
+ console.error('Make sure md-to-pdf is available (npx md-to-pdf)');
249
317
  return 1;
250
318
  }
251
319
  }
252
320
 
253
321
  /**
254
- * Check if a port is available
322
+ * Check if a port is available for binding
323
+ * @param {number} port - Port number to check
324
+ * @returns {Promise<boolean>} True if port is available
255
325
  */
256
- async function isPortAvailable(port) {
326
+ function isPortAvailable(port) {
257
327
  return new Promise((resolve) => {
258
328
  const server = createNetServer();
259
329
  server.once('error', () => resolve(false));
260
- server.once('listening', () => {
261
- server.close(() => resolve(true));
262
- });
330
+ server.once('listening', () => server.close(() => resolve(true)));
263
331
  server.listen(port);
264
332
  });
265
333
  }
266
334
 
267
335
  /**
268
336
  * Find an available port starting from the given port
337
+ * @param {number} startPort - Starting port number
338
+ * @param {number} [maxRetries=100] - Maximum ports to try
339
+ * @returns {Promise<number|null>} Available port or null if none found
269
340
  */
270
341
  async function findAvailablePort(startPort, maxRetries = 100) {
271
- for (let i = 0; i < maxRetries; i++) {
272
- const port = startPort + i;
273
- const available = await isPortAvailable(port);
274
- if (available) {
342
+ for (let offset = 0; offset < maxRetries; offset++) {
343
+ const port = startPort + offset;
344
+ if (await isPortAvailable(port)) {
275
345
  return port;
276
346
  }
277
- if (i > 0) {
347
+ if (offset > 0) {
278
348
  console.log(`ポート ${port - 1} は使用中です。${port} を試します...`);
279
349
  }
280
350
  }
@@ -282,29 +352,40 @@ async function findAvailablePort(startPort, maxRetries = 100) {
282
352
  }
283
353
 
284
354
  /**
285
- * Start MDV server with auto port increment
355
+ * Resolve target path to root directory and optional initial file
356
+ * @param {string} targetPath - User-provided path
357
+ * @returns {Promise<{rootDir: string, initialFile: string|null}>}
286
358
  */
287
- async function startViewer(targetPath, startPort, openBrowser) {
288
- let rootDir = process.cwd();
289
- let initialFile = null;
359
+ async function resolveTargetPath(targetPath) {
360
+ if (!targetPath || targetPath === '.') {
361
+ return { rootDir: process.cwd(), initialFile: null };
362
+ }
290
363
 
291
- if (targetPath && targetPath !== '.') {
292
- const resolved = path.resolve(targetPath);
293
- try {
294
- const stats = await fs.stat(resolved);
295
- if (stats.isDirectory()) {
296
- rootDir = resolved;
297
- } else if (stats.isFile()) {
298
- rootDir = path.dirname(resolved);
299
- initialFile = path.basename(resolved);
300
- }
301
- } catch {
302
- console.error(`Error: Path not found: ${targetPath}`);
303
- process.exit(1);
364
+ const resolved = path.resolve(targetPath);
365
+ try {
366
+ const stats = await fs.stat(resolved);
367
+ if (stats.isDirectory()) {
368
+ return { rootDir: resolved, initialFile: null };
369
+ }
370
+ if (stats.isFile()) {
371
+ return { rootDir: path.dirname(resolved), initialFile: path.basename(resolved) };
304
372
  }
373
+ } catch {
374
+ console.error(`Error: Path not found: ${targetPath}`);
375
+ process.exit(1);
305
376
  }
377
+ return { rootDir: process.cwd(), initialFile: null };
378
+ }
379
+
380
+ /**
381
+ * Start MDV server with auto port increment
382
+ * @param {string} targetPath - Target directory or file path
383
+ * @param {number} startPort - Starting port number
384
+ * @param {boolean} openBrowser - Whether to open browser automatically
385
+ */
386
+ async function startViewer(targetPath, startPort, openBrowser) {
387
+ const { rootDir, initialFile } = await resolveTargetPath(targetPath);
306
388
 
307
- // Find available port
308
389
  const port = await findAvailablePort(startPort);
309
390
  if (!port) {
310
391
  console.error('Error: 利用可能なポートが見つかりませんでした');
@@ -315,7 +396,6 @@ async function startViewer(targetPath, startPort, openBrowser) {
315
396
  console.log(`ポート ${startPort} は使用中のため、${port} で起動します`);
316
397
  }
317
398
 
318
- // Create and start server
319
399
  const mdv = createMdvServer({ rootDir, port });
320
400
  await mdv.start();
321
401
 
@@ -337,11 +417,14 @@ async function startViewer(targetPath, startPort, openBrowser) {
337
417
  }
338
418
  }
339
419
 
340
- async function main() {
341
- let args;
420
+ /**
421
+ * Parse command line arguments safely
422
+ * @returns {{values: object, positionals: string[]}}
423
+ */
424
+ function parseCommandLineArgs() {
342
425
  try {
343
- args = parseArgs({
344
- options,
426
+ return parseArgs({
427
+ options: OPTIONS,
345
428
  allowPositionals: true,
346
429
  strict: false
347
430
  });
@@ -350,33 +433,33 @@ async function main() {
350
433
  showHelp();
351
434
  process.exit(1);
352
435
  }
436
+ }
353
437
 
354
- const { values, positionals } = args;
438
+ /**
439
+ * Main entry point
440
+ */
441
+ async function main() {
442
+ const { values, positionals } = parseCommandLineArgs();
355
443
 
356
- // Help
357
444
  if (values.help) {
358
445
  showHelp();
359
446
  process.exit(0);
360
447
  }
361
448
 
362
- // Version
363
449
  if (values.version) {
364
- console.log('mdv v0.3.0');
450
+ console.log('mdv v0.3.2');
365
451
  process.exit(0);
366
452
  }
367
453
 
368
- // List servers
369
454
  if (values.list) {
370
455
  process.exit(listServers());
371
456
  }
372
457
 
373
- // Kill servers
374
458
  if (values.kill) {
375
459
  const pid = positionals[0] || null;
376
460
  process.exit(killServers(pid, values.all));
377
461
  }
378
462
 
379
- // PDF conversion
380
463
  if (values.pdf) {
381
464
  const inputPath = positionals[0];
382
465
  if (!inputPath) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mdv-live",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Markdown Viewer - File tree + Live preview + Marp support + Hot reload",
5
5
  "main": "src/server.js",
6
6
  "bin": {
@@ -30,11 +30,11 @@
30
30
  },
31
31
  "repository": {
32
32
  "type": "git",
33
- "url": "git+https://github.com/panhouse/mdv.git"
33
+ "url": "git+https://github.com/panhouse/mdv-live.git"
34
34
  },
35
- "homepage": "https://github.com/panhouse/mdv#readme",
35
+ "homepage": "https://github.com/panhouse/mdv-live#readme",
36
36
  "bugs": {
37
- "url": "https://github.com/panhouse/mdv/issues"
37
+ "url": "https://github.com/panhouse/mdv-live/issues"
38
38
  },
39
39
  "files": [
40
40
  "bin/",
@@ -43,7 +43,7 @@ Usage:
43
43
  - Double-click any .md file
44
44
  - Drag & drop .md files onto this app
45
45
 
46
- Version: 0.3.0 (Node.js)" buttons {"OK"} default button "OK" with title "MDV"
46
+ Version: 0.3.1 (Node.js)" buttons {"OK"} default button "OK" with title "MDV"
47
47
  end run
48
48
  EOF
49
49
 
@@ -127,9 +127,9 @@ cat << 'EOF' > "$TEMP_DIR/$APP_NAME/Contents/Info.plist"
127
127
  <key>CFBundlePackageType</key>
128
128
  <string>APPL</string>
129
129
  <key>CFBundleShortVersionString</key>
130
- <string>0.3.0</string>
130
+ <string>0.3.1</string>
131
131
  <key>CFBundleVersion</key>
132
- <string>0.3.0</string>
132
+ <string>0.3.1</string>
133
133
  <key>LSMinimumSystemVersion</key>
134
134
  <string>10.13</string>
135
135
  </dict>