mdv-live 0.5.2 → 0.5.3
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 +16 -0
- package/bin/mdv.js +19 -8
- package/package.json +1 -1
- package/src/api/file.js +9 -3
- package/src/api/pdf.js +3 -4
- package/src/server.js +4 -1
- package/src/watcher.js +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,22 @@ 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.5.3] - 2026-03-29
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- Security: exec/execSync → execFile/process.kill でコマンドインジェクション防止(PDF生成・サーバーkill)
|
|
13
|
+
- Security: PIDバリデーション厳密化(数字のみ許可、部分一致を拒否)
|
|
14
|
+
- Range Requestのバリデーション追加(不正ヘッダで416、end超過はRFC準拠でclamp)
|
|
15
|
+
- ファイル監視の再描画でrelativeDirを渡すように修正(サブフォルダ内Markdownの画像パス解決)
|
|
16
|
+
- バージョン表示をpackage.jsonから動的取得に統一(CLI・サーバー・テスト全箇所)
|
|
17
|
+
|
|
18
|
+
## [0.5.2] - 2026-03-27
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- CJK + Unicode句読点で太字・斜体が壊れる問題を修正
|
|
23
|
+
|
|
8
24
|
## [0.5.1] - 2026-03-20
|
|
9
25
|
|
|
10
26
|
### Fixed
|
package/bin/mdv.js
CHANGED
|
@@ -5,10 +5,12 @@
|
|
|
5
5
|
* Compatible with the original Python mdv-live CLI
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { execSync } from 'node:child_process';
|
|
8
|
+
import { execFileSync, execSync } from 'node:child_process';
|
|
9
|
+
import { readFileSync } from 'node:fs';
|
|
9
10
|
import fs from 'node:fs/promises';
|
|
10
11
|
import { createServer as createNetServer } from 'node:net';
|
|
11
12
|
import path from 'node:path';
|
|
13
|
+
import { fileURLToPath } from 'node:url';
|
|
12
14
|
import { parseArgs } from 'node:util';
|
|
13
15
|
|
|
14
16
|
import open from 'open';
|
|
@@ -184,12 +186,17 @@ function listServers() {
|
|
|
184
186
|
function killServers(target, killAll) {
|
|
185
187
|
if (target) {
|
|
186
188
|
// Kill specific PID
|
|
189
|
+
if (!/^\d+$/.test(target)) {
|
|
190
|
+
console.log(`無効なPID: ${target}`);
|
|
191
|
+
return 1;
|
|
192
|
+
}
|
|
193
|
+
const pid = Number(target);
|
|
187
194
|
try {
|
|
188
|
-
|
|
189
|
-
console.log(`PID ${
|
|
195
|
+
process.kill(pid);
|
|
196
|
+
console.log(`PID ${pid} を停止しました`);
|
|
190
197
|
return 0;
|
|
191
198
|
} catch {
|
|
192
|
-
console.log(`PID ${
|
|
199
|
+
console.log(`PID ${pid} の停止に失敗しました`);
|
|
193
200
|
return 1;
|
|
194
201
|
}
|
|
195
202
|
}
|
|
@@ -214,7 +221,7 @@ function killServers(target, killAll) {
|
|
|
214
221
|
let killed = 0;
|
|
215
222
|
for (const proc of processes) {
|
|
216
223
|
try {
|
|
217
|
-
|
|
224
|
+
process.kill(proc.pid);
|
|
218
225
|
console.log(` PID ${proc.pid} (port ${proc.port}) を停止`);
|
|
219
226
|
killed++;
|
|
220
227
|
} catch {
|
|
@@ -279,7 +286,7 @@ async function convertToPdf(inputPath, outputPath) {
|
|
|
279
286
|
*/
|
|
280
287
|
async function convertMarpToPdf(inputPath, outputPath) {
|
|
281
288
|
try {
|
|
282
|
-
|
|
289
|
+
execFileSync('npx', ['@marp-team/marp-cli', '--no-stdin', inputPath, '--pdf', '--html', '--allow-local-files', '-o', outputPath], {
|
|
283
290
|
encoding: 'utf-8',
|
|
284
291
|
stdio: 'inherit'
|
|
285
292
|
});
|
|
@@ -302,7 +309,7 @@ async function convertMarkdownToPdf(inputPath, outputPath) {
|
|
|
302
309
|
|
|
303
310
|
try {
|
|
304
311
|
const pdfOptions = '{"format":"A4","margin":{"top":"20mm","right":"20mm","bottom":"20mm","left":"20mm"}}';
|
|
305
|
-
|
|
312
|
+
execFileSync('npx', ['md-to-pdf', inputPath, '--pdf-options', pdfOptions], {
|
|
306
313
|
encoding: 'utf-8',
|
|
307
314
|
stdio: 'inherit',
|
|
308
315
|
cwd: path.dirname(inputPath)
|
|
@@ -453,7 +460,11 @@ async function main() {
|
|
|
453
460
|
}
|
|
454
461
|
|
|
455
462
|
if (values.version) {
|
|
456
|
-
|
|
463
|
+
const __cliDir = path.dirname(fileURLToPath(import.meta.url));
|
|
464
|
+
const { version } = JSON.parse(
|
|
465
|
+
readFileSync(path.join(__cliDir, '..', 'package.json'), 'utf-8')
|
|
466
|
+
);
|
|
467
|
+
console.log(`mdv v${version}`);
|
|
457
468
|
process.exit(0);
|
|
458
469
|
}
|
|
459
470
|
|
package/package.json
CHANGED
package/src/api/file.js
CHANGED
|
@@ -292,9 +292,15 @@ export function setupFileRoutes(app) {
|
|
|
292
292
|
|
|
293
293
|
// Range Request for video/audio streaming
|
|
294
294
|
const fileSize = stat.size;
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
|
|
295
|
+
const match = /^bytes=(\d+)-(\d+)?$/.exec(rangeHeader);
|
|
296
|
+
if (!match) {
|
|
297
|
+
return res.status(416).set('Content-Range', `bytes */${fileSize}`).end();
|
|
298
|
+
}
|
|
299
|
+
const start = Number(match[1]);
|
|
300
|
+
if (start >= fileSize) {
|
|
301
|
+
return res.status(416).set('Content-Range', `bytes */${fileSize}`).end();
|
|
302
|
+
}
|
|
303
|
+
const end = Math.min(match[2] ? Number(match[2]) : fileSize - 1, fileSize - 1);
|
|
298
304
|
const chunkSize = end - start + 1;
|
|
299
305
|
const mimeType = mime.lookup(fullPath) || 'application/octet-stream';
|
|
300
306
|
|
package/src/api/pdf.js
CHANGED
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
* Uses marp-cli for Marp presentations
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { execFile } from 'child_process';
|
|
7
7
|
import { promisify } from 'util';
|
|
8
8
|
import fs from 'fs/promises';
|
|
9
9
|
import path from 'path';
|
|
10
10
|
import { fileURLToPath } from 'url';
|
|
11
11
|
import { validatePath } from '../utils/path.js';
|
|
12
12
|
|
|
13
|
-
const
|
|
13
|
+
const execFileAsync = promisify(execFile);
|
|
14
14
|
const marpBin = path.join(
|
|
15
15
|
path.dirname(fileURLToPath(import.meta.url)),
|
|
16
16
|
'..',
|
|
@@ -49,10 +49,9 @@ export function setupPdfRoutes(app) {
|
|
|
49
49
|
|
|
50
50
|
const outputPath = fullPath.replace(/\.md$/, '.pdf');
|
|
51
51
|
const outputFileName = path.basename(outputPath);
|
|
52
|
-
const command = `"${marpBin}" "${fullPath}" -o "${outputPath}" --html --allow-local-files --no-stdin`;
|
|
53
52
|
|
|
54
53
|
try {
|
|
55
|
-
await
|
|
54
|
+
await execFileAsync(marpBin, [fullPath, '-o', outputPath, '--html', '--allow-local-files', '--no-stdin'], { timeout: 60000 });
|
|
56
55
|
res.download(outputPath, outputFileName, (err) => {
|
|
57
56
|
if (err) {
|
|
58
57
|
console.error('Download error:', err);
|
package/src/server.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import express from 'express';
|
|
7
|
+
import { readFileSync } from 'fs';
|
|
7
8
|
import { createServer } from 'http';
|
|
8
9
|
import path from 'path';
|
|
9
10
|
import { fileURLToPath } from 'url';
|
|
@@ -17,7 +18,9 @@ import { setupWebSocket } from './websocket.js';
|
|
|
17
18
|
|
|
18
19
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
19
20
|
const STATIC_DIR = path.join(__dirname, 'static');
|
|
20
|
-
const VERSION =
|
|
21
|
+
const { version: VERSION } = JSON.parse(
|
|
22
|
+
readFileSync(path.join(__dirname, '..', 'package.json'), 'utf-8')
|
|
23
|
+
);
|
|
21
24
|
|
|
22
25
|
/**
|
|
23
26
|
* Setup API routes for the Express app
|
package/src/watcher.js
CHANGED
|
@@ -65,9 +65,10 @@ export function setupWatcher(rootDir, wss, options = {}) {
|
|
|
65
65
|
|
|
66
66
|
watcher.on('change', async (filePath) => {
|
|
67
67
|
const relativePath = toRelativePath(filePath);
|
|
68
|
+
const relativeDir = path.dirname(relativePath);
|
|
68
69
|
|
|
69
70
|
try {
|
|
70
|
-
const rendered = await renderFile(filePath);
|
|
71
|
+
const rendered = await renderFile(filePath, relativeDir === '.' ? '' : relativeDir);
|
|
71
72
|
wss.broadcastFileUpdate(relativePath, {
|
|
72
73
|
type: 'file_update',
|
|
73
74
|
path: relativePath,
|