vimd 0.1.6 → 0.1.8
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/dist/cli/commands/dev.d.ts.map +1 -1
- package/dist/cli/commands/dev.js +42 -10
- package/dist/themes/styles/dark.css +505 -35
- package/dist/utils/session-manager.d.ts +65 -0
- package/dist/utils/session-manager.d.ts.map +1 -0
- package/dist/utils/session-manager.js +168 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/dev.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/dev.ts"],"names":[],"mappings":"AAYA,UAAU,UAAU;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,wBAAsB,UAAU,CAC9B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,UAAU,GAClB,OAAO,CAAC,IAAI,CAAC,CA+If"}
|
package/dist/cli/commands/dev.js
CHANGED
|
@@ -6,6 +6,7 @@ import { LiveServer } from '../../core/server.js';
|
|
|
6
6
|
import { PandocDetector } from '../../core/pandoc-detector.js';
|
|
7
7
|
import { Logger } from '../../utils/logger.js';
|
|
8
8
|
import { ProcessManager } from '../../utils/process-manager.js';
|
|
9
|
+
import { SessionManager } from '../../utils/session-manager.js';
|
|
9
10
|
import * as path from 'path';
|
|
10
11
|
import fs from 'fs-extra';
|
|
11
12
|
export async function devCommand(filePath, options) {
|
|
@@ -23,44 +24,73 @@ export async function devCommand(filePath, options) {
|
|
|
23
24
|
if (options.open !== undefined) {
|
|
24
25
|
config.open = options.open;
|
|
25
26
|
}
|
|
27
|
+
let port = config.port;
|
|
28
|
+
// 2. Clean dead sessions first
|
|
29
|
+
const deadCleaned = await SessionManager.cleanDeadSessions();
|
|
30
|
+
if (deadCleaned > 0) {
|
|
31
|
+
Logger.info(`Cleaned ${deadCleaned} stale session(s)`);
|
|
32
|
+
}
|
|
33
|
+
// 3. Check and cleanup previous session on same port
|
|
34
|
+
const cleanup = await SessionManager.cleanupSessionOnPort(port);
|
|
35
|
+
if (cleanup.killed) {
|
|
36
|
+
Logger.info(`Stopped previous session on port ${port}`);
|
|
37
|
+
}
|
|
38
|
+
if (cleanup.htmlRemoved) {
|
|
39
|
+
Logger.info('Removed previous preview file');
|
|
40
|
+
}
|
|
41
|
+
// 4. Check if port is available (might be used by other app)
|
|
42
|
+
if (!(await SessionManager.isPortAvailable(port))) {
|
|
43
|
+
const newPort = await SessionManager.findAvailablePort(port + 1);
|
|
44
|
+
Logger.warn(`Port ${port} is in use by another application`);
|
|
45
|
+
Logger.info(`Using port ${newPort} instead`);
|
|
46
|
+
port = newPort;
|
|
47
|
+
}
|
|
26
48
|
Logger.info(`Theme: ${config.theme}`);
|
|
27
|
-
Logger.info(`Port: ${
|
|
28
|
-
//
|
|
49
|
+
Logger.info(`Port: ${port}`);
|
|
50
|
+
// 5. Check pandoc installation
|
|
29
51
|
PandocDetector.ensureInstalled();
|
|
30
|
-
//
|
|
52
|
+
// 6. Check file exists
|
|
31
53
|
const absolutePath = path.resolve(filePath);
|
|
32
54
|
if (!(await fs.pathExists(absolutePath))) {
|
|
33
55
|
Logger.error(`File not found: ${filePath}`);
|
|
34
56
|
process.exit(1);
|
|
35
57
|
}
|
|
36
|
-
//
|
|
58
|
+
// 7. Prepare output HTML in source directory
|
|
37
59
|
const sourceDir = path.dirname(absolutePath);
|
|
38
60
|
const basename = path.basename(filePath, path.extname(filePath));
|
|
39
61
|
const htmlFileName = `vimd-preview-${basename}.html`;
|
|
40
62
|
const htmlPath = path.join(sourceDir, htmlFileName);
|
|
41
|
-
//
|
|
63
|
+
// 8. Prepare converter
|
|
42
64
|
const converter = new MarkdownConverter({
|
|
43
65
|
theme: config.theme,
|
|
44
66
|
pandocOptions: config.pandoc,
|
|
45
67
|
customCSS: config.css,
|
|
46
68
|
template: config.template,
|
|
47
69
|
});
|
|
48
|
-
//
|
|
70
|
+
// 9. Initial conversion
|
|
49
71
|
Logger.info('Converting markdown...');
|
|
50
72
|
const html = await converter.convertWithTemplate(absolutePath);
|
|
51
73
|
await converter.writeHTML(html, htmlPath);
|
|
52
74
|
Logger.success('Conversion complete');
|
|
53
|
-
//
|
|
75
|
+
// 10. Start live server from source directory
|
|
54
76
|
const server = new LiveServer({
|
|
55
|
-
port:
|
|
77
|
+
port: port,
|
|
56
78
|
host: config.host,
|
|
57
79
|
open: config.open,
|
|
58
80
|
root: sourceDir,
|
|
59
81
|
});
|
|
60
82
|
await server.start(htmlPath);
|
|
83
|
+
// 11. Save session
|
|
84
|
+
await SessionManager.saveSession({
|
|
85
|
+
pid: process.pid,
|
|
86
|
+
port: port,
|
|
87
|
+
htmlPath: htmlPath,
|
|
88
|
+
sourcePath: absolutePath,
|
|
89
|
+
startedAt: new Date().toISOString(),
|
|
90
|
+
});
|
|
61
91
|
Logger.info(`Watching: ${filePath}`);
|
|
62
92
|
Logger.info('Press Ctrl+C to stop');
|
|
63
|
-
//
|
|
93
|
+
// 12. Start file watching
|
|
64
94
|
const watcher = new FileWatcher(absolutePath, config.watch);
|
|
65
95
|
watcher.onChange(async (changedPath) => {
|
|
66
96
|
Logger.info('File changed, reconverting...');
|
|
@@ -77,7 +107,7 @@ export async function devCommand(filePath, options) {
|
|
|
77
107
|
}
|
|
78
108
|
});
|
|
79
109
|
watcher.start();
|
|
80
|
-
//
|
|
110
|
+
// 13. Register cleanup - remove generated HTML file and session
|
|
81
111
|
ProcessManager.onExit(async () => {
|
|
82
112
|
Logger.info('Shutting down...');
|
|
83
113
|
await watcher.stop();
|
|
@@ -90,6 +120,8 @@ export async function devCommand(filePath, options) {
|
|
|
90
120
|
catch {
|
|
91
121
|
// Ignore errors when removing file
|
|
92
122
|
}
|
|
123
|
+
// Remove session from registry
|
|
124
|
+
await SessionManager.removeSession(port);
|
|
93
125
|
Logger.info('Cleanup complete');
|
|
94
126
|
});
|
|
95
127
|
}
|
|
@@ -1,54 +1,524 @@
|
|
|
1
|
-
/*
|
|
1
|
+
/*
|
|
2
|
+
* GitHub Dark Theme for vimd
|
|
3
|
+
* Based on github-markdown-css by Sindre Sorhus
|
|
4
|
+
* https://github.com/sindresorhus/github-markdown-css
|
|
5
|
+
* MIT License
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/* Full page dark background */
|
|
9
|
+
html {
|
|
10
|
+
background-color: #0d1117;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/* Main body styles */
|
|
2
14
|
body {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
background: #1e1e1e;
|
|
7
|
-
max-width: 900px;
|
|
15
|
+
color-scheme: dark;
|
|
16
|
+
-ms-text-size-adjust: 100%;
|
|
17
|
+
-webkit-text-size-adjust: 100%;
|
|
8
18
|
margin: 0 auto;
|
|
9
19
|
padding: 2rem;
|
|
20
|
+
max-width: 980px;
|
|
21
|
+
color: #f0f6fc;
|
|
22
|
+
background-color: #0d1117;
|
|
23
|
+
font-family: -apple-system,BlinkMacSystemFont,"Segoe UI","Noto Sans",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji";
|
|
24
|
+
font-size: 16px;
|
|
25
|
+
line-height: 1.5;
|
|
26
|
+
word-wrap: break-word;
|
|
10
27
|
}
|
|
11
28
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
a {
|
|
19
|
-
color: #569cd6;
|
|
29
|
+
/* Links */
|
|
30
|
+
body a {
|
|
31
|
+
background-color: transparent;
|
|
32
|
+
color: #4493f8;
|
|
20
33
|
text-decoration: none;
|
|
21
34
|
}
|
|
22
35
|
|
|
23
|
-
a:hover {
|
|
36
|
+
body a:hover {
|
|
24
37
|
text-decoration: underline;
|
|
25
38
|
}
|
|
26
39
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
border-radius: 3px;
|
|
32
|
-
font-family: 'Fira Code', monospace;
|
|
40
|
+
/* Strong / Bold */
|
|
41
|
+
body b,
|
|
42
|
+
body strong {
|
|
43
|
+
font-weight: 600;
|
|
33
44
|
}
|
|
34
45
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
46
|
+
/* Headings */
|
|
47
|
+
body h1,
|
|
48
|
+
body h2,
|
|
49
|
+
body h3,
|
|
50
|
+
body h4,
|
|
51
|
+
body h5,
|
|
52
|
+
body h6 {
|
|
53
|
+
margin-top: 1.5rem;
|
|
54
|
+
margin-bottom: 1rem;
|
|
55
|
+
font-weight: 600;
|
|
56
|
+
line-height: 1.25;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
body h1 {
|
|
60
|
+
margin: .67em 0;
|
|
61
|
+
font-weight: 600;
|
|
62
|
+
padding-bottom: .3em;
|
|
63
|
+
font-size: 2em;
|
|
64
|
+
border-bottom: 1px solid #3d444db3;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
body h2 {
|
|
68
|
+
font-weight: 600;
|
|
69
|
+
padding-bottom: .3em;
|
|
70
|
+
font-size: 1.5em;
|
|
71
|
+
border-bottom: 1px solid #3d444db3;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
body h3 {
|
|
75
|
+
font-weight: 600;
|
|
76
|
+
font-size: 1.25em;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
body h4 {
|
|
80
|
+
font-weight: 600;
|
|
81
|
+
font-size: 1em;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
body h5 {
|
|
85
|
+
font-weight: 600;
|
|
86
|
+
font-size: .875em;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
body h6 {
|
|
90
|
+
font-weight: 600;
|
|
91
|
+
font-size: .85em;
|
|
92
|
+
color: #9198a1;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/* Paragraphs */
|
|
96
|
+
body p {
|
|
97
|
+
margin-top: 0;
|
|
98
|
+
margin-bottom: 10px;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/* Lists */
|
|
102
|
+
body ul,
|
|
103
|
+
body ol {
|
|
104
|
+
margin-top: 0;
|
|
105
|
+
margin-bottom: 0;
|
|
106
|
+
padding-left: 2em;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
body li+li {
|
|
110
|
+
margin-top: .25em;
|
|
41
111
|
}
|
|
42
112
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
113
|
+
/* Blockquote */
|
|
114
|
+
body blockquote {
|
|
115
|
+
margin: 0;
|
|
116
|
+
padding: 0 1em;
|
|
117
|
+
color: #9198a1;
|
|
118
|
+
border-left: .25em solid #3d444d;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/* Horizontal Rule */
|
|
122
|
+
body hr {
|
|
123
|
+
box-sizing: content-box;
|
|
124
|
+
overflow: hidden;
|
|
125
|
+
background: transparent;
|
|
126
|
+
border-bottom: 1px solid #3d444db3;
|
|
127
|
+
height: .25em;
|
|
46
128
|
padding: 0;
|
|
129
|
+
margin: 1.5rem 0;
|
|
130
|
+
background-color: #3d444d;
|
|
131
|
+
border: 0;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/* Images */
|
|
135
|
+
body img {
|
|
136
|
+
border-style: none;
|
|
137
|
+
max-width: 100%;
|
|
138
|
+
box-sizing: content-box;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/* Tables */
|
|
142
|
+
body table {
|
|
143
|
+
border-spacing: 0;
|
|
144
|
+
border-collapse: collapse;
|
|
145
|
+
display: block;
|
|
146
|
+
width: max-content;
|
|
147
|
+
max-width: 100%;
|
|
148
|
+
overflow: auto;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
body table th {
|
|
152
|
+
font-weight: 600;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
body table th,
|
|
156
|
+
body table td {
|
|
157
|
+
padding: 6px 13px;
|
|
158
|
+
border: 1px solid #3d444d;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
body table tr {
|
|
162
|
+
background-color: #0d1117;
|
|
163
|
+
border-top: 1px solid #3d444db3;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
body table tr:nth-child(2n) {
|
|
167
|
+
background-color: #151b23;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/* Inline Code */
|
|
171
|
+
body code,
|
|
172
|
+
body tt {
|
|
173
|
+
padding: .2em .4em;
|
|
174
|
+
margin: 0;
|
|
175
|
+
font-size: 85%;
|
|
176
|
+
white-space: break-spaces;
|
|
177
|
+
background-color: #656c7633;
|
|
178
|
+
border-radius: 6px;
|
|
179
|
+
font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/* Code Blocks */
|
|
183
|
+
body pre {
|
|
184
|
+
margin-top: 0;
|
|
185
|
+
margin-bottom: 0;
|
|
186
|
+
font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace;
|
|
187
|
+
font-size: 12px;
|
|
188
|
+
word-wrap: normal;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
body pre code {
|
|
192
|
+
font-size: 100%;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
body pre>code {
|
|
196
|
+
padding: 0;
|
|
197
|
+
margin: 0;
|
|
198
|
+
word-break: normal;
|
|
199
|
+
white-space: pre;
|
|
200
|
+
background: transparent;
|
|
201
|
+
border: 0;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
body .highlight pre,
|
|
205
|
+
body pre {
|
|
206
|
+
padding: 1rem;
|
|
207
|
+
overflow: auto;
|
|
208
|
+
font-size: 85%;
|
|
209
|
+
line-height: 1.45;
|
|
210
|
+
color: #f0f6fc;
|
|
211
|
+
background-color: #151b23;
|
|
212
|
+
border-radius: 6px;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
body pre code,
|
|
216
|
+
body pre tt {
|
|
217
|
+
display: inline;
|
|
218
|
+
max-width: auto;
|
|
219
|
+
padding: 0;
|
|
220
|
+
margin: 0;
|
|
221
|
+
overflow: visible;
|
|
222
|
+
line-height: inherit;
|
|
223
|
+
word-wrap: normal;
|
|
224
|
+
background-color: transparent;
|
|
225
|
+
border: 0;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/* Keyboard */
|
|
229
|
+
body kbd {
|
|
230
|
+
display: inline-block;
|
|
231
|
+
padding: 0.25rem;
|
|
232
|
+
font: 11px ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace;
|
|
233
|
+
line-height: 10px;
|
|
234
|
+
color: #f0f6fc;
|
|
235
|
+
vertical-align: middle;
|
|
236
|
+
background-color: #151b23;
|
|
237
|
+
border: solid 1px #3d444db3;
|
|
238
|
+
border-bottom-color: #3d444db3;
|
|
239
|
+
border-radius: 6px;
|
|
240
|
+
box-shadow: inset 0 -1px 0 #3d444db3;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/* Mark / Highlight */
|
|
244
|
+
body mark {
|
|
245
|
+
background-color: #bb800926;
|
|
246
|
+
color: #f0f6fc;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/* Definition Lists */
|
|
250
|
+
body dl {
|
|
251
|
+
padding: 0;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
body dl dt {
|
|
255
|
+
padding: 0;
|
|
256
|
+
margin-top: 1rem;
|
|
257
|
+
font-size: 1em;
|
|
258
|
+
font-style: italic;
|
|
259
|
+
font-weight: 600;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
body dl dd {
|
|
263
|
+
padding: 0 1rem;
|
|
264
|
+
margin-bottom: 1rem;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/* Table of Contents (pandoc) */
|
|
268
|
+
nav#TOC {
|
|
269
|
+
background-color: #151b23;
|
|
270
|
+
border: 1px solid #3d444d;
|
|
271
|
+
border-radius: 6px;
|
|
272
|
+
padding: 1rem 1.5rem;
|
|
273
|
+
margin-bottom: 1.5rem;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
nav#TOC > ul {
|
|
277
|
+
margin: 0;
|
|
278
|
+
padding-left: 0;
|
|
279
|
+
list-style: none;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
nav#TOC ul {
|
|
283
|
+
padding-left: 1.5em;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
nav#TOC li {
|
|
287
|
+
margin: 0.4em 0;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
nav#TOC a {
|
|
291
|
+
color: #4493f8;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
nav#TOC a:hover {
|
|
295
|
+
color: #58a6ff;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/* Pandoc Syntax Highlighting - GitHub Dark Colors */
|
|
299
|
+
div.sourceCode {
|
|
300
|
+
background-color: #151b23;
|
|
301
|
+
border-radius: 6px;
|
|
302
|
+
overflow: auto;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
pre.sourceCode {
|
|
306
|
+
margin: 0;
|
|
307
|
+
padding: 1rem;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
code.sourceCode {
|
|
311
|
+
background: transparent;
|
|
312
|
+
color: #f0f6fc;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/* Comments */
|
|
316
|
+
code.sourceCode span.co,
|
|
317
|
+
.sourceCode .co {
|
|
318
|
+
color: #9198a1;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/* Constants */
|
|
322
|
+
code.sourceCode span.cn,
|
|
323
|
+
code.sourceCode span.c1,
|
|
324
|
+
code.sourceCode span.dv,
|
|
325
|
+
code.sourceCode span.bn,
|
|
326
|
+
code.sourceCode span.fl,
|
|
327
|
+
.sourceCode .cn,
|
|
328
|
+
.sourceCode .c1,
|
|
329
|
+
.sourceCode .dv,
|
|
330
|
+
.sourceCode .bn,
|
|
331
|
+
.sourceCode .fl {
|
|
332
|
+
color: #79c0ff;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/* Strings */
|
|
336
|
+
code.sourceCode span.st,
|
|
337
|
+
code.sourceCode span.ch,
|
|
338
|
+
code.sourceCode span.vs,
|
|
339
|
+
.sourceCode .st,
|
|
340
|
+
.sourceCode .ch,
|
|
341
|
+
.sourceCode .vs {
|
|
342
|
+
color: #a5d6ff;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/* Keywords */
|
|
346
|
+
code.sourceCode span.kw,
|
|
347
|
+
code.sourceCode span.cf,
|
|
348
|
+
.sourceCode .kw,
|
|
349
|
+
.sourceCode .cf {
|
|
350
|
+
color: #ff7b72;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/* Functions */
|
|
354
|
+
code.sourceCode span.fu,
|
|
355
|
+
code.sourceCode span.bu,
|
|
356
|
+
.sourceCode .fu,
|
|
357
|
+
.sourceCode .bu {
|
|
358
|
+
color: #d2a8ff;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/* Variables */
|
|
362
|
+
code.sourceCode span.va,
|
|
363
|
+
.sourceCode .va {
|
|
364
|
+
color: #ffa657;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/* Data Types */
|
|
368
|
+
code.sourceCode span.dt,
|
|
369
|
+
.sourceCode .dt {
|
|
370
|
+
color: #79c0ff;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/* Operators */
|
|
374
|
+
code.sourceCode span.op,
|
|
375
|
+
.sourceCode .op {
|
|
376
|
+
color: #f0f6fc;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/* Attributes / Decorators */
|
|
380
|
+
code.sourceCode span.at,
|
|
381
|
+
.sourceCode .at {
|
|
382
|
+
color: #7ee787;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/* Preprocessor */
|
|
386
|
+
code.sourceCode span.pp,
|
|
387
|
+
.sourceCode .pp {
|
|
388
|
+
color: #ff7b72;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/* Import */
|
|
392
|
+
code.sourceCode span.im,
|
|
393
|
+
.sourceCode .im {
|
|
394
|
+
color: #ff7b72;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/* Special characters */
|
|
398
|
+
code.sourceCode span.sc,
|
|
399
|
+
.sourceCode .sc {
|
|
400
|
+
color: #a5d6ff;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/* Regular expressions */
|
|
404
|
+
code.sourceCode span.ss,
|
|
405
|
+
.sourceCode .ss {
|
|
406
|
+
color: #a5d6ff;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/* Annotations */
|
|
410
|
+
code.sourceCode span.an,
|
|
411
|
+
.sourceCode .an {
|
|
412
|
+
color: #9198a1;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/* Documentation */
|
|
416
|
+
code.sourceCode span.do,
|
|
417
|
+
.sourceCode .do {
|
|
418
|
+
color: #9198a1;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/* Errors */
|
|
422
|
+
code.sourceCode span.er,
|
|
423
|
+
code.sourceCode span.al,
|
|
424
|
+
.sourceCode .er,
|
|
425
|
+
.sourceCode .al {
|
|
426
|
+
color: #f85149;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/* Warnings */
|
|
430
|
+
code.sourceCode span.wa,
|
|
431
|
+
.sourceCode .wa {
|
|
432
|
+
color: #d29922;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/* Information */
|
|
436
|
+
code.sourceCode span.in,
|
|
437
|
+
.sourceCode .in {
|
|
438
|
+
color: #9198a1;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/* GitHub .pl-* classes (for compatibility) */
|
|
442
|
+
body .pl-c {
|
|
443
|
+
color: #9198a1;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
body .pl-c1,
|
|
447
|
+
body .pl-s .pl-v {
|
|
448
|
+
color: #79c0ff;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
body .pl-e,
|
|
452
|
+
body .pl-en {
|
|
453
|
+
color: #d2a8ff;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
body .pl-smi,
|
|
457
|
+
body .pl-s .pl-s1 {
|
|
458
|
+
color: #f0f6fc;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
body .pl-ent {
|
|
462
|
+
color: #7ee787;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
body .pl-k {
|
|
466
|
+
color: #ff7b72;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
body .pl-s,
|
|
470
|
+
body .pl-pds,
|
|
471
|
+
body .pl-s .pl-pse .pl-s1,
|
|
472
|
+
body .pl-sr,
|
|
473
|
+
body .pl-sr .pl-cce,
|
|
474
|
+
body .pl-sr .pl-sre,
|
|
475
|
+
body .pl-sr .pl-sra {
|
|
476
|
+
color: #a5d6ff;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
body .pl-v,
|
|
480
|
+
body .pl-smw {
|
|
481
|
+
color: #ffa657;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
body .pl-bu {
|
|
485
|
+
color: #f85149;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/* Selection */
|
|
489
|
+
::selection {
|
|
490
|
+
background-color: #264f78;
|
|
491
|
+
color: #ffffff;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/* Scrollbar (WebKit) */
|
|
495
|
+
::-webkit-scrollbar {
|
|
496
|
+
width: 10px;
|
|
497
|
+
height: 10px;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
::-webkit-scrollbar-track {
|
|
501
|
+
background: #0d1117;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
::-webkit-scrollbar-thumb {
|
|
505
|
+
background: #3d444d;
|
|
506
|
+
border-radius: 5px;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
::-webkit-scrollbar-thumb:hover {
|
|
510
|
+
background: #4f5761;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/* Focus states */
|
|
514
|
+
body a:focus {
|
|
515
|
+
outline: 2px solid #1f6feb;
|
|
516
|
+
outline-offset: -2px;
|
|
47
517
|
}
|
|
48
518
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
519
|
+
/* Responsive */
|
|
520
|
+
@media (max-width: 768px) {
|
|
521
|
+
body {
|
|
522
|
+
padding: 1rem;
|
|
523
|
+
}
|
|
54
524
|
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
export interface VimdSession {
|
|
2
|
+
pid: number;
|
|
3
|
+
port: number;
|
|
4
|
+
htmlPath: string;
|
|
5
|
+
sourcePath: string;
|
|
6
|
+
startedAt: string;
|
|
7
|
+
}
|
|
8
|
+
export interface VimdSessions {
|
|
9
|
+
[port: string]: VimdSession;
|
|
10
|
+
}
|
|
11
|
+
export interface CleanupResult {
|
|
12
|
+
killed: boolean;
|
|
13
|
+
htmlRemoved: boolean;
|
|
14
|
+
previousPort?: number;
|
|
15
|
+
previousSource?: string;
|
|
16
|
+
}
|
|
17
|
+
export declare class SessionManager {
|
|
18
|
+
private static readonly SESSIONS_DIR;
|
|
19
|
+
private static readonly SESSIONS_FILE;
|
|
20
|
+
/**
|
|
21
|
+
* Load all sessions from file
|
|
22
|
+
*/
|
|
23
|
+
static loadSessions(): Promise<VimdSessions>;
|
|
24
|
+
/**
|
|
25
|
+
* Save sessions to file
|
|
26
|
+
*/
|
|
27
|
+
static saveSessions(sessions: VimdSessions): Promise<void>;
|
|
28
|
+
/**
|
|
29
|
+
* Get session for specific port
|
|
30
|
+
*/
|
|
31
|
+
static getSession(port: number): Promise<VimdSession | null>;
|
|
32
|
+
/**
|
|
33
|
+
* Save session for specific port
|
|
34
|
+
*/
|
|
35
|
+
static saveSession(session: VimdSession): Promise<void>;
|
|
36
|
+
/**
|
|
37
|
+
* Remove session for specific port
|
|
38
|
+
*/
|
|
39
|
+
static removeSession(port: number): Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* Check if a process is alive
|
|
42
|
+
*/
|
|
43
|
+
static isProcessAlive(pid: number): boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Kill a process safely
|
|
46
|
+
*/
|
|
47
|
+
static killProcess(pid: number): Promise<boolean>;
|
|
48
|
+
/**
|
|
49
|
+
* Clean up dead sessions (PIDs that no longer exist)
|
|
50
|
+
*/
|
|
51
|
+
static cleanDeadSessions(): Promise<number>;
|
|
52
|
+
/**
|
|
53
|
+
* Clean up previous session on the same port
|
|
54
|
+
*/
|
|
55
|
+
static cleanupSessionOnPort(port: number): Promise<CleanupResult>;
|
|
56
|
+
/**
|
|
57
|
+
* Check if a port is available
|
|
58
|
+
*/
|
|
59
|
+
static isPortAvailable(port: number): Promise<boolean>;
|
|
60
|
+
/**
|
|
61
|
+
* Find an available port starting from startPort
|
|
62
|
+
*/
|
|
63
|
+
static findAvailablePort(startPort: number): Promise<number>;
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=session-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-manager.d.ts","sourceRoot":"","sources":["../../src/utils/session-manager.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;CAC7B;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,OAAO,CAAC;IAChB,WAAW,EAAE,OAAO,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAkC;IACtE,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAGnC;IAEF;;OAEG;WACU,YAAY,IAAI,OAAO,CAAC,YAAY,CAAC;IAWlD;;OAEG;WACU,YAAY,CAAC,QAAQ,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAKhE;;OAEG;WACU,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAKlE;;OAEG;WACU,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAM7D;;OAEG;WACU,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMvD;;OAEG;IACH,MAAM,CAAC,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAS3C;;OAEG;WACU,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAgBvD;;OAEG;WACU,iBAAiB,IAAI,OAAO,CAAC,MAAM,CAAC;IA0BjD;;OAEG;WACU,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAmCvE;;OAEG;IACH,MAAM,CAAC,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAYtD;;OAEG;WACU,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CAcnE"}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// src/utils/session-manager.ts
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import * as os from 'os';
|
|
5
|
+
import * as net from 'net';
|
|
6
|
+
export class SessionManager {
|
|
7
|
+
/**
|
|
8
|
+
* Load all sessions from file
|
|
9
|
+
*/
|
|
10
|
+
static async loadSessions() {
|
|
11
|
+
try {
|
|
12
|
+
if (await fs.pathExists(this.SESSIONS_FILE)) {
|
|
13
|
+
return await fs.readJson(this.SESSIONS_FILE);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
// Corrupted file, start fresh
|
|
18
|
+
}
|
|
19
|
+
return {};
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Save sessions to file
|
|
23
|
+
*/
|
|
24
|
+
static async saveSessions(sessions) {
|
|
25
|
+
await fs.ensureDir(this.SESSIONS_DIR);
|
|
26
|
+
await fs.writeJson(this.SESSIONS_FILE, sessions, { spaces: 2 });
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Get session for specific port
|
|
30
|
+
*/
|
|
31
|
+
static async getSession(port) {
|
|
32
|
+
const sessions = await this.loadSessions();
|
|
33
|
+
return sessions[port.toString()] || null;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Save session for specific port
|
|
37
|
+
*/
|
|
38
|
+
static async saveSession(session) {
|
|
39
|
+
const sessions = await this.loadSessions();
|
|
40
|
+
sessions[session.port.toString()] = session;
|
|
41
|
+
await this.saveSessions(sessions);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Remove session for specific port
|
|
45
|
+
*/
|
|
46
|
+
static async removeSession(port) {
|
|
47
|
+
const sessions = await this.loadSessions();
|
|
48
|
+
delete sessions[port.toString()];
|
|
49
|
+
await this.saveSessions(sessions);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Check if a process is alive
|
|
53
|
+
*/
|
|
54
|
+
static isProcessAlive(pid) {
|
|
55
|
+
try {
|
|
56
|
+
process.kill(pid, 0);
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Kill a process safely
|
|
65
|
+
*/
|
|
66
|
+
static async killProcess(pid) {
|
|
67
|
+
try {
|
|
68
|
+
process.kill(pid, 'SIGTERM');
|
|
69
|
+
// Wait a bit for graceful shutdown
|
|
70
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
71
|
+
// Force kill if still alive
|
|
72
|
+
if (this.isProcessAlive(pid)) {
|
|
73
|
+
process.kill(pid, 'SIGKILL');
|
|
74
|
+
}
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Clean up dead sessions (PIDs that no longer exist)
|
|
83
|
+
*/
|
|
84
|
+
static async cleanDeadSessions() {
|
|
85
|
+
const sessions = await this.loadSessions();
|
|
86
|
+
let cleaned = 0;
|
|
87
|
+
for (const [port, session] of Object.entries(sessions)) {
|
|
88
|
+
if (!this.isProcessAlive(session.pid)) {
|
|
89
|
+
// Process is dead, clean up HTML if exists
|
|
90
|
+
try {
|
|
91
|
+
if (await fs.pathExists(session.htmlPath)) {
|
|
92
|
+
await fs.remove(session.htmlPath);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
// Ignore removal errors
|
|
97
|
+
}
|
|
98
|
+
delete sessions[port];
|
|
99
|
+
cleaned++;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (cleaned > 0) {
|
|
103
|
+
await this.saveSessions(sessions);
|
|
104
|
+
}
|
|
105
|
+
return cleaned;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Clean up previous session on the same port
|
|
109
|
+
*/
|
|
110
|
+
static async cleanupSessionOnPort(port) {
|
|
111
|
+
const session = await this.getSession(port);
|
|
112
|
+
if (!session) {
|
|
113
|
+
return { killed: false, htmlRemoved: false };
|
|
114
|
+
}
|
|
115
|
+
const result = {
|
|
116
|
+
killed: false,
|
|
117
|
+
htmlRemoved: false,
|
|
118
|
+
previousPort: port,
|
|
119
|
+
previousSource: session.sourcePath,
|
|
120
|
+
};
|
|
121
|
+
// Kill process if alive
|
|
122
|
+
if (this.isProcessAlive(session.pid)) {
|
|
123
|
+
result.killed = await this.killProcess(session.pid);
|
|
124
|
+
}
|
|
125
|
+
// Remove HTML file
|
|
126
|
+
try {
|
|
127
|
+
if (await fs.pathExists(session.htmlPath)) {
|
|
128
|
+
await fs.remove(session.htmlPath);
|
|
129
|
+
result.htmlRemoved = true;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
// Ignore removal errors
|
|
134
|
+
}
|
|
135
|
+
// Remove session from registry
|
|
136
|
+
await this.removeSession(port);
|
|
137
|
+
return result;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Check if a port is available
|
|
141
|
+
*/
|
|
142
|
+
static isPortAvailable(port) {
|
|
143
|
+
return new Promise((resolve) => {
|
|
144
|
+
const server = net.createServer();
|
|
145
|
+
server.once('error', () => resolve(false));
|
|
146
|
+
server.once('listening', () => {
|
|
147
|
+
server.close();
|
|
148
|
+
resolve(true);
|
|
149
|
+
});
|
|
150
|
+
server.listen(port, '127.0.0.1');
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Find an available port starting from startPort
|
|
155
|
+
*/
|
|
156
|
+
static async findAvailablePort(startPort) {
|
|
157
|
+
const MAX_ATTEMPTS = 10;
|
|
158
|
+
for (let i = 0; i < MAX_ATTEMPTS; i++) {
|
|
159
|
+
const port = startPort + i;
|
|
160
|
+
if (await this.isPortAvailable(port)) {
|
|
161
|
+
return port;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
throw new Error(`No available port found (tried ${startPort}-${startPort + MAX_ATTEMPTS - 1})`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
SessionManager.SESSIONS_DIR = path.join(os.tmpdir(), 'vimd');
|
|
168
|
+
SessionManager.SESSIONS_FILE = path.join(SessionManager.SESSIONS_DIR, 'sessions.json');
|