mdv-live 0.3.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/CHANGELOG.md +50 -0
- package/LICENSE +21 -0
- package/README.md +240 -0
- package/bin/mdv.js +400 -0
- package/package.json +62 -0
- package/scripts/setup-macos-app.sh +172 -0
- package/src/api/file.js +243 -0
- package/src/api/pdf.js +74 -0
- package/src/api/tree.js +111 -0
- package/src/api/upload.js +70 -0
- package/src/rendering/index.js +98 -0
- package/src/rendering/markdown.js +126 -0
- package/src/rendering/marp.js +43 -0
- package/src/server.js +109 -0
- package/src/static/app.js +1883 -0
- package/src/static/favicon.ico +0 -0
- package/src/static/images/icon-128.png +0 -0
- package/src/static/images/icon-256.png +0 -0
- package/src/static/images/icon-32.png +0 -0
- package/src/static/images/icon-512.png +0 -0
- package/src/static/images/icon-64.png +0 -0
- package/src/static/images/icon.png +0 -0
- package/src/static/index.html +123 -0
- package/src/static/styles.css +1026 -0
- package/src/utils/fileTypes.js +148 -0
- package/src/utils/path.js +48 -0
- package/src/watcher.js +97 -0
- package/src/websocket.js +75 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.3.0] - 2026-01-31
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Initial Node.js version (rewrite from Python mdv-live)
|
|
13
|
+
- Express server with WebSocket for live reload
|
|
14
|
+
- markdown-it for standard Markdown rendering
|
|
15
|
+
- @marp-team/marp-core for Marp slide rendering
|
|
16
|
+
- File tree navigation with lazy loading
|
|
17
|
+
- Edit mode with textarea editor
|
|
18
|
+
- Dark/Light theme support
|
|
19
|
+
- Syntax highlighting with highlight.js
|
|
20
|
+
- Mermaid diagram support
|
|
21
|
+
- PDF output via browser print
|
|
22
|
+
- File operations (create, delete, rename, move, upload)
|
|
23
|
+
- Keyboard shortcuts for common actions
|
|
24
|
+
- Task list (checkbox) support with markdown-it-task-lists
|
|
25
|
+
- Range Request support for video/audio streaming
|
|
26
|
+
- WebSocket tree_update broadcast for multi-client sync
|
|
27
|
+
- Comprehensive security tests (76 tests)
|
|
28
|
+
|
|
29
|
+
### Security
|
|
30
|
+
|
|
31
|
+
- Path traversal prevention (absolute path, `..`, null byte)
|
|
32
|
+
- Filename sanitization for uploads
|
|
33
|
+
- Unified validatePath() for all API endpoints
|
|
34
|
+
|
|
35
|
+
### Marp Features
|
|
36
|
+
|
|
37
|
+
- Full compatibility with marp-core
|
|
38
|
+
- Official themes: default, gaia, uncover
|
|
39
|
+
- All directives: paginate, header, footer, backgroundColor, etc.
|
|
40
|
+
- Background images and split backgrounds
|
|
41
|
+
- KaTeX math support
|
|
42
|
+
- Slide navigation with arrow keys
|
|
43
|
+
|
|
44
|
+
### CLI Features
|
|
45
|
+
|
|
46
|
+
- Port auto-increment when port is in use
|
|
47
|
+
- Server list (`mdv -l`)
|
|
48
|
+
- Server kill (`mdv -k PID` or `mdv -k -a`)
|
|
49
|
+
- PDF conversion (`mdv --pdf file.md`)
|
|
50
|
+
- No-browser mode (`mdv --no-browser`)
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 PanHouse
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
# MDV - Markdown Viewer with Marp Support
|
|
2
|
+
|
|
3
|
+
ファイルツリー + ライブプレビュー + Marp完全対応のMarkdownビューア
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/mdv-live)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- 📁 左側にフォルダツリー表示(遅延読み込み対応)
|
|
11
|
+
- 📄 Markdownをリアルタイムレンダリング
|
|
12
|
+
- 🎬 **Marp完全対応**(公式テーマ・ディレクティブ・数式)
|
|
13
|
+
- 🔄 ファイル更新時に自動リロード(WebSocket)
|
|
14
|
+
- 🎨 シンタックスハイライト(highlight.js)
|
|
15
|
+
- 📊 Mermaid図のレンダリング
|
|
16
|
+
- 🌙 ダーク/ライトテーマ切り替え
|
|
17
|
+
- ✏️ インラインエディタ(Cmd+E)
|
|
18
|
+
- ✅ タスクリスト(チェックボックス)対応
|
|
19
|
+
- 📥 PDF出力(Cmd+P)
|
|
20
|
+
- 🎬 動画/音声ストリーミング再生(Range Request対応)
|
|
21
|
+
- 📤 ファイルアップロード(ドラッグ&ドロップ)
|
|
22
|
+
- 🔒 セキュリティ強化(パストラバーサル防止)
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# グローバルインストール(推奨)
|
|
28
|
+
npm install -g mdv-live
|
|
29
|
+
|
|
30
|
+
# または npx で直接実行
|
|
31
|
+
npx mdv-live
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Usage
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
# カレントディレクトリを表示
|
|
38
|
+
mdv
|
|
39
|
+
|
|
40
|
+
# 特定のディレクトリを表示
|
|
41
|
+
mdv ./docs
|
|
42
|
+
|
|
43
|
+
# 特定のファイルを開く
|
|
44
|
+
mdv README.md
|
|
45
|
+
|
|
46
|
+
# ポート指定(デフォルト: 8642)
|
|
47
|
+
mdv -p 9000
|
|
48
|
+
|
|
49
|
+
# ブラウザを自動で開かない
|
|
50
|
+
mdv --no-browser
|
|
51
|
+
|
|
52
|
+
# 起動中のサーバー一覧
|
|
53
|
+
mdv -l
|
|
54
|
+
|
|
55
|
+
# サーバーを停止(PID指定)
|
|
56
|
+
mdv -k 12345
|
|
57
|
+
|
|
58
|
+
# 全サーバーを停止
|
|
59
|
+
mdv -k -a
|
|
60
|
+
|
|
61
|
+
# PDFに変換
|
|
62
|
+
mdv --pdf slide.md
|
|
63
|
+
mdv --pdf slide.md -o output.pdf
|
|
64
|
+
|
|
65
|
+
# バージョン表示
|
|
66
|
+
mdv -v
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### ポート自動増分
|
|
70
|
+
|
|
71
|
+
ポートが使用中の場合、自動的に次のポート番号を試します。
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
$ mdv -p 8642
|
|
75
|
+
ポート 8642 は使用中です。8643 を試します...
|
|
76
|
+
MDV server running at http://localhost:8643
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## macOS Finder Integration
|
|
80
|
+
|
|
81
|
+
macOSで`.md`ファイルをダブルクリックしてMDVで開けるようにする設定です。
|
|
82
|
+
|
|
83
|
+
### セットアップスクリプトを使用(推奨)
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
# mdvがインストールされていることを確認
|
|
87
|
+
which mdv
|
|
88
|
+
|
|
89
|
+
# セットアップスクリプトを実行
|
|
90
|
+
curl -fsSL https://raw.githubusercontent.com/panhouse/mdv/main/scripts/setup-macos-app.sh | bash
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
または、リポジトリをクローンしている場合:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
npm run setup-macos
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### デフォルトアプリに設定
|
|
100
|
+
|
|
101
|
+
1. Finderで任意の`.md`ファイルを右クリック
|
|
102
|
+
2. 「情報を見る」を選択
|
|
103
|
+
3. 「このアプリケーションで開く」で「MDV」を選択
|
|
104
|
+
4. 「すべてを変更...」をクリック
|
|
105
|
+
|
|
106
|
+
## Marp Support
|
|
107
|
+
|
|
108
|
+
`marp: true` フロントマターを含むMarkdownファイルは自動的にMarpスライドとしてレンダリングされます。
|
|
109
|
+
|
|
110
|
+
```markdown
|
|
111
|
+
---
|
|
112
|
+
marp: true
|
|
113
|
+
theme: default
|
|
114
|
+
paginate: true
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
# スライドタイトル
|
|
118
|
+
|
|
119
|
+
内容...
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
# 次のスライド
|
|
124
|
+
|
|
125
|
+
- 箇条書き
|
|
126
|
+
- 数式: $E = mc^2$
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### サポートされるMarp機能
|
|
130
|
+
|
|
131
|
+
- **テーマ**: default, gaia, uncover
|
|
132
|
+
- **ディレクティブ**: paginate, header, footer, backgroundColor, etc.
|
|
133
|
+
- **画像構文**: `![bg]`, `![w:100px]`, `![bg left]`
|
|
134
|
+
- **数式**: KaTeX対応(インライン `$...$`、ブロック `$$...$$`)
|
|
135
|
+
|
|
136
|
+
## Keyboard Shortcuts
|
|
137
|
+
|
|
138
|
+
| ショートカット | 機能 |
|
|
139
|
+
|---------------|------|
|
|
140
|
+
| Cmd/Ctrl + B | サイドバー表示切替 |
|
|
141
|
+
| Cmd/Ctrl + E | 編集モード切替 |
|
|
142
|
+
| Cmd/Ctrl + S | 保存(編集モード時) |
|
|
143
|
+
| Cmd/Ctrl + P | PDF出力 |
|
|
144
|
+
| Cmd/Ctrl + W | タブを閉じる |
|
|
145
|
+
| ← / → | スライド移動(Marp時) |
|
|
146
|
+
| F2 | ファイル名変更 |
|
|
147
|
+
| Delete | ファイル削除 |
|
|
148
|
+
|
|
149
|
+
## API Endpoints
|
|
150
|
+
|
|
151
|
+
| Endpoint | Method | Description |
|
|
152
|
+
|----------|--------|-------------|
|
|
153
|
+
| `/api/file` | GET | ファイル内容取得 |
|
|
154
|
+
| `/api/file` | POST | ファイル保存 |
|
|
155
|
+
| `/api/file` | DELETE | ファイル/ディレクトリ削除 |
|
|
156
|
+
| `/api/tree` | GET | ファイルツリー取得 |
|
|
157
|
+
| `/api/tree/expand` | GET | ディレクトリ展開(遅延読み込み) |
|
|
158
|
+
| `/api/mkdir` | POST | ディレクトリ作成 |
|
|
159
|
+
| `/api/move` | POST | ファイル移動/リネーム |
|
|
160
|
+
| `/api/download` | GET | ファイルダウンロード |
|
|
161
|
+
| `/api/upload` | POST | ファイルアップロード |
|
|
162
|
+
| `/api/info` | GET | サーバー情報 |
|
|
163
|
+
|
|
164
|
+
## Tech Stack
|
|
165
|
+
|
|
166
|
+
- **Backend**: Node.js + Express
|
|
167
|
+
- **Frontend**: Vanilla JavaScript
|
|
168
|
+
- **Markdown**: markdown-it + markdown-it-task-lists
|
|
169
|
+
- **Marp**: @marp-team/marp-core
|
|
170
|
+
- **WebSocket**: ws
|
|
171
|
+
- **File Watching**: chokidar
|
|
172
|
+
- **Syntax Highlight**: highlight.js
|
|
173
|
+
|
|
174
|
+
## Development
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
# Clone repository
|
|
178
|
+
git clone https://github.com/panhouse/mdv.git
|
|
179
|
+
cd mdv
|
|
180
|
+
|
|
181
|
+
# Install dependencies
|
|
182
|
+
npm install
|
|
183
|
+
|
|
184
|
+
# Start development server
|
|
185
|
+
npm run dev
|
|
186
|
+
|
|
187
|
+
# Run tests
|
|
188
|
+
npm test
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Project Structure
|
|
192
|
+
|
|
193
|
+
```
|
|
194
|
+
mdv/
|
|
195
|
+
├── bin/mdv.js # CLI entry point
|
|
196
|
+
├── src/
|
|
197
|
+
│ ├── server.js # Express server setup
|
|
198
|
+
│ ├── watcher.js # File watching (chokidar)
|
|
199
|
+
│ ├── api/
|
|
200
|
+
│ │ ├── file.js # File operations API
|
|
201
|
+
│ │ ├── tree.js # File tree API
|
|
202
|
+
│ │ └── upload.js # Upload API
|
|
203
|
+
│ ├── rendering/
|
|
204
|
+
│ │ ├── index.js # Rendering entry
|
|
205
|
+
│ │ ├── markdown.js # Markdown rendering
|
|
206
|
+
│ │ └── marp.js # Marp rendering
|
|
207
|
+
│ ├── utils/
|
|
208
|
+
│ │ ├── fileTypes.js # File type detection
|
|
209
|
+
│ │ └── path.js # Path security utilities
|
|
210
|
+
│ └── static/ # Frontend files
|
|
211
|
+
│ ├── index.html
|
|
212
|
+
│ ├── app.js
|
|
213
|
+
│ └── styles.css
|
|
214
|
+
├── scripts/
|
|
215
|
+
│ └── setup-macos-app.sh # macOS app setup
|
|
216
|
+
└── tests/ # Test files
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Requirements
|
|
220
|
+
|
|
221
|
+
- Node.js 18+
|
|
222
|
+
|
|
223
|
+
## Migration from Python version
|
|
224
|
+
|
|
225
|
+
以前のPython版(`pip install mdv-live`)からの移行:
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
# Python版をアンインストール
|
|
229
|
+
pip uninstall mdv-live
|
|
230
|
+
|
|
231
|
+
# Node.js版をインストール
|
|
232
|
+
npm install -g mdv-live
|
|
233
|
+
|
|
234
|
+
# macOSアプリを再設定(必要な場合)
|
|
235
|
+
npm run setup-macos
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## License
|
|
239
|
+
|
|
240
|
+
MIT
|
package/bin/mdv.js
ADDED
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* MDV CLI - Markdown Viewer with Marp support
|
|
5
|
+
* Compatible with the original Python mdv-live CLI
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createMdvServer } from '../src/server.js';
|
|
9
|
+
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';
|
|
14
|
+
import open from 'open';
|
|
15
|
+
|
|
16
|
+
const DEFAULT_PORT = 8642;
|
|
17
|
+
|
|
18
|
+
// Parse command line arguments
|
|
19
|
+
const options = {
|
|
20
|
+
port: {
|
|
21
|
+
type: 'string',
|
|
22
|
+
short: 'p',
|
|
23
|
+
},
|
|
24
|
+
'no-browser': {
|
|
25
|
+
type: 'boolean',
|
|
26
|
+
default: false
|
|
27
|
+
},
|
|
28
|
+
list: {
|
|
29
|
+
type: 'boolean',
|
|
30
|
+
short: 'l',
|
|
31
|
+
default: false
|
|
32
|
+
},
|
|
33
|
+
kill: {
|
|
34
|
+
type: 'boolean',
|
|
35
|
+
short: 'k',
|
|
36
|
+
default: false
|
|
37
|
+
},
|
|
38
|
+
all: {
|
|
39
|
+
type: 'boolean',
|
|
40
|
+
short: 'a',
|
|
41
|
+
default: false
|
|
42
|
+
},
|
|
43
|
+
pdf: {
|
|
44
|
+
type: 'boolean',
|
|
45
|
+
default: false
|
|
46
|
+
},
|
|
47
|
+
output: {
|
|
48
|
+
type: 'string',
|
|
49
|
+
short: 'o',
|
|
50
|
+
},
|
|
51
|
+
help: {
|
|
52
|
+
type: 'boolean',
|
|
53
|
+
short: 'h',
|
|
54
|
+
default: false
|
|
55
|
+
},
|
|
56
|
+
version: {
|
|
57
|
+
type: 'boolean',
|
|
58
|
+
short: 'v',
|
|
59
|
+
default: false
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
function showHelp() {
|
|
64
|
+
console.log(`
|
|
65
|
+
MDV - Markdown Viewer with file tree + live preview + Marp support
|
|
66
|
+
|
|
67
|
+
Usage: mdv [options] [path]
|
|
68
|
+
|
|
69
|
+
Arguments:
|
|
70
|
+
path Directory or file path to view (default: current directory)
|
|
71
|
+
|
|
72
|
+
Server Options:
|
|
73
|
+
-p, --port <n> Server port (default: ${DEFAULT_PORT})
|
|
74
|
+
--no-browser Don't open browser automatically
|
|
75
|
+
|
|
76
|
+
Server Management:
|
|
77
|
+
-l, --list List running MDV servers
|
|
78
|
+
-k, --kill [PID] Stop server (-k -a for all, -k <PID> for specific)
|
|
79
|
+
-a, --all Use with -k to stop all servers
|
|
80
|
+
|
|
81
|
+
PDF Conversion:
|
|
82
|
+
--pdf Convert markdown file to PDF
|
|
83
|
+
-o, --output <file> Output PDF file path
|
|
84
|
+
|
|
85
|
+
Other:
|
|
86
|
+
-h, --help Show this help message
|
|
87
|
+
-v, --version Show version number
|
|
88
|
+
|
|
89
|
+
Examples:
|
|
90
|
+
mdv Start viewer in current directory
|
|
91
|
+
mdv /path/to/dir Start viewer in specified directory
|
|
92
|
+
mdv README.md Open specific file
|
|
93
|
+
mdv --pdf README.md Convert markdown to PDF
|
|
94
|
+
mdv -p 3000 Start on port 3000
|
|
95
|
+
mdv -l List running servers
|
|
96
|
+
mdv -k -a Stop all servers
|
|
97
|
+
`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Get running MDV server processes
|
|
102
|
+
*/
|
|
103
|
+
function getMdvProcesses() {
|
|
104
|
+
try {
|
|
105
|
+
const result = execSync('lsof -i -P -n 2>/dev/null || true', { encoding: 'utf-8' });
|
|
106
|
+
const processes = [];
|
|
107
|
+
|
|
108
|
+
for (const line of result.split('\n')) {
|
|
109
|
+
if (!line.includes('node') || !line.includes('LISTEN')) continue;
|
|
110
|
+
|
|
111
|
+
const parts = line.split(/\s+/);
|
|
112
|
+
if (parts.length < 9) continue;
|
|
113
|
+
|
|
114
|
+
const pid = parts[1];
|
|
115
|
+
|
|
116
|
+
// Check if this is an MDV process
|
|
117
|
+
try {
|
|
118
|
+
const cmdResult = execSync(`ps -p ${pid} -o command= 2>/dev/null || true`, { encoding: 'utf-8' }).trim();
|
|
119
|
+
if (!cmdResult.toLowerCase().includes('mdv')) continue;
|
|
120
|
+
|
|
121
|
+
// Extract port
|
|
122
|
+
const portInfo = parts[8] || '';
|
|
123
|
+
let port = '';
|
|
124
|
+
if (portInfo.includes(':')) {
|
|
125
|
+
port = portInfo.split(':').pop().split('->')[0];
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const displayCmd = cmdResult.length > 60 ? cmdResult.slice(0, 60) + '...' : cmdResult;
|
|
129
|
+
processes.push({ pid, port, command: displayCmd });
|
|
130
|
+
} catch {
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return processes;
|
|
136
|
+
} catch {
|
|
137
|
+
return [];
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* List running MDV servers
|
|
143
|
+
*/
|
|
144
|
+
function listServers() {
|
|
145
|
+
const processes = getMdvProcesses();
|
|
146
|
+
|
|
147
|
+
if (processes.length === 0) {
|
|
148
|
+
console.log('稼働中のMDVサーバーはありません');
|
|
149
|
+
return 0;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
console.log(`稼働中のMDVサーバー: ${processes.length}件`);
|
|
153
|
+
console.log('-'.repeat(60));
|
|
154
|
+
console.log(`${'PID'.padEnd(8)} ${'Port'.padEnd(8)} Command`);
|
|
155
|
+
console.log('-'.repeat(60));
|
|
156
|
+
|
|
157
|
+
for (const proc of processes) {
|
|
158
|
+
console.log(`${proc.pid.padEnd(8)} ${proc.port.padEnd(8)} ${proc.command}`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
console.log('-'.repeat(60));
|
|
162
|
+
console.log('\n停止: mdv -k -a (全停止) / mdv -k <PID> (個別停止)');
|
|
163
|
+
return 0;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Kill MDV server(s)
|
|
168
|
+
*/
|
|
169
|
+
function killServers(target, killAll) {
|
|
170
|
+
if (target) {
|
|
171
|
+
// Kill specific PID
|
|
172
|
+
try {
|
|
173
|
+
execSync(`kill ${target}`, { encoding: 'utf-8' });
|
|
174
|
+
console.log(`PID ${target} を停止しました`);
|
|
175
|
+
return 0;
|
|
176
|
+
} catch {
|
|
177
|
+
console.log(`PID ${target} の停止に失敗しました`);
|
|
178
|
+
return 1;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (!killAll) {
|
|
183
|
+
console.log('全サーバーを停止するには -a オプションが必要です');
|
|
184
|
+
console.log(' mdv -k -a 全サーバーを停止');
|
|
185
|
+
console.log(' mdv -k <PID> 特定のサーバーを停止');
|
|
186
|
+
return 1;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Kill all servers
|
|
190
|
+
const processes = getMdvProcesses();
|
|
191
|
+
|
|
192
|
+
if (processes.length === 0) {
|
|
193
|
+
console.log('稼働中のMDVサーバーはありません');
|
|
194
|
+
return 0;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
console.log(`${processes.length}件のMDVサーバーを停止します...`);
|
|
198
|
+
|
|
199
|
+
let killed = 0;
|
|
200
|
+
for (const proc of processes) {
|
|
201
|
+
try {
|
|
202
|
+
execSync(`kill ${proc.pid}`, { encoding: 'utf-8' });
|
|
203
|
+
console.log(` PID ${proc.pid} (port ${proc.port}) を停止`);
|
|
204
|
+
killed++;
|
|
205
|
+
} catch {
|
|
206
|
+
console.log(` PID ${proc.pid} の停止に失敗`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
console.log(`\n完了: ${killed}/${processes.length} 件を停止しました`);
|
|
211
|
+
return killed === processes.length ? 0 : 1;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Convert markdown to PDF using marp-cli
|
|
216
|
+
*/
|
|
217
|
+
async function convertToPdf(inputPath, outputPath) {
|
|
218
|
+
const resolved = path.resolve(inputPath);
|
|
219
|
+
|
|
220
|
+
try {
|
|
221
|
+
await fs.access(resolved);
|
|
222
|
+
} catch {
|
|
223
|
+
console.error(`Error: File not found: ${inputPath}`);
|
|
224
|
+
return 1;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const ext = path.extname(resolved).toLowerCase();
|
|
228
|
+
if (!['.md', '.markdown'].includes(ext)) {
|
|
229
|
+
console.error(`Error: Not a markdown file: ${inputPath}`);
|
|
230
|
+
return 1;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const defaultOutput = resolved.replace(/\.(md|markdown)$/i, '.pdf');
|
|
234
|
+
const finalOutput = outputPath ? path.resolve(outputPath) : defaultOutput;
|
|
235
|
+
|
|
236
|
+
console.log(`Converting ${inputPath} to PDF...`);
|
|
237
|
+
|
|
238
|
+
try {
|
|
239
|
+
// Use marp-cli for PDF conversion (supports Marp slides)
|
|
240
|
+
execSync(`npx @marp-team/marp-cli "${resolved}" --pdf -o "${finalOutput}"`, {
|
|
241
|
+
encoding: 'utf-8',
|
|
242
|
+
stdio: 'inherit'
|
|
243
|
+
});
|
|
244
|
+
console.log(`PDF saved: ${finalOutput}`);
|
|
245
|
+
return 0;
|
|
246
|
+
} catch (err) {
|
|
247
|
+
console.error('Error: PDF conversion failed');
|
|
248
|
+
console.error('Make sure Node.js and npx are installed');
|
|
249
|
+
return 1;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Check if a port is available
|
|
255
|
+
*/
|
|
256
|
+
async function isPortAvailable(port) {
|
|
257
|
+
return new Promise((resolve) => {
|
|
258
|
+
const server = createNetServer();
|
|
259
|
+
server.once('error', () => resolve(false));
|
|
260
|
+
server.once('listening', () => {
|
|
261
|
+
server.close(() => resolve(true));
|
|
262
|
+
});
|
|
263
|
+
server.listen(port);
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Find an available port starting from the given port
|
|
269
|
+
*/
|
|
270
|
+
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) {
|
|
275
|
+
return port;
|
|
276
|
+
}
|
|
277
|
+
if (i > 0) {
|
|
278
|
+
console.log(`ポート ${port - 1} は使用中です。${port} を試します...`);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Start MDV server with auto port increment
|
|
286
|
+
*/
|
|
287
|
+
async function startViewer(targetPath, startPort, openBrowser) {
|
|
288
|
+
let rootDir = process.cwd();
|
|
289
|
+
let initialFile = null;
|
|
290
|
+
|
|
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);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Find available port
|
|
308
|
+
const port = await findAvailablePort(startPort);
|
|
309
|
+
if (!port) {
|
|
310
|
+
console.error('Error: 利用可能なポートが見つかりませんでした');
|
|
311
|
+
process.exit(1);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (port !== startPort) {
|
|
315
|
+
console.log(`ポート ${startPort} は使用中のため、${port} で起動します`);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Create and start server
|
|
319
|
+
const mdv = createMdvServer({ rootDir, port });
|
|
320
|
+
await mdv.start();
|
|
321
|
+
|
|
322
|
+
const url = initialFile
|
|
323
|
+
? `http://localhost:${port}?file=${encodeURIComponent(initialFile)}`
|
|
324
|
+
: `http://localhost:${port}`;
|
|
325
|
+
|
|
326
|
+
console.log(`
|
|
327
|
+
MDV - Markdown Viewer with Marp support
|
|
328
|
+
|
|
329
|
+
Server running at: ${url}
|
|
330
|
+
Root directory: ${rootDir}
|
|
331
|
+
|
|
332
|
+
Press Ctrl+C to stop
|
|
333
|
+
`);
|
|
334
|
+
|
|
335
|
+
if (openBrowser) {
|
|
336
|
+
await open(url);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
async function main() {
|
|
341
|
+
let args;
|
|
342
|
+
try {
|
|
343
|
+
args = parseArgs({
|
|
344
|
+
options,
|
|
345
|
+
allowPositionals: true,
|
|
346
|
+
strict: false
|
|
347
|
+
});
|
|
348
|
+
} catch (err) {
|
|
349
|
+
console.error('Error parsing arguments:', err.message);
|
|
350
|
+
showHelp();
|
|
351
|
+
process.exit(1);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const { values, positionals } = args;
|
|
355
|
+
|
|
356
|
+
// Help
|
|
357
|
+
if (values.help) {
|
|
358
|
+
showHelp();
|
|
359
|
+
process.exit(0);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Version
|
|
363
|
+
if (values.version) {
|
|
364
|
+
console.log('mdv v0.3.0');
|
|
365
|
+
process.exit(0);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// List servers
|
|
369
|
+
if (values.list) {
|
|
370
|
+
process.exit(listServers());
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Kill servers
|
|
374
|
+
if (values.kill) {
|
|
375
|
+
const pid = positionals[0] || null;
|
|
376
|
+
process.exit(killServers(pid, values.all));
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// PDF conversion
|
|
380
|
+
if (values.pdf) {
|
|
381
|
+
const inputPath = positionals[0];
|
|
382
|
+
if (!inputPath) {
|
|
383
|
+
console.error('Error: --pdf requires a markdown file path');
|
|
384
|
+
process.exit(1);
|
|
385
|
+
}
|
|
386
|
+
process.exit(await convertToPdf(inputPath, values.output));
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Default: start viewer
|
|
390
|
+
const targetPath = positionals[0] || '.';
|
|
391
|
+
const port = parseInt(values.port, 10) || DEFAULT_PORT;
|
|
392
|
+
const openBrowser = !values['no-browser'];
|
|
393
|
+
|
|
394
|
+
await startViewer(targetPath, port, openBrowser);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
main().catch(err => {
|
|
398
|
+
console.error('Error:', err.message);
|
|
399
|
+
process.exit(1);
|
|
400
|
+
});
|