vimd 0.2.0 → 0.2.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/README.md CHANGED
@@ -107,6 +107,7 @@ vimd config
107
107
  | `vimd build <file>` | 静的HTMLを生成 |
108
108
  | `vimd theme` | テーマを対話的に変更 |
109
109
  | `vimd config` | 設定を対話的に編集 |
110
+ | `vimd kill` | 実行中のセッションを終了 |
110
111
 
111
112
  ### オプション
112
113
 
@@ -121,6 +122,10 @@ vimd dev draft.md --pandoc # pandocパーサーを使用
121
122
  vimd build draft.md -o output.html # 出力先指定
122
123
  vimd build draft.md --fast # markdown-itで高速ビルド
123
124
  vimd build draft.md --theme dark # テーマ指定
125
+
126
+ # kill コマンド
127
+ vimd kill # 全セッションを終了
128
+ vimd kill --port 38080 # 特定ポートのセッションを終了
124
129
  ```
125
130
 
126
131
  ---
@@ -132,7 +137,7 @@ vimd build draft.md --theme dark # テーマ指定
132
137
  ```javascript
133
138
  export default {
134
139
  theme: 'github',
135
- port: 8080,
140
+ port: 38080, // デフォルト: 38080(v0.2.1で変更)
136
141
  open: true,
137
142
  devParser: 'markdown-it', // dev用パーサー(デフォルト: markdown-it)
138
143
  buildParser: 'pandoc', // build用パーサー(デフォルト: pandoc)
@@ -146,7 +151,7 @@ export default {
146
151
  | `markdown-it` | 高速、pandoc不要 | 開発時のプレビュー |
147
152
  | `pandoc` | 高品質、多機能 | 最終出力の生成 |
148
153
 
149
- 詳細な設定オプションは [docs/api.md](docs/api.md) を参照してください。
154
+ 詳細な設定オプションは [docs/ja/api.md](docs/ja/api.md) を参照してください。
150
155
 
151
156
  ---
152
157
 
@@ -165,11 +170,12 @@ export default {
165
170
 
166
171
  ## ドキュメント
167
172
 
168
- - [開発ガイド](docs/development.md) - 開発環境構築
169
- - [アーキテクチャ](docs/architecture.md) - プロジェクト構造
170
- - [APIリファレンス](docs/api.md) - 詳細なオプション
171
- - [テスト](docs/testing.md) - テスト構成
172
- - [トラブルシューティング](docs/troubleshooting.md) - よくある問題
173
+ - [開発ガイド](docs/ja/development.md) - 開発環境構築
174
+ - [アーキテクチャ](docs/ja/architecture.md) - プロジェクト構造
175
+ - [APIリファレンス](docs/ja/api.md) - 詳細なオプション
176
+ - [テスト](docs/ja/testing.md) - テスト構成
177
+ - [トラブルシューティング](docs/ja/troubleshooting.md) - よくある問題
178
+ - [v0.2.0 リリースノート](docs/ja/releases/v0.2.0.md) - Dual Parser System
173
179
 
174
180
  ---
175
181
 
@@ -1 +1 @@
1
- {"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/dev.ts"],"names":[],"mappings":"AAaA,UAAU,UAAU;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,wBAAsB,UAAU,CAC9B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,UAAU,GAClB,OAAO,CAAC,IAAI,CAAC,CAuJf"}
1
+ {"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/dev.ts"],"names":[],"mappings":"AAaA,UAAU,UAAU;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,wBAAsB,UAAU,CAC9B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,UAAU,GAClB,OAAO,CAAC,IAAI,CAAC,CA6Jf"}
@@ -87,11 +87,16 @@ export async function devCommand(filePath, options) {
87
87
  open: config.open,
88
88
  root: sourceDir,
89
89
  });
90
- await server.start(htmlPath);
91
- // 12. Save session
90
+ const startResult = await server.start(htmlPath);
91
+ // Update port if live-server used a different one
92
+ const actualPort = startResult.actualPort;
93
+ if (startResult.portChanged) {
94
+ port = actualPort;
95
+ }
96
+ // 12. Save session with actual port
92
97
  await SessionManager.saveSession({
93
98
  pid: process.pid,
94
- port: port,
99
+ port: actualPort,
95
100
  htmlPath: htmlPath,
96
101
  sourcePath: absolutePath,
97
102
  startedAt: new Date().toISOString(),
@@ -128,8 +133,8 @@ export async function devCommand(filePath, options) {
128
133
  catch {
129
134
  // Ignore errors when removing file
130
135
  }
131
- // Remove session from registry
132
- await SessionManager.removeSession(port);
136
+ // Remove session from registry (use actual port)
137
+ await SessionManager.removeSession(actualPort);
133
138
  Logger.info('Cleanup complete');
134
139
  });
135
140
  }
@@ -0,0 +1,7 @@
1
+ interface KillOptions {
2
+ all?: boolean;
3
+ port?: string;
4
+ }
5
+ export declare function killCommand(options: KillOptions): Promise<void>;
6
+ export {};
7
+ //# sourceMappingURL=kill.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kill.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/kill.ts"],"names":[],"mappings":"AAQA,UAAU,WAAW;IACnB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAsDD,wBAAsB,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAyErE"}
@@ -0,0 +1,110 @@
1
+ // src/cli/commands/kill.ts
2
+ import { SessionManager } from '../../utils/session-manager.js';
3
+ import { Logger } from '../../utils/logger.js';
4
+ import { exec } from 'child_process';
5
+ import { promisify } from 'util';
6
+ const execAsync = promisify(exec);
7
+ /**
8
+ * Check if a PID belongs to a vimd/node process
9
+ */
10
+ async function isVimdProcess(pid) {
11
+ try {
12
+ const { stdout } = await execAsync(`ps -p ${pid} -o comm=`);
13
+ const processName = stdout.trim().toLowerCase();
14
+ return processName.includes('node') || processName.includes('vimd');
15
+ }
16
+ catch {
17
+ // Process doesn't exist
18
+ return false;
19
+ }
20
+ }
21
+ /**
22
+ * Kill a single session with validation
23
+ */
24
+ async function killSession(session) {
25
+ const result = {
26
+ port: session.port,
27
+ pid: session.pid,
28
+ killed: false,
29
+ htmlRemoved: false,
30
+ };
31
+ // Check if process is alive
32
+ if (!SessionManager.isProcessAlive(session.pid)) {
33
+ result.reason = 'Process already dead';
34
+ return result;
35
+ }
36
+ // Validate that this is a vimd process (PID safety check)
37
+ const isVimd = await isVimdProcess(session.pid);
38
+ if (!isVimd) {
39
+ result.reason = 'PID reused by another process';
40
+ return result;
41
+ }
42
+ // Kill the process
43
+ result.killed = await SessionManager.killProcess(session.pid);
44
+ return result;
45
+ }
46
+ export async function killCommand(options) {
47
+ try {
48
+ // Clean up dead sessions first
49
+ const deadCleaned = await SessionManager.cleanDeadSessions();
50
+ if (deadCleaned > 0) {
51
+ Logger.info(`Cleaned ${deadCleaned} dead session(s)`);
52
+ }
53
+ // Load all sessions
54
+ const sessions = await SessionManager.loadSessions();
55
+ const sessionList = Object.values(sessions);
56
+ if (sessionList.length === 0) {
57
+ Logger.info('No active sessions found');
58
+ return;
59
+ }
60
+ // Filter sessions if --port is specified
61
+ let targetSessions;
62
+ if (options.port) {
63
+ const port = parseInt(options.port, 10);
64
+ const session = sessions[port.toString()];
65
+ if (!session) {
66
+ Logger.warn(`No session found on port ${port}`);
67
+ return;
68
+ }
69
+ targetSessions = [session];
70
+ }
71
+ else {
72
+ // Kill all sessions (default behavior, same as --all)
73
+ targetSessions = sessionList;
74
+ }
75
+ Logger.info(`Found ${targetSessions.length} active session(s)`);
76
+ // Kill each session
77
+ const results = [];
78
+ let htmlRemovedCount = 0;
79
+ for (const session of targetSessions) {
80
+ const result = await killSession(session);
81
+ // Remove HTML file regardless of kill result
82
+ const cleanupResult = await SessionManager.cleanupSessionOnPort(session.port);
83
+ result.htmlRemoved = cleanupResult.htmlRemoved;
84
+ if (result.htmlRemoved) {
85
+ htmlRemovedCount++;
86
+ }
87
+ results.push(result);
88
+ // Log result
89
+ if (result.killed) {
90
+ Logger.success(`Killed session on port ${result.port} (PID: ${result.pid})`);
91
+ }
92
+ else if (result.reason) {
93
+ Logger.info(`Port ${result.port}: ${result.reason}`);
94
+ }
95
+ }
96
+ // Summary
97
+ const killedCount = results.filter((r) => r.killed).length;
98
+ if (killedCount > 0 || htmlRemovedCount > 0) {
99
+ if (htmlRemovedCount > 0) {
100
+ Logger.success(`Removed ${htmlRemovedCount} preview file(s)`);
101
+ }
102
+ Logger.success('All sessions terminated');
103
+ }
104
+ }
105
+ catch (error) {
106
+ const errorMessage = error instanceof Error ? error.message : String(error);
107
+ Logger.error(`Failed to kill sessions: ${errorMessage}`);
108
+ process.exit(1);
109
+ }
110
+ }
package/dist/cli/index.js CHANGED
@@ -5,6 +5,7 @@ import { devCommand } from './commands/dev.js';
5
5
  import { buildCommand } from './commands/build.js';
6
6
  import { themeCommand } from './commands/theme.js';
7
7
  import { configCommand } from './commands/config.js';
8
+ import { killCommand } from './commands/kill.js';
8
9
  const require = createRequire(import.meta.url);
9
10
  const packageJson = require('../../package.json');
10
11
  const program = new Command();
@@ -16,7 +17,7 @@ program
16
17
  program
17
18
  .command('dev <file>')
18
19
  .description('Start live preview server')
19
- .option('-p, --port <port>', 'Port number', '8080')
20
+ .option('-p, --port <port>', 'Port number')
20
21
  .option('-t, --theme <theme>', 'Theme name')
21
22
  .option('--no-open', 'Do not open browser automatically')
22
23
  .option('--pandoc', 'Use pandoc parser instead of markdown-it')
@@ -40,4 +41,11 @@ program
40
41
  .description('Edit configuration interactively')
41
42
  .option('-l, --list', 'List current configuration')
42
43
  .action(configCommand);
44
+ // vimd kill
45
+ program
46
+ .command('kill')
47
+ .description('Kill vimd dev sessions')
48
+ .option('--all', 'Kill all sessions (default)')
49
+ .option('--port <port>', 'Kill session on specific port')
50
+ .action(killCommand);
43
51
  program.parse(process.argv);
@@ -1,6 +1,6 @@
1
1
  export const DEFAULT_CONFIG = {
2
2
  theme: 'github',
3
- port: 8080,
3
+ port: 38080,
4
4
  host: 'localhost',
5
5
  open: true,
6
6
  pandoc: {
@@ -1,11 +1,18 @@
1
1
  import { ServerConfig } from '../config/types.js';
2
+ export interface ServerStartResult {
3
+ actualPort: number;
4
+ requestedPort: number;
5
+ portChanged: boolean;
6
+ }
2
7
  export declare class LiveServer {
3
8
  private config;
4
9
  private running;
10
+ private actualPort;
5
11
  constructor(config: ServerConfig);
6
- start(htmlPath: string): Promise<void>;
12
+ start(htmlPath: string): Promise<ServerStartResult>;
7
13
  stop(): Promise<void>;
8
14
  openBrowser(url: string): Promise<void>;
9
15
  getURL(): string;
16
+ getActualPort(): number;
10
17
  }
11
18
  //# sourceMappingURL=server.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/core/server.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAKlD,qBAAa,UAAU;IAGT,OAAO,CAAC,MAAM;IAF1B,OAAO,CAAC,OAAO,CAAS;gBAEJ,MAAM,EAAE,YAAY;IAElC,KAAK,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA+BtC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAUrB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAS7C,MAAM,IAAI,MAAM;CAGjB"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/core/server.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAMlD,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,qBAAa,UAAU;IAIT,OAAO,CAAC,MAAM;IAH1B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,UAAU,CAAS;gBAEP,MAAM,EAAE,YAAY;IAIlC,KAAK,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;IA8DnD,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAUrB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAS7C,MAAM,IAAI,MAAM;IAIhB,aAAa,IAAI,MAAM;CAGxB"}
@@ -7,6 +7,7 @@ export class LiveServer {
7
7
  constructor(config) {
8
8
  this.config = config;
9
9
  this.running = false;
10
+ this.actualPort = config.port;
10
11
  }
11
12
  async start(htmlPath) {
12
13
  const root = path.dirname(htmlPath);
@@ -22,13 +23,38 @@ export class LiveServer {
22
23
  watch: [root], // explicitly watch the root directory
23
24
  };
24
25
  try {
25
- liveServer.start(params);
26
+ const server = liveServer.start(params);
27
+ // Wait for server to start and get actual port
28
+ const actualPort = await new Promise((resolve, reject) => {
29
+ const timeout = setTimeout(() => {
30
+ reject(new Error('Server start timeout'));
31
+ }, 10000);
32
+ server.addListener('listening', () => {
33
+ clearTimeout(timeout);
34
+ const address = server.address();
35
+ resolve(address.port);
36
+ });
37
+ server.addListener('error', (err) => {
38
+ clearTimeout(timeout);
39
+ reject(err);
40
+ });
41
+ });
42
+ this.actualPort = actualPort;
26
43
  this.running = true;
27
- const url = `http://${this.config.host}:${this.config.port}`;
44
+ const portChanged = actualPort !== this.config.port;
45
+ const url = `http://${this.config.host}:${actualPort}`;
46
+ if (portChanged) {
47
+ Logger.warn(`Port ${this.config.port} was unavailable, using port ${actualPort}`);
48
+ }
28
49
  Logger.success(`Server started at ${url}`);
29
50
  if (this.config.open) {
30
51
  await this.openBrowser(url);
31
52
  }
53
+ return {
54
+ actualPort,
55
+ requestedPort: this.config.port,
56
+ portChanged,
57
+ };
32
58
  }
33
59
  catch (error) {
34
60
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -53,6 +79,9 @@ export class LiveServer {
53
79
  }
54
80
  }
55
81
  getURL() {
56
- return `http://${this.config.host}:${this.config.port}`;
82
+ return `http://${this.config.host}:${this.actualPort}`;
83
+ }
84
+ getActualPort() {
85
+ return this.actualPort;
57
86
  }
58
87
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vimd",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Real-time Markdown preview tool with pandoc (view markdown)",
5
5
  "type": "module",
6
6
  "keywords": [