@mgks/docmd 0.3.6 → 0.3.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/LICENSE +1 -1
- package/README.md +86 -77
- package/bin/docmd.js +13 -16
- package/bin/postinstall.js +4 -4
- package/package.json +19 -16
- package/src/assets/css/docmd-highlight-dark.css +86 -1
- package/src/assets/css/docmd-highlight-light.css +86 -1
- package/src/assets/css/docmd-main.css +544 -464
- package/src/assets/css/docmd-theme-retro.css +105 -106
- package/src/assets/css/docmd-theme-ruby.css +92 -92
- package/src/assets/css/docmd-theme-sky.css +63 -64
- package/src/assets/favicon.ico +0 -0
- package/src/assets/images/docmd-logo-dark.png +0 -0
- package/src/assets/images/docmd-logo-light.png +0 -0
- package/src/assets/js/docmd-image-lightbox.js +2 -2
- package/src/assets/js/docmd-main.js +14 -6
- package/src/assets/js/docmd-mermaid.js +1 -1
- package/src/assets/js/docmd-search.js +1 -1
- package/src/commands/build.js +71 -370
- package/src/commands/dev.js +199 -72
- package/src/commands/init.js +135 -134
- package/src/commands/live.js +145 -0
- package/src/core/asset-manager.js +72 -0
- package/src/core/config-loader.js +2 -2
- package/src/core/config-validator.js +1 -1
- package/src/core/file-processor.js +13 -9
- package/src/core/fs-utils.js +40 -0
- package/src/core/html-formatter.js +97 -0
- package/src/core/html-generator.js +61 -65
- package/src/core/icon-renderer.js +1 -1
- package/src/core/logger.js +1 -1
- package/src/core/markdown/containers.js +1 -1
- package/src/core/markdown/renderers.js +1 -1
- package/src/core/markdown/rules.js +1 -2
- package/src/core/markdown/setup.js +1 -1
- package/src/core/navigation-helper.js +1 -1
- package/src/index.js +12 -0
- package/src/live/core.js +5 -1
- package/src/live/index.html +16 -1
- package/src/live/live.css +157 -68
- package/src/plugins/analytics.js +1 -1
- package/src/plugins/seo.js +26 -36
- package/src/plugins/sitemap.js +2 -2
- package/src/templates/layout.ejs +50 -81
- package/src/templates/navigation.ejs +23 -76
- package/src/templates/no-style.ejs +115 -129
- package/src/templates/partials/theme-init.js +1 -1
- package/src/templates/toc.ejs +6 -35
- package/docmd.config.js +0 -175
- package/scripts/build-live.js +0 -157
- package/scripts/test-live.js +0 -54
- package/src/assets/images/docmd-logo.png +0 -0
- package/src/live/templates.js +0 -9
package/src/commands/dev.js
CHANGED
|
@@ -1,16 +1,126 @@
|
|
|
1
|
-
// Source file from the docmd project — https://github.com/
|
|
1
|
+
// Source file from the docmd project — https://github.com/docmd-io/docmd
|
|
2
2
|
|
|
3
|
-
const express = require('express');
|
|
4
3
|
const http = require('http');
|
|
5
4
|
const WebSocket = require('ws');
|
|
6
5
|
const chokidar = require('chokidar');
|
|
7
6
|
const path = require('path');
|
|
8
|
-
const fs = require('fs-
|
|
7
|
+
const fs = require('../core/fs-utils');
|
|
9
8
|
const chalk = require('chalk');
|
|
10
|
-
const os = require('os');
|
|
9
|
+
const os = require('os');
|
|
10
|
+
const readline = require('readline');
|
|
11
11
|
const { buildSite } = require('./build');
|
|
12
12
|
const { loadConfig } = require('../core/config-loader');
|
|
13
13
|
|
|
14
|
+
// --- 1. Native Static File Server ---
|
|
15
|
+
const MIME_TYPES = {
|
|
16
|
+
'.html': 'text/html',
|
|
17
|
+
'.js': 'text/javascript',
|
|
18
|
+
'.css': 'text/css',
|
|
19
|
+
'.json': 'application/json',
|
|
20
|
+
'.png': 'image/png',
|
|
21
|
+
'.jpg': 'image/jpg',
|
|
22
|
+
'.jpeg': 'image/jpg',
|
|
23
|
+
'.gif': 'image/gif',
|
|
24
|
+
'.svg': 'image/svg+xml',
|
|
25
|
+
'.ico': 'image/x-icon',
|
|
26
|
+
'.woff': 'application/font-woff',
|
|
27
|
+
'.woff2': 'font/woff2',
|
|
28
|
+
'.ttf': 'application/font-ttf',
|
|
29
|
+
'.txt': 'text/plain',
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
async function serveStatic(req, res, rootDir) {
|
|
33
|
+
// Normalize path and remove query strings
|
|
34
|
+
let safePath = path.normalize(req.url).replace(/^(\.\.[\/\\])+/, '').split('?')[0].split('#')[0];
|
|
35
|
+
if (safePath === '/' || safePath === '\\') safePath = 'index.html';
|
|
36
|
+
|
|
37
|
+
let filePath = path.join(rootDir, safePath);
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
let stats;
|
|
41
|
+
try {
|
|
42
|
+
stats = await fs.stat(filePath);
|
|
43
|
+
} catch (e) {
|
|
44
|
+
// If direct path fails, try appending .html (clean URLs support)
|
|
45
|
+
if (path.extname(filePath) === '') {
|
|
46
|
+
filePath += '.html';
|
|
47
|
+
stats = await fs.stat(filePath);
|
|
48
|
+
} else {
|
|
49
|
+
throw e;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (stats.isDirectory()) {
|
|
54
|
+
filePath = path.join(filePath, 'index.html');
|
|
55
|
+
await fs.stat(filePath);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
59
|
+
const contentType = MIME_TYPES[ext] || 'application/octet-stream';
|
|
60
|
+
const content = await fs.readFile(filePath);
|
|
61
|
+
|
|
62
|
+
res.writeHead(200, { 'Content-Type': contentType });
|
|
63
|
+
|
|
64
|
+
// Inject Live Reload Script into HTML files only
|
|
65
|
+
if (contentType === 'text/html') {
|
|
66
|
+
const htmlStr = content.toString('utf-8');
|
|
67
|
+
const liveReloadScript = `
|
|
68
|
+
<script>
|
|
69
|
+
(function() {
|
|
70
|
+
let socket;
|
|
71
|
+
let retryCount = 0;
|
|
72
|
+
const maxRetries = 50;
|
|
73
|
+
|
|
74
|
+
function connect() {
|
|
75
|
+
// Avoid connecting if already connected
|
|
76
|
+
if (socket && (socket.readyState === 0 || socket.readyState === 1)) return;
|
|
77
|
+
|
|
78
|
+
socket = new WebSocket('ws://' + window.location.host);
|
|
79
|
+
|
|
80
|
+
socket.onopen = () => {
|
|
81
|
+
console.log('⚡ docmd connected');
|
|
82
|
+
retryCount = 0;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
socket.onmessage = (e) => {
|
|
86
|
+
if(e.data === 'reload') window.location.reload();
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
socket.onclose = () => {
|
|
90
|
+
// Exponential backoff for reconnection
|
|
91
|
+
if (retryCount < maxRetries) {
|
|
92
|
+
retryCount++;
|
|
93
|
+
const delay = Math.min(1000 * (1.5 ** retryCount), 5000);
|
|
94
|
+
setTimeout(connect, delay);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
socket.onerror = (err) => {
|
|
99
|
+
// Ignore errors, let onclose handle retry
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
// Delay initial connection slightly to ensure page load
|
|
103
|
+
setTimeout(connect, 500);
|
|
104
|
+
})();
|
|
105
|
+
</script></body>`;
|
|
106
|
+
res.end(htmlStr.replace('</body>', liveReloadScript));
|
|
107
|
+
} else {
|
|
108
|
+
res.end(content);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
} catch (err) {
|
|
112
|
+
if (err.code === 'ENOENT') {
|
|
113
|
+
res.writeHead(404, { 'Content-Type': 'text/html' });
|
|
114
|
+
res.end('<h1>404 Not Found</h1><p>docmd dev server</p>');
|
|
115
|
+
} else {
|
|
116
|
+
res.writeHead(500);
|
|
117
|
+
res.end(`Server Error: ${err.code}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// --- 2. Helper Utilities ---
|
|
123
|
+
|
|
14
124
|
function formatPathForDisplay(absolutePath, cwd) {
|
|
15
125
|
const relativePath = path.relative(cwd, absolutePath);
|
|
16
126
|
if (!relativePath.startsWith('..') && !path.isAbsolute(relativePath)) {
|
|
@@ -31,11 +141,13 @@ function getNetworkIp() {
|
|
|
31
141
|
return null;
|
|
32
142
|
}
|
|
33
143
|
|
|
144
|
+
// --- 3. Main Dev Function ---
|
|
145
|
+
|
|
34
146
|
async function startDevServer(configPathOption, options = { preserve: false, port: undefined }) {
|
|
35
147
|
let config = await loadConfig(configPathOption);
|
|
36
148
|
const CWD = process.cwd();
|
|
37
149
|
|
|
38
|
-
// Config Fallback
|
|
150
|
+
// Config Fallback Logic
|
|
39
151
|
let actualConfigPath = path.resolve(CWD, configPathOption);
|
|
40
152
|
if (configPathOption === 'docmd.config.js' && !await fs.pathExists(actualConfigPath)) {
|
|
41
153
|
const legacyPath = path.resolve(CWD, 'config.js');
|
|
@@ -56,58 +168,24 @@ async function startDevServer(configPathOption, options = { preserve: false, por
|
|
|
56
168
|
let paths = resolveConfigPaths(config);
|
|
57
169
|
const DOCMD_ROOT = path.resolve(__dirname, '..');
|
|
58
170
|
|
|
59
|
-
|
|
60
|
-
const server = http.createServer(
|
|
61
|
-
|
|
171
|
+
// --- Create Native Server ---
|
|
172
|
+
const server = http.createServer((req, res) => {
|
|
173
|
+
serveStatic(req, res, paths.outputDir);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
let wss; // WebSocket instance (initialized later)
|
|
62
177
|
|
|
63
178
|
function broadcastReload() {
|
|
64
|
-
wss
|
|
65
|
-
|
|
66
|
-
client.
|
|
67
|
-
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Inject live reload script
|
|
72
|
-
app.use((req, res, next) => {
|
|
73
|
-
if (req.path.endsWith('.html') || req.path === '/' || !req.path.includes('.')) {
|
|
74
|
-
const originalSend = res.send;
|
|
75
|
-
res.send = function(body) {
|
|
76
|
-
if (typeof body === 'string' && body.includes('</body>')) {
|
|
77
|
-
const liveReloadScript = `
|
|
78
|
-
<script>
|
|
79
|
-
(function() {
|
|
80
|
-
let socket;
|
|
81
|
-
let reconnectTimer;
|
|
82
|
-
function connect() {
|
|
83
|
-
socket = new WebSocket('ws://' + window.location.host);
|
|
84
|
-
socket.onopen = function() {
|
|
85
|
-
console.log('⚡ docmd live reload connected');
|
|
86
|
-
if (reconnectTimer) clearInterval(reconnectTimer);
|
|
87
|
-
};
|
|
88
|
-
socket.onmessage = function(event) {
|
|
89
|
-
if (event.data === 'reload') window.location.reload();
|
|
90
|
-
};
|
|
91
|
-
socket.onclose = function() {
|
|
92
|
-
reconnectTimer = setTimeout(connect, 1000);
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
connect();
|
|
96
|
-
})();
|
|
97
|
-
</script>
|
|
98
|
-
`;
|
|
99
|
-
body = body.replace('</body>', `${liveReloadScript}</body>`);
|
|
179
|
+
if (wss) {
|
|
180
|
+
wss.clients.forEach((client) => {
|
|
181
|
+
if (client.readyState === WebSocket.OPEN) {
|
|
182
|
+
client.send('reload');
|
|
100
183
|
}
|
|
101
|
-
|
|
102
|
-
};
|
|
184
|
+
});
|
|
103
185
|
}
|
|
104
|
-
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
let staticMiddleware = express.static(paths.outputDir);
|
|
108
|
-
app.use((req, res, next) => staticMiddleware(req, res, next));
|
|
186
|
+
}
|
|
109
187
|
|
|
110
|
-
// ---
|
|
188
|
+
// --- Initial Build ---
|
|
111
189
|
console.log(chalk.blue('🚀 Performing initial build...'));
|
|
112
190
|
try {
|
|
113
191
|
await buildSite(configPathOption, { isDev: true, preserve: options.preserve, noDoubleProcessing: true });
|
|
@@ -115,7 +193,7 @@ async function startDevServer(configPathOption, options = { preserve: false, por
|
|
|
115
193
|
console.error(chalk.red('❌ Initial build failed:'), error.message);
|
|
116
194
|
}
|
|
117
195
|
|
|
118
|
-
// ---
|
|
196
|
+
// --- Watcher Setup ---
|
|
119
197
|
const userAssetsDirExists = await fs.pathExists(paths.userAssetsDir);
|
|
120
198
|
const watchedPaths = [paths.srcDirToWatch, paths.configFileToWatch];
|
|
121
199
|
if (userAssetsDirExists) watchedPaths.push(paths.userAssetsDir);
|
|
@@ -129,16 +207,12 @@ async function startDevServer(configPathOption, options = { preserve: false, por
|
|
|
129
207
|
);
|
|
130
208
|
}
|
|
131
209
|
|
|
132
|
-
// LOGS: Explicitly print what we are watching
|
|
133
210
|
console.log(chalk.dim('\n👀 Watching for changes in:'));
|
|
134
211
|
console.log(chalk.dim(` - Source: ${chalk.cyan(formatPathForDisplay(paths.srcDirToWatch, CWD))}`));
|
|
135
212
|
console.log(chalk.dim(` - Config: ${chalk.cyan(formatPathForDisplay(paths.configFileToWatch, CWD))}`));
|
|
136
213
|
if (userAssetsDirExists) {
|
|
137
214
|
console.log(chalk.dim(` - Assets: ${chalk.cyan(formatPathForDisplay(paths.userAssetsDir, CWD))}`));
|
|
138
215
|
}
|
|
139
|
-
if (process.env.DOCMD_DEV === 'true') {
|
|
140
|
-
console.log(chalk.dim(` - docmd Internal: ${chalk.magenta(formatPathForDisplay(DOCMD_ROOT, CWD))}`));
|
|
141
|
-
}
|
|
142
216
|
console.log('');
|
|
143
217
|
|
|
144
218
|
const watcher = chokidar.watch(watchedPaths, {
|
|
@@ -155,11 +229,9 @@ async function startDevServer(configPathOption, options = { preserve: false, por
|
|
|
155
229
|
try {
|
|
156
230
|
if (filePath === paths.configFileToWatch) {
|
|
157
231
|
config = await loadConfig(configPathOption);
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
}
|
|
162
|
-
paths = newPaths;
|
|
232
|
+
// Note: With native server, we don't need to restart middleware,
|
|
233
|
+
// serveStatic reads from disk dynamically on every request.
|
|
234
|
+
paths = resolveConfigPaths(config);
|
|
163
235
|
}
|
|
164
236
|
|
|
165
237
|
await buildSite(configPathOption, { isDev: true, preserve: options.preserve, noDoubleProcessing: true });
|
|
@@ -171,17 +243,52 @@ async function startDevServer(configPathOption, options = { preserve: false, por
|
|
|
171
243
|
}
|
|
172
244
|
});
|
|
173
245
|
|
|
174
|
-
|
|
246
|
+
// --- Server Startup Logic (Port Checking) ---
|
|
247
|
+
const PORT = parseInt(options.port || process.env.PORT || 3000, 10);
|
|
175
248
|
const MAX_PORT_ATTEMPTS = 10;
|
|
249
|
+
|
|
250
|
+
function checkPortInUse(port) {
|
|
251
|
+
return new Promise((resolve) => {
|
|
252
|
+
const tester = http.createServer()
|
|
253
|
+
.once('error', (err) => {
|
|
254
|
+
if (err.code === 'EADDRINUSE') resolve(true);
|
|
255
|
+
else resolve(false);
|
|
256
|
+
})
|
|
257
|
+
.once('listening', () => {
|
|
258
|
+
tester.close(() => resolve(false));
|
|
259
|
+
})
|
|
260
|
+
.listen(port, '0.0.0.0');
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function askUserConfirmation() {
|
|
265
|
+
return new Promise((resolve) => {
|
|
266
|
+
const rl = readline.createInterface({
|
|
267
|
+
input: process.stdin,
|
|
268
|
+
output: process.stdout
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
console.log(chalk.yellow(`\n⚠️ Port ${PORT} is already in use.`));
|
|
272
|
+
console.log(chalk.yellow(` Another instance of docmd (or another app) might be running.`));
|
|
273
|
+
|
|
274
|
+
rl.question(' Do you want to start another instance on a different port? (Y/n) ', (answer) => {
|
|
275
|
+
rl.close();
|
|
276
|
+
const isYes = answer.trim().toLowerCase() === 'y' || answer.trim() === '';
|
|
277
|
+
resolve(isYes);
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
}
|
|
176
281
|
|
|
177
282
|
function tryStartServer(port, attempt = 1) {
|
|
178
|
-
// 0.0.0.0 allows network access
|
|
179
283
|
server.listen(port, '0.0.0.0')
|
|
180
284
|
.on('listening', async () => {
|
|
285
|
+
// Initialize WebSocket Server only AFTER successful listen
|
|
286
|
+
wss = new WebSocket.Server({ server });
|
|
287
|
+
wss.on('error', (e) => console.error('WebSocket Error:', e.message));
|
|
288
|
+
|
|
181
289
|
const indexHtmlPath = path.join(paths.outputDir, 'index.html');
|
|
182
290
|
const networkIp = getNetworkIp();
|
|
183
291
|
|
|
184
|
-
// Use 127.0.0.1 explicitly
|
|
185
292
|
const localUrl = `http://127.0.0.1:${port}`;
|
|
186
293
|
const networkUrl = networkIp ? `http://${networkIp}:${port}` : null;
|
|
187
294
|
|
|
@@ -203,16 +310,37 @@ async function startDevServer(configPathOption, options = { preserve: false, por
|
|
|
203
310
|
}
|
|
204
311
|
})
|
|
205
312
|
.on('error', (err) => {
|
|
206
|
-
if (err.code === 'EADDRINUSE'
|
|
207
|
-
|
|
313
|
+
if (err.code === 'EADDRINUSE') {
|
|
314
|
+
server.close();
|
|
315
|
+
tryStartServer(port + 1);
|
|
208
316
|
} else {
|
|
209
|
-
|
|
210
|
-
|
|
317
|
+
console.error(chalk.red(`Failed to start server: ${err.message}`));
|
|
318
|
+
process.exit(1);
|
|
211
319
|
}
|
|
212
320
|
});
|
|
213
321
|
}
|
|
214
|
-
|
|
215
|
-
|
|
322
|
+
|
|
323
|
+
// --- Main Execution Flow ---
|
|
324
|
+
(async () => {
|
|
325
|
+
// Skip check if user manually specified port flag
|
|
326
|
+
if (options.port) {
|
|
327
|
+
tryStartServer(PORT);
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const isBusy = await checkPortInUse(PORT);
|
|
332
|
+
|
|
333
|
+
if (isBusy) {
|
|
334
|
+
const shouldProceed = await askUserConfirmation();
|
|
335
|
+
if (!shouldProceed) {
|
|
336
|
+
console.log(chalk.dim('Cancelled.'));
|
|
337
|
+
process.exit(0);
|
|
338
|
+
}
|
|
339
|
+
tryStartServer(PORT + 1);
|
|
340
|
+
} else {
|
|
341
|
+
tryStartServer(PORT);
|
|
342
|
+
}
|
|
343
|
+
})();
|
|
216
344
|
|
|
217
345
|
process.on('SIGINT', () => {
|
|
218
346
|
console.log(chalk.yellow('\n🛑 Shutting down...'));
|
|
@@ -221,5 +349,4 @@ async function startDevServer(configPathOption, options = { preserve: false, por
|
|
|
221
349
|
});
|
|
222
350
|
}
|
|
223
351
|
|
|
224
|
-
// Ensure this export is here!
|
|
225
352
|
module.exports = { startDevServer };
|