@mgks/docmd 0.1.1 → 0.1.3

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.
@@ -8,6 +8,25 @@ const fs = require('fs-extra');
8
8
  const { buildSite } = require('./build'); // Re-use the build logic
9
9
  const { loadConfig } = require('../core/config-loader');
10
10
 
11
+ /**
12
+ * Format paths for display to make them relative to CWD
13
+ * @param {string} absolutePath - The absolute path to format
14
+ * @param {string} cwd - Current working directory
15
+ * @returns {string} - Formatted relative path
16
+ */
17
+ function formatPathForDisplay(absolutePath, cwd) {
18
+ // Get the relative path from CWD
19
+ const relativePath = path.relative(cwd, absolutePath);
20
+
21
+ // If it's not a subdirectory, prefix with ./ for clarity
22
+ if (!relativePath.startsWith('..') && !path.isAbsolute(relativePath)) {
23
+ return `./${relativePath}`;
24
+ }
25
+
26
+ // Return the relative path
27
+ return relativePath;
28
+ }
29
+
11
30
  async function startDevServer(configPathOption, options = { preserve: false }) {
12
31
  let config = await loadConfig(configPathOption); // Load initial config
13
32
  const CWD = process.cwd(); // Current Working Directory where user runs `docmd dev`
@@ -18,6 +37,7 @@ async function startDevServer(configPathOption, options = { preserve: false }) {
18
37
  outputDir: path.resolve(CWD, currentConfig.outputDir),
19
38
  srcDirToWatch: path.resolve(CWD, currentConfig.srcDir),
20
39
  configFileToWatch: path.resolve(CWD, configPathOption), // Path to the config file itself
40
+ userAssetsDir: path.resolve(CWD, 'assets'), // User's assets directory
21
41
  };
22
42
  };
23
43
 
@@ -47,29 +67,92 @@ async function startDevServer(configPathOption, options = { preserve: false }) {
47
67
  console.error('WebSocket Server error:', error);
48
68
  });
49
69
 
50
-
51
70
  function broadcastReload() {
52
- // console.log('Broadcasting reload to', wsClients.size, 'clients');
53
71
  wsClients.forEach(client => {
54
72
  if (client.readyState === WebSocket.OPEN) {
55
- client.send('reload');
73
+ try {
74
+ client.send('reload');
75
+ } catch (error) {
76
+ console.error('Error sending reload command to client:', error);
77
+ }
56
78
  }
57
79
  });
58
80
  }
59
81
 
60
- // Inject live reload script into HTML
82
+ // Inject live reload script into HTML responses
61
83
  app.use((req, res, next) => {
62
- if (req.path.endsWith('.html')) {
84
+ if (req.path.endsWith('.html') || !req.path.includes('.')) {
63
85
  const originalSend = res.send;
64
- res.send = function (body) {
65
- if (typeof body === 'string') {
86
+ res.send = function(body) {
87
+ if (typeof body === 'string' && body.includes('</body>')) {
66
88
  const liveReloadScript = `
67
89
  <script>
68
- const socket = new WebSocket(\`ws://\${window.location.host}\`);
69
- socket.onmessage = function(event) { if (event.data === 'reload') window.location.reload(); };
70
- socket.onerror = function(error) { console.error('WebSocket Client Error:', error); };
71
- // socket.onopen = function() { console.log('WebSocket Client Connected'); };
72
- // socket.onclose = function() { console.log('WebSocket Client Disconnected'); };
90
+ (function() {
91
+ // More robust WebSocket connection with automatic reconnection
92
+ let socket;
93
+ let reconnectAttempts = 0;
94
+ const maxReconnectAttempts = 5;
95
+ const reconnectDelay = 1000; // Start with 1 second delay
96
+
97
+ function connect() {
98
+ socket = new WebSocket(\`ws://\${window.location.host}\`);
99
+
100
+ socket.onmessage = function(event) {
101
+ if (event.data === 'reload') {
102
+ console.log('Received reload signal. Refreshing page...');
103
+ window.location.reload();
104
+ }
105
+ };
106
+
107
+ socket.onopen = function() {
108
+ console.log('Live reload connected.');
109
+ reconnectAttempts = 0; // Reset reconnect counter on successful connection
110
+ };
111
+
112
+ socket.onclose = function() {
113
+ if (reconnectAttempts < maxReconnectAttempts) {
114
+ reconnectAttempts++;
115
+ const delay = reconnectDelay * Math.pow(1.5, reconnectAttempts - 1); // Exponential backoff
116
+ console.log(\`Live reload disconnected. Reconnecting in \${delay/1000} seconds...\`);
117
+ setTimeout(connect, delay);
118
+ } else {
119
+ console.log('Live reload disconnected. Max reconnect attempts reached.');
120
+ }
121
+ };
122
+
123
+ socket.onerror = function(error) {
124
+ console.error('WebSocket error:', error);
125
+ };
126
+ }
127
+
128
+ // Initial connection
129
+ connect();
130
+
131
+ // Backup reload mechanism using polling for browsers with WebSocket issues
132
+ let lastModified = new Date().getTime();
133
+ const pollInterval = 2000; // Poll every 2 seconds
134
+
135
+ function checkForChanges() {
136
+ fetch(window.location.href, { method: 'HEAD', cache: 'no-store' })
137
+ .then(response => {
138
+ const serverLastModified = new Date(response.headers.get('Last-Modified')).getTime();
139
+ if (serverLastModified > lastModified) {
140
+ console.log('Change detected via polling. Refreshing page...');
141
+ window.location.reload();
142
+ }
143
+ lastModified = serverLastModified;
144
+ })
145
+ .catch(error => console.error('Error checking for changes:', error));
146
+ }
147
+
148
+ // Only use polling as a fallback if WebSocket fails
149
+ setTimeout(() => {
150
+ if (socket.readyState !== WebSocket.OPEN) {
151
+ console.log('WebSocket not connected. Falling back to polling.');
152
+ setInterval(checkForChanges, pollInterval);
153
+ }
154
+ }, 5000);
155
+ })();
73
156
  </script>
74
157
  `;
75
158
  body = body.replace('</body>', `${liveReloadScript}</body>`);
@@ -80,6 +163,12 @@ async function startDevServer(configPathOption, options = { preserve: false }) {
80
163
  next();
81
164
  });
82
165
 
166
+ // Add Last-Modified header to all responses for polling fallback
167
+ app.use((req, res, next) => {
168
+ res.setHeader('Last-Modified', new Date().toUTCString());
169
+ next();
170
+ });
171
+
83
172
  // Serve static files from the output directory
84
173
  // This middleware needs to be dynamic if outputDir changes
85
174
  let staticMiddleware = express.static(paths.outputDir);
@@ -88,28 +177,45 @@ async function startDevServer(configPathOption, options = { preserve: false }) {
88
177
  // Initial build
89
178
  console.log('🚀 Performing initial build for dev server...');
90
179
  try {
91
- await buildSite(configPathOption, { isDev: true, preserve: options.preserve }); // Use the original config path option
180
+ await buildSite(configPathOption, { isDev: true, preserve: options.preserve, noDoubleProcessing: true }); // Use the original config path option
92
181
  console.log('✅ Initial build complete.');
93
182
  } catch (error) {
94
183
  console.error('❌ Initial build failed:', error.message, error.stack);
95
184
  // Optionally, don't start server if initial build fails, or serve a specific error page.
96
185
  }
97
186
 
187
+ // Check if user assets directory exists
188
+ const userAssetsDirExists = await fs.pathExists(paths.userAssetsDir);
98
189
 
99
190
  // Watch for changes
100
191
  const watchedPaths = [
101
192
  paths.srcDirToWatch,
102
193
  paths.configFileToWatch,
103
- DOCMD_TEMPLATES_DIR,
104
- DOCMD_ASSETS_DIR
105
194
  ];
106
195
 
107
- console.log(`👀 Watching for changes in:
108
- - Source: ${paths.srcDirToWatch}
109
- - Config: ${paths.configFileToWatch}
110
- - docmd Templates: ${DOCMD_TEMPLATES_DIR} (internal)
111
- - docmd Assets: ${DOCMD_ASSETS_DIR} (internal)
112
- `);
196
+ // Add user assets directory to watched paths if it exists
197
+ if (userAssetsDirExists) {
198
+ watchedPaths.push(paths.userAssetsDir);
199
+ }
200
+
201
+ // Add internal paths for docmd development (not shown to end users)
202
+ const internalPaths = [DOCMD_TEMPLATES_DIR, DOCMD_ASSETS_DIR];
203
+
204
+ // Only in development environments, we might want to watch internal files too
205
+ if (process.env.DOCMD_DEV === 'true') {
206
+ watchedPaths.push(...internalPaths);
207
+ }
208
+
209
+ console.log(`👀 Watching for changes in:`);
210
+ console.log(` - Source: ${formatPathForDisplay(paths.srcDirToWatch, CWD)}`);
211
+ console.log(` - Config: ${formatPathForDisplay(paths.configFileToWatch, CWD)}`);
212
+ if (userAssetsDirExists) {
213
+ console.log(` - Assets: ${formatPathForDisplay(paths.userAssetsDir, CWD)}`);
214
+ }
215
+ if (process.env.DOCMD_DEV === 'true') {
216
+ console.log(` - docmd Templates: ${formatPathForDisplay(DOCMD_TEMPLATES_DIR, CWD)} (internal)`);
217
+ console.log(` - docmd Assets: ${formatPathForDisplay(DOCMD_ASSETS_DIR, CWD)} (internal)`);
218
+ }
113
219
 
114
220
  const watcher = chokidar.watch(watchedPaths, {
115
221
  ignored: /(^|[\/\\])\../, // ignore dotfiles
@@ -134,7 +240,7 @@ async function startDevServer(configPathOption, options = { preserve: false }) {
134
240
  // For simplicity, we might need to restart the watcher or inform user to restart dev server if srcDir/outputDir change.
135
241
  // For now, we'll at least update the static server path.
136
242
  if (newPaths.outputDir !== paths.outputDir) {
137
- console.log(`Output directory changed from ${paths.outputDir} to ${newPaths.outputDir}. Updating static server.`);
243
+ console.log(`Output directory changed from ${formatPathForDisplay(paths.outputDir, CWD)} to ${formatPathForDisplay(newPaths.outputDir, CWD)}. Updating static server.`);
138
244
  staticMiddleware = express.static(newPaths.outputDir);
139
245
  }
140
246
  // If srcDirToWatch changes, chokidar won't automatically pick it up.
@@ -143,9 +249,9 @@ async function startDevServer(configPathOption, options = { preserve: false }) {
143
249
  paths = newPaths; // Update paths for next build reference
144
250
  }
145
251
 
146
- await buildSite(configPathOption, { isDev: true, preserve: options.preserve }); // Re-build using the potentially updated config path
252
+ await buildSite(configPathOption, { isDev: true, preserve: options.preserve, noDoubleProcessing: true }); // Re-build using the potentially updated config path
147
253
  broadcastReload();
148
- console.log('✅ Rebuild complete. Browser should refresh.');
254
+ console.log('✅ Rebuild complete.');
149
255
  } catch (error) {
150
256
  console.error('❌ Rebuild failed:', error.message, error.stack);
151
257
  }
@@ -153,18 +259,40 @@ async function startDevServer(configPathOption, options = { preserve: false }) {
153
259
 
154
260
  watcher.on('error', error => console.error(`Watcher error: ${error}`));
155
261
 
262
+ // Try different ports if the default port is in use
156
263
  const PORT = process.env.PORT || 3000;
157
- server.listen(PORT, async () => {
158
- // Check if index.html exists after initial build
159
- const indexHtmlPath = path.join(paths.outputDir, 'index.html');
160
- if (!await fs.pathExists(indexHtmlPath)) {
161
- console.warn(`⚠️ Warning: ${indexHtmlPath} not found after initial build.
162
- The dev server is running, but you might see a 404 for the root page.
163
- Ensure your '${config.srcDir}' directory contains an 'index.md' or your navigation points to existing files.`);
164
- }
165
- console.log(`🎉 Dev server started at http://localhost:${PORT}`);
166
- console.log(`Serving content from: ${paths.outputDir}`);
167
- });
264
+ const MAX_PORT_ATTEMPTS = 10;
265
+ let currentPort = PORT;
266
+
267
+ // Function to try starting the server on different ports
268
+ function tryStartServer(port, attempt = 1) {
269
+ server.listen(port)
270
+ .on('listening', async () => {
271
+ // Check if index.html exists after initial build
272
+ const indexHtmlPath = path.join(paths.outputDir, 'index.html');
273
+ if (!await fs.pathExists(indexHtmlPath)) {
274
+ console.warn(`⚠️ Warning: ${formatPathForDisplay(indexHtmlPath, CWD)} not found after initial build.
275
+ The dev server is running, but you might see a 404 for the root page.
276
+ Ensure your '${config.srcDir}' directory contains an 'index.md' or your navigation points to existing files.`);
277
+ }
278
+ console.log(`🎉 Dev server started at http://localhost:${port}`);
279
+ console.log(`Serving content from: ${formatPathForDisplay(paths.outputDir, CWD)}`);
280
+ console.log(`Live reload is active. Browser will refresh automatically when files change.`);
281
+ })
282
+ .on('error', (err) => {
283
+ if (err.code === 'EADDRINUSE' && attempt < MAX_PORT_ATTEMPTS) {
284
+ console.log(`Port ${port} is in use, trying port ${port + 1}...`);
285
+ server.close();
286
+ tryStartServer(port + 1, attempt + 1);
287
+ } else {
288
+ console.error(`Failed to start server: ${err.message}`);
289
+ process.exit(1);
290
+ }
291
+ });
292
+ }
293
+
294
+ // Start the server with port fallback
295
+ tryStartServer(currentPort);
168
296
 
169
297
  // Graceful shutdown
170
298
  process.on('SIGINT', () => {
@@ -1,5 +1,6 @@
1
1
  const fs = require('fs-extra');
2
2
  const path = require('path');
3
+ const readline = require('readline');
3
4
 
4
5
  const defaultConfigContent = `// config.js: basic config for docmd
5
6
  module.exports = {
@@ -76,9 +77,18 @@ module.exports = {
76
77
  // Icons are kebab-case names from Lucide Icons (https://lucide.dev/)
77
78
  navigation: [
78
79
  { title: 'Welcome', path: '/', icon: 'home' }, // Corresponds to docs/index.md
80
+ {
81
+ title: 'Getting Started',
82
+ icon: 'rocket',
83
+ path: '#',
84
+ children: [
85
+ { title: 'Documentation', path: 'https://docmd.mgks.dev', icon: 'scroll', external: true },
86
+ { title: 'Installation', path: 'https://docmd.mgks.dev/getting-started/installation', icon: 'download', external: true },
87
+ { title: 'Basic Usage', path: 'https://docmd.mgks.dev/getting-started/basic-usage', icon: 'play', external: true },
88
+ ],
89
+ },
79
90
  // External links:
80
91
  { title: 'GitHub', path: 'https://github.com/mgks/docmd', icon: 'github', external: true },
81
- { title: 'Documentation', path: 'https://github.com/mgks/docmd', icon: 'scroll', external: true }
82
92
  ],
83
93
 
84
94
  // Footer Configuration
@@ -106,16 +116,117 @@ async function initProject() {
106
116
  const docsDir = path.join(baseDir, 'docs');
107
117
  const configFile = path.join(baseDir, 'config.js');
108
118
  const indexMdFile = path.join(docsDir, 'index.md');
119
+ const assetsDir = path.join(baseDir, 'assets');
120
+ const assetsCssDir = path.join(assetsDir, 'css');
121
+ const assetsJsDir = path.join(assetsDir, 'js');
122
+ const assetsImagesDir = path.join(assetsDir, 'images');
123
+
124
+ const existingFiles = [];
125
+ const dirExists = {
126
+ docs: false,
127
+ assets: false
128
+ };
129
+
130
+ // Check each file individually
131
+ if (await fs.pathExists(configFile)) {
132
+ existingFiles.push('config.js');
133
+ }
134
+
135
+ if (await fs.pathExists(docsDir)) {
136
+ dirExists.docs = true;
137
+
138
+ if (await fs.pathExists(indexMdFile)) {
139
+ existingFiles.push('docs/index.md');
140
+ }
141
+ }
109
142
 
110
- if (await fs.pathExists(configFile) || await fs.pathExists(docsDir)) {
111
- console.warn('⚠️ `docs/` directory or `config.js` already exists. Skipping creation to avoid overwriting.');
112
- } else {
143
+ // Check if assets directory exists
144
+ if (await fs.pathExists(assetsDir)) {
145
+ dirExists.assets = true;
146
+ }
147
+
148
+ // Determine if we should override existing files
149
+ let shouldOverride = false;
150
+ if (existingFiles.length > 0) {
151
+ console.warn('⚠️ The following files already exist:');
152
+ existingFiles.forEach(file => console.warn(` - ${file}`));
153
+
154
+ const rl = readline.createInterface({
155
+ input: process.stdin,
156
+ output: process.stdout
157
+ });
158
+
159
+ const answer = await new Promise(resolve => {
160
+ rl.question('Do you want to override these files? (y/N): ', resolve);
161
+ });
162
+
163
+ rl.close();
164
+
165
+ shouldOverride = answer.toLowerCase() === 'y';
166
+
167
+ if (!shouldOverride) {
168
+ console.log('⏭️ Skipping existing files. Will only create new files.');
169
+ }
170
+ }
171
+
172
+ // Create docs directory if it doesn't exist
173
+ if (!dirExists.docs) {
113
174
  await fs.ensureDir(docsDir);
175
+ console.log('📁 Created `docs/` directory');
176
+ } else {
177
+ console.log('📁 Using existing `docs/` directory');
178
+ }
179
+
180
+ // Create assets directory structure if it doesn't exist
181
+ if (!dirExists.assets) {
182
+ await fs.ensureDir(assetsDir);
183
+ await fs.ensureDir(assetsCssDir);
184
+ await fs.ensureDir(assetsJsDir);
185
+ await fs.ensureDir(assetsImagesDir);
186
+ console.log('📁 Created `assets/` directory with css, js, and images subdirectories');
187
+ } else {
188
+ console.log('📁 Using existing `assets/` directory');
189
+
190
+ // Create subdirectories if they don't exist
191
+ if (!await fs.pathExists(assetsCssDir)) {
192
+ await fs.ensureDir(assetsCssDir);
193
+ console.log('📁 Created `assets/css/` directory');
194
+ }
195
+
196
+ if (!await fs.pathExists(assetsJsDir)) {
197
+ await fs.ensureDir(assetsJsDir);
198
+ console.log('📁 Created `assets/js/` directory');
199
+ }
200
+
201
+ if (!await fs.pathExists(assetsImagesDir)) {
202
+ await fs.ensureDir(assetsImagesDir);
203
+ console.log('📁 Created `assets/images/` directory');
204
+ }
205
+ }
206
+
207
+ // Write config file if it doesn't exist or user confirmed override
208
+ if (!await fs.pathExists(configFile)) {
114
209
  await fs.writeFile(configFile, defaultConfigContent, 'utf8');
115
- await fs.writeFile(indexMdFile, defaultIndexMdContent, 'utf8');
116
210
  console.log('📄 Created `config.js`');
117
- console.log('📁 Created `docs/` directory with a sample `index.md`');
211
+ } else if (shouldOverride) {
212
+ await fs.writeFile(configFile, defaultConfigContent, 'utf8');
213
+ console.log('📄 Updated `config.js`');
214
+ } else {
215
+ console.log('⏭️ Skipped existing `config.js`');
216
+ }
217
+
218
+ // Write index.md file if it doesn't exist or user confirmed override
219
+ if (!await fs.pathExists(indexMdFile)) {
220
+ await fs.writeFile(indexMdFile, defaultIndexMdContent, 'utf8');
221
+ console.log('📄 Created `docs/index.md`');
222
+ } else if (shouldOverride) {
223
+ await fs.writeFile(indexMdFile, defaultIndexMdContent, 'utf8');
224
+ console.log('📄 Updated `docs/index.md`');
225
+ } else {
226
+ console.log('⏭️ Skipped existing `docs/index.md`');
118
227
  }
228
+
229
+ console.log('✅ Project initialization complete!');
119
230
  }
120
231
 
121
232
  module.exports = { initProject };
@@ -5,6 +5,21 @@ const matter = require('gray-matter');
5
5
  const hljs = require('highlight.js');
6
6
  const container = require('markdown-it-container');
7
7
  const attrs = require('markdown-it-attrs');
8
+ const path = require('path'); // Add path module for findMarkdownFiles
9
+
10
+ // Function to format paths for display (relative to CWD)
11
+ function formatPathForDisplay(absolutePath) {
12
+ const CWD = process.cwd();
13
+ const relativePath = path.relative(CWD, absolutePath);
14
+
15
+ // If it's not a subdirectory, prefix with ./ for clarity
16
+ if (!relativePath.startsWith('..') && !path.isAbsolute(relativePath)) {
17
+ return `./${relativePath}`;
18
+ }
19
+
20
+ // Return the relative path
21
+ return relativePath;
22
+ }
8
23
 
9
24
  const md = new MarkdownIt({
10
25
  html: true,
@@ -335,7 +350,7 @@ function extractHeadingsFromHtml(htmlContent) {
335
350
  return headings;
336
351
  }
337
352
 
338
- async function processMarkdownFile(filePath) {
353
+ async function processMarkdownFile(filePath, options = { isDev: false }) {
339
354
  const rawContent = await fs.readFile(filePath, 'utf8');
340
355
  let frontmatter, markdownContent;
341
356
 
@@ -346,21 +361,43 @@ async function processMarkdownFile(filePath) {
346
361
  } catch (e) {
347
362
  if (e.name === 'YAMLException') {
348
363
  // Provide more specific error for YAML parsing issues
349
- const errorMessage = `Error parsing YAML frontmatter in ${filePath}: ${e.reason || e.message}${e.mark ? ` at line ${e.mark.line + 1}, column ${e.mark.column + 1}` : ''}. Please check the syntax.`;
364
+ const errorMessage = `Error parsing YAML frontmatter in ${formatPathForDisplay(filePath)}: ${e.reason || e.message}${e.mark ? ` at line ${e.mark.line + 1}, column ${e.mark.column + 1}` : ''}. Please check the syntax.`;
350
365
  console.error(`❌ ${errorMessage}`);
351
366
  throw new Error(errorMessage); // Propagate error to stop build/dev
352
367
  }
353
368
  // For other errors from gray-matter or unknown errors
354
- console.error(`❌ Error processing frontmatter in ${filePath}: ${e.message}`);
369
+ console.error(`❌ Error processing frontmatter in ${formatPathForDisplay(filePath)}: ${e.message}`);
355
370
  throw e;
356
371
  }
357
372
 
358
373
  if (!frontmatter.title) {
359
- console.warn(`⚠️ Warning: Markdown file ${filePath} is missing a 'title' in its frontmatter. Using filename as fallback.`);
374
+ console.warn(`⚠️ Warning: Markdown file ${formatPathForDisplay(filePath)} is missing a 'title' in its frontmatter. Using filename as fallback.`);
360
375
  // Fallback title, or you could make it an error
361
376
  // frontmatter.title = path.basename(filePath, path.extname(filePath));
362
377
  }
363
378
 
379
+ // Special handling for no-style pages with HTML content
380
+ if (frontmatter.noStyle === true) {
381
+ // Only log when not in dev mode to reduce console output during dev
382
+ if (!options.isDev) {
383
+ console.log(`📄 Processing no-style page: ${formatPathForDisplay(filePath)} - Using raw HTML content`);
384
+ }
385
+
386
+ // For no-style pages, we'll use the raw content directly
387
+ // No markdown processing, no HTML escaping
388
+ const htmlContent = markdownContent;
389
+
390
+ // Extract headings for table of contents (if needed)
391
+ const headings = extractHeadingsFromHtml(htmlContent);
392
+
393
+ return {
394
+ frontmatter,
395
+ htmlContent,
396
+ headings,
397
+ };
398
+ }
399
+
400
+ // Regular processing for standard pages
364
401
  // Check if this is a documentation example showing how to use containers
365
402
  const isContainerDocumentation = markdownContent.includes('containerName [optionalTitleOrType]') ||
366
403
  markdownContent.includes('## Callouts') ||
@@ -413,4 +450,29 @@ async function processMarkdownFile(filePath) {
413
450
  };
414
451
  }
415
452
 
416
- module.exports = { processMarkdownFile, mdInstance: md, extractHeadingsFromHtml }; // Export mdInstance if needed by plugins for consistency
453
+ // Add findMarkdownFiles function
454
+ /**
455
+ * Recursively finds all Markdown files in a directory and its subdirectories
456
+ * @param {string} dir - Directory to search in
457
+ * @returns {Promise<string[]>} - Array of file paths
458
+ */
459
+ async function findMarkdownFiles(dir) {
460
+ let files = [];
461
+ const items = await fs.readdir(dir, { withFileTypes: true });
462
+ for (const item of items) {
463
+ const fullPath = path.join(dir, item.name);
464
+ if (item.isDirectory()) {
465
+ files = files.concat(await findMarkdownFiles(fullPath));
466
+ } else if (item.isFile() && (item.name.endsWith('.md') || item.name.endsWith('.markdown'))) {
467
+ files.push(fullPath);
468
+ }
469
+ }
470
+ return files;
471
+ }
472
+
473
+ module.exports = {
474
+ processMarkdownFile,
475
+ mdInstance: md,
476
+ extractHeadingsFromHtml,
477
+ findMarkdownFiles // Export the findMarkdownFiles function
478
+ };
@@ -18,7 +18,7 @@ async function processPluginHooks(config, pageData, relativePathToRoot) {
18
18
  // 1. Favicon (built-in handling)
19
19
  if (config.favicon) {
20
20
  const faviconPath = config.favicon.startsWith('/') ? config.favicon.substring(1) : config.favicon;
21
- faviconLinkHtml = ` <link rel="icon" href="${relativePathToRoot}${faviconPath}">\n`;
21
+ faviconLinkHtml = `<link rel="shortcut icon" href="${relativePathToRoot}${faviconPath}" type="image/x-icon">\n`;
22
22
  }
23
23
 
24
24
  // 2. Theme CSS (built-in handling for theme.name)
@@ -75,9 +75,21 @@ async function generateHtmlPage(templateData) {
75
75
  footerHtml = mdInstance.renderInline(config.footer);
76
76
  }
77
77
 
78
- const layoutTemplatePath = path.join(__dirname, '..', 'templates', 'layout.ejs');
78
+ // Determine which template to use based on frontmatter
79
+ let templateName = 'layout.ejs';
80
+ if (frontmatter.noStyle === true) {
81
+ templateName = 'no-style.ejs';
82
+
83
+ // For no-style pages, ensure we're passing the raw HTML content
84
+ // without any additional processing or escaping
85
+ if (content.includes('&lt;') || content.includes('&gt;')) {
86
+ console.warn(`⚠️ Warning: HTML content in no-style page appears to be escaped. This may cause rendering issues.`);
87
+ }
88
+ }
89
+
90
+ const layoutTemplatePath = path.join(__dirname, '..', 'templates', templateName);
79
91
  if (!await fs.pathExists(layoutTemplatePath)) {
80
- throw new Error(`Layout template not found: ${layoutTemplatePath}`);
92
+ throw new Error(`Template not found: ${layoutTemplatePath}`);
81
93
  }
82
94
  const layoutTemplate = await fs.readFile(layoutTemplatePath, 'utf8');
83
95
 
@@ -105,6 +117,7 @@ async function generateHtmlPage(templateData) {
105
117
  currentPagePath, // Pass the current page path for active state detection
106
118
  headings: headings || [], // Pass headings for TOC, default to empty array if not provided
107
119
  isActivePage, // Flag to determine if TOC should be shown
120
+ frontmatter, // Pass the entire frontmatter for no-style template
108
121
  ...pluginOutputs, // Spread all plugin generated HTML strings
109
122
  };
110
123
 
@@ -1,16 +1,34 @@
1
1
  const fs = require('fs-extra');
2
2
  const path = require('path');
3
3
 
4
+ // Function to format paths for display (relative to CWD)
5
+ function formatPathForDisplay(absolutePath) {
6
+ const CWD = process.cwd();
7
+ const relativePath = path.relative(CWD, absolutePath);
8
+
9
+ // If it's not a subdirectory, prefix with ./ for clarity
10
+ if (!relativePath.startsWith('..') && !path.isAbsolute(relativePath)) {
11
+ return `./${relativePath}`;
12
+ }
13
+
14
+ // Return the relative path
15
+ return relativePath;
16
+ }
17
+
4
18
  /**
5
19
  * Generate sitemap.xml in the output directory root
6
20
  * @param {Object} config - The full configuration object
7
21
  * @param {Array} pages - Array of page objects with data about each processed page
8
22
  * @param {string} outputDir - Path to the output directory
23
+ * @param {Object} options - Additional options
24
+ * @param {boolean} options.isDev - Whether running in development mode
9
25
  */
10
- async function generateSitemap(config, pages, outputDir) {
26
+ async function generateSitemap(config, pages, outputDir, options = { isDev: false }) {
11
27
  // Skip if no siteUrl is defined (sitemap needs absolute URLs)
12
28
  if (!config.siteUrl) {
13
- console.warn('⚠️ No siteUrl defined in config. Skipping sitemap generation.');
29
+ if (!options.isDev) {
30
+ console.warn('⚠️ No siteUrl defined in config. Skipping sitemap generation.');
31
+ }
14
32
  return;
15
33
  }
16
34
 
@@ -94,7 +112,10 @@ async function generateSitemap(config, pages, outputDir) {
94
112
  const sitemapPath = path.join(outputDir, 'sitemap.xml');
95
113
  await fs.writeFile(sitemapPath, sitemapXml);
96
114
 
97
- console.log(`✅ Generated sitemap at ${sitemapPath}`);
115
+ // Only show sitemap generation message in production mode or if DOCMD_DEV is true
116
+ if (!options.isDev || process.env.DOCMD_DEV === 'true') {
117
+ console.log(`✅ Generated sitemap at ${formatPathForDisplay(sitemapPath)}`);
118
+ }
98
119
  }
99
120
 
100
121
  module.exports = { generateSitemap };
@@ -100,7 +100,7 @@
100
100
  <%- footerHtml || '' %>
101
101
  </div>
102
102
  <div class="branding-footer">
103
- Build with 💜 <a href="https://docmd.mgks.dev" target="_blank" rel="noopener">docmd.</a>
103
+ Build with <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z"></path><path d="M12 5 9.04 7.96a2.17 2.17 0 0 0 0 3.08c.82.82 2.13.85 3 .07l2.07-1.9a2.82 2.82 0 0 1 3.79 0l2.96 2.66"></path><path d="m18 15-2-2"></path><path d="m15 18-2-2"></path></svg> <a href="https://docmd.mgks.dev" target="_blank" rel="noopener">docmd.</a>
104
104
  </div>
105
105
  </div>
106
106
  </footer>