@mgks/docmd 0.3.4 ā 0.3.6
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 +92 -100
- package/bin/docmd.js +55 -93
- package/docmd.config.js +175 -0
- package/package.json +16 -15
- package/scripts/build-live.js +157 -0
- package/scripts/test-live.js +54 -0
- package/src/assets/js/docmd-main.js +35 -106
- package/src/commands/build.js +11 -6
- package/src/commands/dev.js +100 -189
- package/src/commands/init.js +8 -8
- package/src/core/config-loader.js +23 -3
- package/src/core/file-processor.js +8 -2
- package/src/core/html-generator.js +107 -102
- package/src/live/core.js +63 -0
- package/src/live/index.html +201 -0
- package/src/live/live.css +167 -0
- package/src/live/shims.js +1 -0
- package/src/live/templates.js +9 -0
- package/src/templates/layout.ejs +11 -11
- package/src/templates/navigation.ejs +69 -7
- package/config.js +0 -175
package/src/commands/dev.js
CHANGED
|
@@ -6,156 +6,93 @@ const WebSocket = require('ws');
|
|
|
6
6
|
const chokidar = require('chokidar');
|
|
7
7
|
const path = require('path');
|
|
8
8
|
const fs = require('fs-extra');
|
|
9
|
-
const
|
|
9
|
+
const chalk = require('chalk');
|
|
10
|
+
const os = require('os');
|
|
11
|
+
const { buildSite } = require('./build');
|
|
10
12
|
const { loadConfig } = require('../core/config-loader');
|
|
11
13
|
|
|
12
|
-
/**
|
|
13
|
-
* Format paths for display to make them relative to CWD
|
|
14
|
-
* @param {string} absolutePath - The absolute path to format
|
|
15
|
-
* @param {string} cwd - Current working directory
|
|
16
|
-
* @returns {string} - Formatted relative path
|
|
17
|
-
*/
|
|
18
14
|
function formatPathForDisplay(absolutePath, cwd) {
|
|
19
|
-
// Get the relative path from CWD
|
|
20
15
|
const relativePath = path.relative(cwd, absolutePath);
|
|
21
|
-
|
|
22
|
-
// If it's not a subdirectory, prefix with ./ for clarity
|
|
23
16
|
if (!relativePath.startsWith('..') && !path.isAbsolute(relativePath)) {
|
|
24
17
|
return `./${relativePath}`;
|
|
25
18
|
}
|
|
26
|
-
|
|
27
|
-
// Return the relative path
|
|
28
19
|
return relativePath;
|
|
29
20
|
}
|
|
30
21
|
|
|
22
|
+
function getNetworkIp() {
|
|
23
|
+
const interfaces = os.networkInterfaces();
|
|
24
|
+
for (const name of Object.keys(interfaces)) {
|
|
25
|
+
for (const iface of interfaces[name]) {
|
|
26
|
+
if (iface.family === 'IPv4' && !iface.internal) {
|
|
27
|
+
return iface.address;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
31
34
|
async function startDevServer(configPathOption, options = { preserve: false, port: undefined }) {
|
|
32
|
-
let config = await loadConfig(configPathOption);
|
|
33
|
-
const CWD = process.cwd();
|
|
35
|
+
let config = await loadConfig(configPathOption);
|
|
36
|
+
const CWD = process.cwd();
|
|
37
|
+
|
|
38
|
+
// Config Fallback for Watcher
|
|
39
|
+
let actualConfigPath = path.resolve(CWD, configPathOption);
|
|
40
|
+
if (configPathOption === 'docmd.config.js' && !await fs.pathExists(actualConfigPath)) {
|
|
41
|
+
const legacyPath = path.resolve(CWD, 'config.js');
|
|
42
|
+
if (await fs.pathExists(legacyPath)) {
|
|
43
|
+
actualConfigPath = legacyPath;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
34
46
|
|
|
35
|
-
// Function to resolve paths based on current config
|
|
36
47
|
const resolveConfigPaths = (currentConfig) => {
|
|
37
48
|
return {
|
|
38
49
|
outputDir: path.resolve(CWD, currentConfig.outputDir),
|
|
39
50
|
srcDirToWatch: path.resolve(CWD, currentConfig.srcDir),
|
|
40
|
-
configFileToWatch:
|
|
41
|
-
userAssetsDir: path.resolve(CWD, 'assets'),
|
|
51
|
+
configFileToWatch: actualConfigPath,
|
|
52
|
+
userAssetsDir: path.resolve(CWD, 'assets'),
|
|
42
53
|
};
|
|
43
54
|
};
|
|
44
55
|
|
|
45
56
|
let paths = resolveConfigPaths(config);
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const DOCMD_COMMANDS_DIR = path.resolve(__dirname, '..', 'commands');
|
|
49
|
-
const DOCMD_CORE_DIR = path.resolve(__dirname, '..', 'core');
|
|
50
|
-
const DOCMD_PLUGINS_DIR = path.resolve(__dirname, '..', 'plugins');
|
|
51
|
-
const DOCMD_TEMPLATES_DIR = path.resolve(__dirname, '..', 'templates');
|
|
52
|
-
const DOCMD_ASSETS_DIR = path.resolve(__dirname, '..', 'assets');
|
|
53
|
-
|
|
57
|
+
const DOCMD_ROOT = path.resolve(__dirname, '..');
|
|
58
|
+
|
|
54
59
|
const app = express();
|
|
55
60
|
const server = http.createServer(app);
|
|
56
61
|
const wss = new WebSocket.Server({ server });
|
|
57
62
|
|
|
58
|
-
let wsClients = new Set();
|
|
59
|
-
wss.on('connection', (ws) => {
|
|
60
|
-
wsClients.add(ws);
|
|
61
|
-
// console.log('Client connected to WebSocket. Total clients:', wsClients.size);
|
|
62
|
-
ws.on('close', () => {
|
|
63
|
-
wsClients.delete(ws);
|
|
64
|
-
// console.log('Client disconnected. Total clients:', wsClients.size);
|
|
65
|
-
});
|
|
66
|
-
ws.on('error', (error) => {
|
|
67
|
-
console.error('WebSocket error on client:', error);
|
|
68
|
-
});
|
|
69
|
-
});
|
|
70
|
-
wss.on('error', (error) => {
|
|
71
|
-
console.error('WebSocket Server error:', error);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
63
|
function broadcastReload() {
|
|
75
|
-
|
|
64
|
+
wss.clients.forEach((client) => {
|
|
76
65
|
if (client.readyState === WebSocket.OPEN) {
|
|
77
|
-
|
|
78
|
-
client.send('reload');
|
|
79
|
-
} catch (error) {
|
|
80
|
-
console.error('Error sending reload command to client:', error);
|
|
81
|
-
}
|
|
66
|
+
client.send('reload');
|
|
82
67
|
}
|
|
83
68
|
});
|
|
84
69
|
}
|
|
85
70
|
|
|
86
|
-
// Inject live reload script
|
|
71
|
+
// Inject live reload script
|
|
87
72
|
app.use((req, res, next) => {
|
|
88
|
-
if (req.path.endsWith('.html') || !req.path.includes('.')) {
|
|
73
|
+
if (req.path.endsWith('.html') || req.path === '/' || !req.path.includes('.')) {
|
|
89
74
|
const originalSend = res.send;
|
|
90
75
|
res.send = function(body) {
|
|
91
76
|
if (typeof body === 'string' && body.includes('</body>')) {
|
|
92
77
|
const liveReloadScript = `
|
|
93
78
|
<script>
|
|
94
79
|
(function() {
|
|
95
|
-
// More robust WebSocket connection with automatic reconnection
|
|
96
80
|
let socket;
|
|
97
|
-
let
|
|
98
|
-
const maxReconnectAttempts = 5;
|
|
99
|
-
const reconnectDelay = 1000; // Start with 1 second delay
|
|
100
|
-
|
|
81
|
+
let reconnectTimer;
|
|
101
82
|
function connect() {
|
|
102
|
-
socket = new WebSocket(
|
|
103
|
-
|
|
104
|
-
socket.onmessage = function(event) {
|
|
105
|
-
if (event.data === 'reload') {
|
|
106
|
-
console.log('Received reload signal. Refreshing page...');
|
|
107
|
-
window.location.reload();
|
|
108
|
-
}
|
|
109
|
-
};
|
|
110
|
-
|
|
83
|
+
socket = new WebSocket('ws://' + window.location.host);
|
|
111
84
|
socket.onopen = function() {
|
|
112
|
-
console.log('
|
|
113
|
-
|
|
85
|
+
console.log('ā” docmd live reload connected');
|
|
86
|
+
if (reconnectTimer) clearInterval(reconnectTimer);
|
|
114
87
|
};
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
if (reconnectAttempts < maxReconnectAttempts) {
|
|
118
|
-
reconnectAttempts++;
|
|
119
|
-
const delay = reconnectDelay * Math.pow(1.5, reconnectAttempts - 1); // Exponential backoff
|
|
120
|
-
console.log(\`Live reload disconnected. Reconnecting in \${delay/1000} seconds...\`);
|
|
121
|
-
setTimeout(connect, delay);
|
|
122
|
-
} else {
|
|
123
|
-
console.log('Live reload disconnected. Max reconnect attempts reached.');
|
|
124
|
-
}
|
|
88
|
+
socket.onmessage = function(event) {
|
|
89
|
+
if (event.data === 'reload') window.location.reload();
|
|
125
90
|
};
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
console.error('WebSocket error:', error);
|
|
91
|
+
socket.onclose = function() {
|
|
92
|
+
reconnectTimer = setTimeout(connect, 1000);
|
|
129
93
|
};
|
|
130
94
|
}
|
|
131
|
-
|
|
132
|
-
// Initial connection
|
|
133
95
|
connect();
|
|
134
|
-
|
|
135
|
-
// Backup reload mechanism using polling for browsers with WebSocket issues
|
|
136
|
-
let lastModified = new Date().getTime();
|
|
137
|
-
const pollInterval = 2000; // Poll every 2 seconds
|
|
138
|
-
|
|
139
|
-
function checkForChanges() {
|
|
140
|
-
fetch(window.location.href, { method: 'HEAD', cache: 'no-store' })
|
|
141
|
-
.then(response => {
|
|
142
|
-
const serverLastModified = new Date(response.headers.get('Last-Modified')).getTime();
|
|
143
|
-
if (serverLastModified > lastModified) {
|
|
144
|
-
console.log('Change detected via polling. Refreshing page...');
|
|
145
|
-
window.location.reload();
|
|
146
|
-
}
|
|
147
|
-
lastModified = serverLastModified;
|
|
148
|
-
})
|
|
149
|
-
.catch(error => console.error('Error checking for changes:', error));
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Only use polling as a fallback if WebSocket fails
|
|
153
|
-
setTimeout(() => {
|
|
154
|
-
if (socket.readyState !== WebSocket.OPEN) {
|
|
155
|
-
console.log('WebSocket not connected. Falling back to polling.');
|
|
156
|
-
setInterval(checkForChanges, pollInterval);
|
|
157
|
-
}
|
|
158
|
-
}, 5000);
|
|
159
96
|
})();
|
|
160
97
|
</script>
|
|
161
98
|
`;
|
|
@@ -167,148 +104,122 @@ async function startDevServer(configPathOption, options = { preserve: false, por
|
|
|
167
104
|
next();
|
|
168
105
|
});
|
|
169
106
|
|
|
170
|
-
// Add Last-Modified header to all responses for polling fallback
|
|
171
|
-
app.use((req, res, next) => {
|
|
172
|
-
res.setHeader('Last-Modified', new Date().toUTCString());
|
|
173
|
-
next();
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
// Serve static files from the output directory
|
|
177
|
-
// This middleware needs to be dynamic if outputDir changes
|
|
178
107
|
let staticMiddleware = express.static(paths.outputDir);
|
|
179
108
|
app.use((req, res, next) => staticMiddleware(req, res, next));
|
|
180
109
|
|
|
181
|
-
// Initial
|
|
182
|
-
console.log('š Performing initial build
|
|
110
|
+
// --- 1. Initial Build ---
|
|
111
|
+
console.log(chalk.blue('š Performing initial build...'));
|
|
183
112
|
try {
|
|
184
|
-
await buildSite(configPathOption, { isDev: true, preserve: options.preserve, noDoubleProcessing: true });
|
|
185
|
-
console.log('ā
Initial build complete.');
|
|
113
|
+
await buildSite(configPathOption, { isDev: true, preserve: options.preserve, noDoubleProcessing: true });
|
|
186
114
|
} catch (error) {
|
|
187
|
-
console.error('ā Initial build failed:', error.message
|
|
188
|
-
// Optionally, don't start server if initial build fails, or serve a specific error page.
|
|
115
|
+
console.error(chalk.red('ā Initial build failed:'), error.message);
|
|
189
116
|
}
|
|
190
117
|
|
|
191
|
-
//
|
|
118
|
+
// --- 2. Setup Watcher & Logs ---
|
|
192
119
|
const userAssetsDirExists = await fs.pathExists(paths.userAssetsDir);
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
const watchedPaths = [
|
|
196
|
-
paths.srcDirToWatch,
|
|
197
|
-
paths.configFileToWatch,
|
|
198
|
-
];
|
|
199
|
-
|
|
200
|
-
// Add user assets directory to watched paths if it exists
|
|
201
|
-
if (userAssetsDirExists) {
|
|
202
|
-
watchedPaths.push(paths.userAssetsDir);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// Add internal paths for docmd development (not shown to end users)
|
|
206
|
-
const internalPaths = [DOCMD_TEMPLATES_DIR, DOCMD_ASSETS_DIR, DOCMD_COMMANDS_DIR, DOCMD_CORE_DIR, DOCMD_PLUGINS_DIR];
|
|
120
|
+
const watchedPaths = [paths.srcDirToWatch, paths.configFileToWatch];
|
|
121
|
+
if (userAssetsDirExists) watchedPaths.push(paths.userAssetsDir);
|
|
207
122
|
|
|
208
|
-
// Only in development environments, we might want to watch internal files too
|
|
209
123
|
if (process.env.DOCMD_DEV === 'true') {
|
|
210
|
-
watchedPaths.push(
|
|
124
|
+
watchedPaths.push(
|
|
125
|
+
path.join(DOCMD_ROOT, 'templates'),
|
|
126
|
+
path.join(DOCMD_ROOT, 'assets'),
|
|
127
|
+
path.join(DOCMD_ROOT, 'core'),
|
|
128
|
+
path.join(DOCMD_ROOT, 'plugins')
|
|
129
|
+
);
|
|
211
130
|
}
|
|
212
131
|
|
|
213
|
-
|
|
214
|
-
console.log(
|
|
215
|
-
console.log(`
|
|
132
|
+
// LOGS: Explicitly print what we are watching
|
|
133
|
+
console.log(chalk.dim('\nš Watching for changes in:'));
|
|
134
|
+
console.log(chalk.dim(` - Source: ${chalk.cyan(formatPathForDisplay(paths.srcDirToWatch, CWD))}`));
|
|
135
|
+
console.log(chalk.dim(` - Config: ${chalk.cyan(formatPathForDisplay(paths.configFileToWatch, CWD))}`));
|
|
216
136
|
if (userAssetsDirExists) {
|
|
217
|
-
console.log(`
|
|
137
|
+
console.log(chalk.dim(` - Assets: ${chalk.cyan(formatPathForDisplay(paths.userAssetsDir, CWD))}`));
|
|
218
138
|
}
|
|
219
139
|
if (process.env.DOCMD_DEV === 'true') {
|
|
220
|
-
console.log(`
|
|
221
|
-
console.log(` - docmd Assets: ${formatPathForDisplay(DOCMD_ASSETS_DIR, CWD)} (internal)`);
|
|
140
|
+
console.log(chalk.dim(` - docmd Internal: ${chalk.magenta(formatPathForDisplay(DOCMD_ROOT, CWD))}`));
|
|
222
141
|
}
|
|
142
|
+
console.log('');
|
|
223
143
|
|
|
224
144
|
const watcher = chokidar.watch(watchedPaths, {
|
|
225
|
-
ignored: /(^|[\/\\])\../,
|
|
145
|
+
ignored: /(^|[\/\\])\../,
|
|
226
146
|
persistent: true,
|
|
227
|
-
ignoreInitial: true,
|
|
228
|
-
awaitWriteFinish: {
|
|
229
|
-
stabilityThreshold: 100,
|
|
230
|
-
pollInterval: 100
|
|
231
|
-
}
|
|
147
|
+
ignoreInitial: true,
|
|
148
|
+
awaitWriteFinish: { stabilityThreshold: 100, pollInterval: 100 }
|
|
232
149
|
});
|
|
233
150
|
|
|
234
151
|
watcher.on('all', async (event, filePath) => {
|
|
235
152
|
const relativeFilePath = path.relative(CWD, filePath);
|
|
236
|
-
|
|
153
|
+
process.stdout.write(chalk.dim(`ā» Change in ${relativeFilePath}... `));
|
|
154
|
+
|
|
237
155
|
try {
|
|
238
156
|
if (filePath === paths.configFileToWatch) {
|
|
239
|
-
|
|
240
|
-
config = await loadConfig(configPathOption); // Reload config
|
|
157
|
+
config = await loadConfig(configPathOption);
|
|
241
158
|
const newPaths = resolveConfigPaths(config);
|
|
242
|
-
|
|
243
|
-
// Update watcher if srcDir changed - Chokidar doesn't easily support dynamic path changes after init.
|
|
244
|
-
// For simplicity, we might need to restart the watcher or inform user to restart dev server if srcDir/outputDir change.
|
|
245
|
-
// For now, we'll at least update the static server path.
|
|
246
159
|
if (newPaths.outputDir !== paths.outputDir) {
|
|
247
|
-
console.log(`Output directory changed from ${formatPathForDisplay(paths.outputDir, CWD)} to ${formatPathForDisplay(newPaths.outputDir, CWD)}. Updating static server.`);
|
|
248
160
|
staticMiddleware = express.static(newPaths.outputDir);
|
|
249
161
|
}
|
|
250
|
-
|
|
251
|
-
// A full dev server restart would be more robust for such config changes.
|
|
252
|
-
// For now, the old srcDir will still be watched.
|
|
253
|
-
paths = newPaths; // Update paths for next build reference
|
|
162
|
+
paths = newPaths;
|
|
254
163
|
}
|
|
255
164
|
|
|
256
|
-
await buildSite(configPathOption, { isDev: true, preserve: options.preserve, noDoubleProcessing: true });
|
|
165
|
+
await buildSite(configPathOption, { isDev: true, preserve: options.preserve, noDoubleProcessing: true });
|
|
257
166
|
broadcastReload();
|
|
258
|
-
|
|
167
|
+
process.stdout.write(chalk.green('Done.\n'));
|
|
168
|
+
|
|
259
169
|
} catch (error) {
|
|
260
|
-
console.error('ā Rebuild failed:', error.message
|
|
170
|
+
console.error(chalk.red('\nā Rebuild failed:'), error.message);
|
|
261
171
|
}
|
|
262
172
|
});
|
|
263
173
|
|
|
264
|
-
watcher.on('error', error => console.error(`Watcher error: ${error}`));
|
|
265
|
-
|
|
266
|
-
// Try different ports if the default port is in use
|
|
267
174
|
const PORT = options.port || process.env.PORT || 3000;
|
|
268
175
|
const MAX_PORT_ATTEMPTS = 10;
|
|
269
|
-
let currentPort = parseInt(PORT, 10);
|
|
270
176
|
|
|
271
|
-
// Function to try starting the server on different ports
|
|
272
177
|
function tryStartServer(port, attempt = 1) {
|
|
273
|
-
|
|
178
|
+
// 0.0.0.0 allows network access
|
|
179
|
+
server.listen(port, '0.0.0.0')
|
|
274
180
|
.on('listening', async () => {
|
|
275
|
-
// Check if index.html exists after initial build
|
|
276
181
|
const indexHtmlPath = path.join(paths.outputDir, 'index.html');
|
|
182
|
+
const networkIp = getNetworkIp();
|
|
183
|
+
|
|
184
|
+
// Use 127.0.0.1 explicitly
|
|
185
|
+
const localUrl = `http://127.0.0.1:${port}`;
|
|
186
|
+
const networkUrl = networkIp ? `http://${networkIp}:${port}` : null;
|
|
187
|
+
|
|
188
|
+
const border = chalk.gray('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
189
|
+
console.log(border);
|
|
190
|
+
console.log(` ${chalk.bold.green('SERVER RUNNING')} ${chalk.dim(`(v${require('../../package.json').version})`)}`);
|
|
191
|
+
console.log('');
|
|
192
|
+
console.log(` ${chalk.bold('Local:')} ${chalk.cyan(localUrl)}`);
|
|
193
|
+
if (networkUrl) {
|
|
194
|
+
console.log(` ${chalk.bold('Network:')} ${chalk.cyan(networkUrl)}`);
|
|
195
|
+
}
|
|
196
|
+
console.log('');
|
|
197
|
+
console.log(` ${chalk.dim('Serving:')} ${formatPathForDisplay(paths.outputDir, CWD)}`);
|
|
198
|
+
console.log(border);
|
|
199
|
+
console.log('');
|
|
200
|
+
|
|
277
201
|
if (!await fs.pathExists(indexHtmlPath)) {
|
|
278
|
-
console.warn(`ā ļø Warning:
|
|
279
|
-
The dev server is running, but you might see a 404 for the root page.
|
|
280
|
-
Ensure your '${config.srcDir}' directory contains an 'index.md' or your navigation points to existing files.`);
|
|
202
|
+
console.warn(chalk.yellow(`ā ļø Warning: Root index.html not found.`));
|
|
281
203
|
}
|
|
282
|
-
console.log(`š Dev server started at http://localhost:${port}`);
|
|
283
|
-
console.log(`Serving content from: ${formatPathForDisplay(paths.outputDir, CWD)}`);
|
|
284
|
-
console.log(`Live reload is active. Browser will refresh automatically when files change.`);
|
|
285
204
|
})
|
|
286
205
|
.on('error', (err) => {
|
|
287
206
|
if (err.code === 'EADDRINUSE' && attempt < MAX_PORT_ATTEMPTS) {
|
|
288
|
-
console.log(`Port ${port} is in use, trying port ${port + 1}...`);
|
|
289
|
-
server.close();
|
|
290
207
|
tryStartServer(port + 1, attempt + 1);
|
|
291
208
|
} else {
|
|
292
|
-
console.error(`Failed to start server: ${err.message}`);
|
|
209
|
+
console.error(chalk.red(`Failed to start server: ${err.message}`));
|
|
293
210
|
process.exit(1);
|
|
294
211
|
}
|
|
295
212
|
});
|
|
296
213
|
}
|
|
297
214
|
|
|
298
|
-
|
|
299
|
-
tryStartServer(currentPort);
|
|
215
|
+
tryStartServer(parseInt(PORT, 10));
|
|
300
216
|
|
|
301
|
-
// Graceful shutdown
|
|
302
217
|
process.on('SIGINT', () => {
|
|
303
|
-
console.log('\nš Shutting down
|
|
218
|
+
console.log(chalk.yellow('\nš Shutting down...'));
|
|
304
219
|
watcher.close();
|
|
305
|
-
|
|
306
|
-
server.close(() => {
|
|
307
|
-
console.log('Server closed.');
|
|
308
|
-
process.exit(0);
|
|
309
|
-
});
|
|
310
|
-
});
|
|
220
|
+
process.exit(0);
|
|
311
221
|
});
|
|
312
222
|
}
|
|
313
223
|
|
|
224
|
+
// Ensure this export is here!
|
|
314
225
|
module.exports = { startDevServer };
|
package/src/commands/init.js
CHANGED
|
@@ -14,10 +14,10 @@ module.exports = {
|
|
|
14
14
|
|
|
15
15
|
// Logo Configuration
|
|
16
16
|
logo: {
|
|
17
|
-
light: '
|
|
18
|
-
dark: '
|
|
17
|
+
light: 'assets/images/docmd-logo-light.png', // Path relative to outputDir root
|
|
18
|
+
dark: 'assets/images/docmd-logo-dark.png', // Path relative to outputDir root
|
|
19
19
|
alt: 'docmd logo', // Alt text for the logo
|
|
20
|
-
href: '
|
|
20
|
+
href: './', // Link for the logo, defaults to site root
|
|
21
21
|
},
|
|
22
22
|
|
|
23
23
|
// Directory Configuration
|
|
@@ -44,14 +44,14 @@ module.exports = {
|
|
|
44
44
|
positionMode: 'top', // 'top' or 'bottom' for the theme toggle
|
|
45
45
|
codeHighlight: true, // Enable/disable codeblock highlighting and import of highlight.js
|
|
46
46
|
customCss: [ // Array of paths to custom CSS files
|
|
47
|
-
// '
|
|
47
|
+
// 'assets/css/custom.css', // Custom TOC styles
|
|
48
48
|
]
|
|
49
49
|
},
|
|
50
50
|
|
|
51
51
|
// Custom JavaScript Files
|
|
52
52
|
customJs: [ // Array of paths to custom JS files, loaded at end of body
|
|
53
|
-
// '
|
|
54
|
-
'
|
|
53
|
+
// 'assets/js/custom-script.js', // Paths relative to outputDir root
|
|
54
|
+
'assets/js/docmd-image-lightbox.js', // Image lightbox functionality
|
|
55
55
|
],
|
|
56
56
|
|
|
57
57
|
// Content Processing
|
|
@@ -71,7 +71,7 @@ module.exports = {
|
|
|
71
71
|
// siteName: 'docmd Documentation', // Optional, defaults to config.siteTitle
|
|
72
72
|
// Default image for og:image if not specified in page frontmatter
|
|
73
73
|
// Path relative to outputDir root
|
|
74
|
-
defaultImage: '
|
|
74
|
+
defaultImage: 'assets/images/docmd-preview.png',
|
|
75
75
|
},
|
|
76
76
|
twitter: { // For Twitter Cards
|
|
77
77
|
cardType: 'summary_large_image', // 'summary', 'summary_large_image'
|
|
@@ -139,7 +139,7 @@ module.exports = {
|
|
|
139
139
|
|
|
140
140
|
// Favicon Configuration
|
|
141
141
|
// Path relative to outputDir root
|
|
142
|
-
favicon: '
|
|
142
|
+
favicon: 'assets/favicon.ico',
|
|
143
143
|
};
|
|
144
144
|
`;
|
|
145
145
|
|
|
@@ -5,12 +5,32 @@ const fs = require('fs-extra');
|
|
|
5
5
|
const { validateConfig } = require('./config-validator');
|
|
6
6
|
|
|
7
7
|
async function loadConfig(configPath) {
|
|
8
|
-
const
|
|
8
|
+
const cwd = process.cwd();
|
|
9
|
+
let absoluteConfigPath = path.resolve(cwd, configPath);
|
|
10
|
+
|
|
11
|
+
// 1. Check if the requested config file exists
|
|
9
12
|
if (!await fs.pathExists(absoluteConfigPath)) {
|
|
10
|
-
|
|
13
|
+
// 2. Fallback Logic:
|
|
14
|
+
// If the user didn't specify a custom path (i.e., using default 'docmd.config.js')
|
|
15
|
+
// AND 'docmd.config.js' is missing...
|
|
16
|
+
// Check if legacy 'config.js' exists.
|
|
17
|
+
if (configPath === 'docmd.config.js') {
|
|
18
|
+
const legacyPath = path.resolve(cwd, 'config.js');
|
|
19
|
+
if (await fs.pathExists(legacyPath)) {
|
|
20
|
+
// console.log('ā ļø Using legacy config.js. Please rename to docmd.config.js'); // Optional warning
|
|
21
|
+
absoluteConfigPath = legacyPath;
|
|
22
|
+
} else {
|
|
23
|
+
// Neither exists
|
|
24
|
+
throw new Error(`Configuration file not found at: ${absoluteConfigPath}\nRun "docmd init" to create one.`);
|
|
25
|
+
}
|
|
26
|
+
} else {
|
|
27
|
+
// User specified a custom path that doesn't exist
|
|
28
|
+
throw new Error(`Configuration file not found at: ${absoluteConfigPath}`);
|
|
29
|
+
}
|
|
11
30
|
}
|
|
31
|
+
|
|
12
32
|
try {
|
|
13
|
-
// Clear require cache to always get the freshest config
|
|
33
|
+
// Clear require cache to always get the freshest config (important for dev mode reloading)
|
|
14
34
|
delete require.cache[require.resolve(absoluteConfigPath)];
|
|
15
35
|
const config = require(absoluteConfigPath);
|
|
16
36
|
|
|
@@ -34,12 +34,17 @@ function formatPathForDisplay(absolutePath) {
|
|
|
34
34
|
|
|
35
35
|
async function processMarkdownFile(filePath, md, config) {
|
|
36
36
|
const rawContent = await fs.readFile(filePath, 'utf8');
|
|
37
|
+
return processMarkdownContent(rawContent, md, config, filePath);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Pure logic, no file reading (Used by Live Editor)
|
|
41
|
+
function processMarkdownContent(rawContent, md, config, filePath = 'memory') {
|
|
37
42
|
let frontmatter, markdownContent;
|
|
38
43
|
|
|
39
44
|
try {
|
|
40
45
|
({ data: frontmatter, content: markdownContent } = matter(rawContent));
|
|
41
46
|
} catch (e) {
|
|
42
|
-
console.error(`ā Error parsing frontmatter in ${formatPathForDisplay(filePath)}:`);
|
|
47
|
+
console.error(`ā Error parsing frontmatter in ${filePath === 'memory' ? 'content' : formatPathForDisplay(filePath)}:`);
|
|
43
48
|
console.error(` ${e.message}`);
|
|
44
49
|
return null;
|
|
45
50
|
}
|
|
@@ -70,7 +75,7 @@ async function processMarkdownFile(filePath, md, config) {
|
|
|
70
75
|
|
|
71
76
|
return { frontmatter, htmlContent, headings, searchData };
|
|
72
77
|
}
|
|
73
|
-
|
|
78
|
+
|
|
74
79
|
async function findMarkdownFiles(dir) {
|
|
75
80
|
let files = [];
|
|
76
81
|
const items = await fs.readdir(dir, { withFileTypes: true });
|
|
@@ -87,6 +92,7 @@ async function findMarkdownFiles(dir) {
|
|
|
87
92
|
|
|
88
93
|
module.exports = {
|
|
89
94
|
processMarkdownFile,
|
|
95
|
+
processMarkdownContent,
|
|
90
96
|
createMarkdownItInstance,
|
|
91
97
|
extractHeadingsFromHtml,
|
|
92
98
|
findMarkdownFiles
|