@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.
- package/.github/workflows/publish.yml +1 -1
- package/README.md +3 -1
- package/assets/css/welcome.css +362 -0
- package/assets/images/preview-dark-1.png +0 -0
- package/assets/images/preview-dark-2.png +0 -0
- package/assets/images/preview-dark-3.png +0 -0
- package/assets/images/preview-light-1.png +0 -0
- package/assets/images/preview-light-2.png +0 -0
- package/assets/images/preview-light-3.png +0 -0
- package/bin/docmd.js +4 -2
- package/config.js +5 -2
- package/docs/content/no-style-example.md +110 -0
- package/docs/content/no-style-pages.md +202 -0
- package/docs/index.md +140 -53
- package/docs/overview.md +56 -0
- package/docs/theming/assets-management.md +126 -0
- package/docs/theming/custom-css-js.md +2 -36
- package/package.json +1 -2
- package/src/assets/css/docmd-main.css +3 -1
- package/src/commands/build.js +282 -205
- package/src/commands/dev.js +163 -35
- package/src/commands/init.js +117 -6
- package/src/core/file-processor.js +67 -5
- package/src/core/html-generator.js +16 -3
- package/src/plugins/sitemap.js +24 -3
- package/src/templates/layout.ejs +1 -1
- package/src/templates/no-style.ejs +159 -0
package/src/commands/dev.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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.
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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', () => {
|
package/src/commands/init.js
CHANGED
|
@@ -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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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('<') || content.includes('>')) {
|
|
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(`
|
|
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
|
|
package/src/plugins/sitemap.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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 };
|
package/src/templates/layout.ejs
CHANGED
|
@@ -100,7 +100,7 @@
|
|
|
100
100
|
<%- footerHtml || '' %>
|
|
101
101
|
</div>
|
|
102
102
|
<div class="branding-footer">
|
|
103
|
-
Build with
|
|
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>
|