@mgks/docmd 0.1.0

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.
Files changed (54) hide show
  1. package/.gitattributes +2 -0
  2. package/.github/FUNDING.yml +15 -0
  3. package/.github/workflows/deploy-docmd.yml +45 -0
  4. package/.github/workflows/publish.yml +84 -0
  5. package/LICENSE +21 -0
  6. package/README.md +83 -0
  7. package/bin/docmd.js +63 -0
  8. package/config.js +137 -0
  9. package/docs/cli-commands.md +87 -0
  10. package/docs/configuration.md +166 -0
  11. package/docs/contributing.md +86 -0
  12. package/docs/deployment.md +129 -0
  13. package/docs/getting-started/basic-usage.md +88 -0
  14. package/docs/getting-started/index.md +21 -0
  15. package/docs/getting-started/installation.md +75 -0
  16. package/docs/index.md +56 -0
  17. package/docs/plugins/analytics.md +76 -0
  18. package/docs/plugins/index.md +71 -0
  19. package/docs/plugins/seo.md +79 -0
  20. package/docs/plugins/sitemap.md +88 -0
  21. package/docs/theming/available-themes.md +85 -0
  22. package/docs/theming/custom-css-js.md +84 -0
  23. package/docs/theming/icons.md +93 -0
  24. package/docs/theming/index.md +19 -0
  25. package/docs/theming/light-dark-mode.md +107 -0
  26. package/docs/writing-content/custom-containers.md +129 -0
  27. package/docs/writing-content/frontmatter.md +76 -0
  28. package/docs/writing-content/index.md +17 -0
  29. package/docs/writing-content/markdown-syntax.md +277 -0
  30. package/package.json +56 -0
  31. package/src/assets/css/highlight-dark.css +1 -0
  32. package/src/assets/css/highlight-light.css +1 -0
  33. package/src/assets/css/main.css +562 -0
  34. package/src/assets/css/theme-sky.css +499 -0
  35. package/src/assets/css/toc.css +76 -0
  36. package/src/assets/favicon.ico +0 -0
  37. package/src/assets/images/docmd-logo.png +0 -0
  38. package/src/assets/images/docmd-preview.png +0 -0
  39. package/src/assets/images/logo-dark.png +0 -0
  40. package/src/assets/images/logo-light.png +0 -0
  41. package/src/assets/js/theme-toggle.js +59 -0
  42. package/src/commands/build.js +300 -0
  43. package/src/commands/dev.js +182 -0
  44. package/src/commands/init.js +51 -0
  45. package/src/core/config-loader.js +28 -0
  46. package/src/core/file-processor.js +376 -0
  47. package/src/core/html-generator.js +139 -0
  48. package/src/core/icon-renderer.js +105 -0
  49. package/src/plugins/analytics.js +44 -0
  50. package/src/plugins/seo.js +65 -0
  51. package/src/plugins/sitemap.js +100 -0
  52. package/src/templates/layout.ejs +174 -0
  53. package/src/templates/navigation.ejs +107 -0
  54. package/src/templates/toc.ejs +34 -0
@@ -0,0 +1,300 @@
1
+ // src/commands/build.js
2
+ const fs = require('fs-extra');
3
+ const path = require('path');
4
+ const { loadConfig } = require('../core/config-loader');
5
+ const { processMarkdownFile } = require('../core/file-processor');
6
+ const { generateHtmlPage, generateNavigationHtml } = require('../core/html-generator');
7
+ const { renderIcon, clearWarnedIcons } = require('../core/icon-renderer'); // Update import
8
+ const { generateSitemap } = require('../plugins/sitemap'); // Import our sitemap plugin
9
+
10
+ // Debug function to log navigation information
11
+ function logNavigationPaths(pagePath, navPath, normalizedPath) {
12
+ console.log(`\nPage: ${pagePath}`);
13
+ console.log(`Navigation Path: ${navPath}`);
14
+ console.log(`Normalized Path: ${normalizedPath}`);
15
+ }
16
+
17
+ // Add a global or scoped flag to track if the warning has been shown in the current dev session
18
+ let highlightWarningShown = false;
19
+
20
+ async function buildSite(configPath, options = { isDev: false }) {
21
+ clearWarnedIcons(); // Clear warnings at the start of every build
22
+
23
+ const config = await loadConfig(configPath);
24
+ const CWD = process.cwd();
25
+ const SRC_DIR = path.resolve(CWD, config.srcDir);
26
+ const OUTPUT_DIR = path.resolve(CWD, config.outputDir);
27
+
28
+ if (!await fs.pathExists(SRC_DIR)) {
29
+ throw new Error(`Source directory not found: ${SRC_DIR}`);
30
+ }
31
+
32
+ await fs.emptyDir(OUTPUT_DIR);
33
+ if (!options.isDev) {
34
+ console.log(`๐Ÿงน Cleaned output directory: ${OUTPUT_DIR}`);
35
+ }
36
+
37
+ const assetsSrcDir = path.join(__dirname, '..', 'assets');
38
+ const assetsDestDir = path.join(OUTPUT_DIR, 'assets');
39
+ if (await fs.pathExists(assetsSrcDir)) {
40
+ await fs.copy(assetsSrcDir, assetsDestDir);
41
+ if (!options.isDev) {
42
+ console.log(`๐Ÿ“‚ Copied assets to ${assetsDestDir}`);
43
+ }
44
+ } else {
45
+ console.warn(`โš ๏ธ Assets source directory not found: ${assetsSrcDir}`);
46
+ }
47
+
48
+ // Check for Highlight.js themes
49
+ const lightThemePath = path.join(__dirname, '..', 'assets', 'css', 'highlight-light.css');
50
+ const darkThemePath = path.join(__dirname, '..', 'assets', 'css', 'highlight-dark.css');
51
+
52
+ const themesMissing = !await fs.pathExists(lightThemePath) || !await fs.pathExists(darkThemePath);
53
+
54
+ if (themesMissing) {
55
+ // For 'docmd build', always show.
56
+ // For 'docmd dev', show only once per session if not already shown.
57
+ if (!options.isDev || (options.isDev && !highlightWarningShown)) {
58
+ console.warn(`โš ๏ธ Highlight.js themes not found in assets. Please ensure these files exist:
59
+ - ${lightThemePath}
60
+ - ${darkThemePath}
61
+ Syntax highlighting may not work correctly.`);
62
+ if (options.isDev) {
63
+ highlightWarningShown = true; // Mark as shown for this dev session
64
+ }
65
+ }
66
+ }
67
+
68
+
69
+ const markdownFiles = await findMarkdownFiles(SRC_DIR);
70
+ if (markdownFiles.length === 0) {
71
+ console.warn(`โš ๏ธ No Markdown files found in ${SRC_DIR}. Nothing to build.`);
72
+ return;
73
+ }
74
+ if (!options.isDev) {
75
+ console.log(`๐Ÿ“„ Found ${markdownFiles.length} markdown files.`);
76
+ }
77
+
78
+ // Array to collect information about all processed pages for sitemap
79
+ const processedPages = [];
80
+
81
+ // Extract a flattened navigation array for prev/next links
82
+ const flatNavigation = [];
83
+
84
+ // Helper function to create a normalized path for navigation matching
85
+ function createNormalizedPath(item) {
86
+ if (!item.path) return null;
87
+ return item.path.startsWith('/') ? item.path : '/' + item.path;
88
+ }
89
+
90
+ function extractNavigationItems(items, parentPath = '') {
91
+ if (!items || !Array.isArray(items)) return;
92
+
93
+ for (const item of items) {
94
+ if (item.external) continue; // Skip external links
95
+
96
+ // Only include items with paths (not section headers without links)
97
+ if (item.path) {
98
+ // Normalize path - ensure leading slash
99
+ let normalizedPath = createNormalizedPath(item);
100
+
101
+ // For parent items with children, ensure path ends with / (folders)
102
+ // This helps with matching in the navigation template
103
+ if (item.children && item.children.length > 0) {
104
+ // If path from config doesn't end with slash, add it
105
+ if (!item.path.endsWith('/') && !normalizedPath.endsWith('/')) {
106
+ normalizedPath += '/';
107
+ }
108
+ }
109
+
110
+ flatNavigation.push({
111
+ title: item.title,
112
+ path: normalizedPath,
113
+ fullPath: item.path, // Original path as defined in config
114
+ isParent: item.children && item.children.length > 0 // Mark if it's a parent with children
115
+ });
116
+ }
117
+
118
+ // Process children (depth first to maintain document outline order)
119
+ if (item.children && Array.isArray(item.children)) {
120
+ extractNavigationItems(item.children, item.path || parentPath);
121
+ }
122
+ }
123
+ }
124
+
125
+ // Extract navigation items into flat array
126
+ extractNavigationItems(config.navigation);
127
+
128
+ for (const mdFilePath of markdownFiles) {
129
+ const relativeMdPath = path.relative(SRC_DIR, mdFilePath);
130
+
131
+ // Pretty URL handling - properly handle index.md files in subfolders
132
+ let outputHtmlPath;
133
+ const fileName = path.basename(relativeMdPath);
134
+ const isIndexFile = fileName === 'index.md';
135
+
136
+ if (isIndexFile) {
137
+ // For any index.md file (in root or subfolder), convert to index.html in the same folder
138
+ const dirPath = path.dirname(relativeMdPath);
139
+ outputHtmlPath = path.join(dirPath, 'index.html');
140
+ } else {
141
+ // For non-index files, create a folder with index.html
142
+ outputHtmlPath = relativeMdPath.replace(/\.md$/, '/index.html');
143
+ }
144
+
145
+ const finalOutputHtmlPath = path.join(OUTPUT_DIR, outputHtmlPath);
146
+
147
+ const depth = outputHtmlPath.split(path.sep).length - 1;
148
+ const relativePathToRoot = depth > 0 ? '../'.repeat(depth) : './';
149
+
150
+ const { frontmatter, htmlContent, headings } = await processMarkdownFile(mdFilePath);
151
+
152
+ // Get the URL path for navigation
153
+ let currentPagePathForNav;
154
+ let normalizedPath;
155
+
156
+ if (isIndexFile) {
157
+ // For index.md files, the nav path should be the directory itself with trailing slash
158
+ const dirPath = path.dirname(relativeMdPath);
159
+ if (dirPath === '.') {
160
+ // Root index.md
161
+ currentPagePathForNav = 'index.html';
162
+ normalizedPath = '/';
163
+ } else {
164
+ // Subfolder index.md - simple format: directory-name/
165
+ currentPagePathForNav = dirPath + '/';
166
+ normalizedPath = '/' + dirPath;
167
+ }
168
+ } else {
169
+ // For non-index files, the path should be the file name with trailing slash
170
+ const pathWithoutExt = relativeMdPath.replace(/\.md$/, '');
171
+ currentPagePathForNav = pathWithoutExt + '/';
172
+ normalizedPath = '/' + pathWithoutExt;
173
+ }
174
+
175
+ // Convert Windows backslashes to forward slashes for web paths
176
+ currentPagePathForNav = currentPagePathForNav.replace(/\\/g, '/');
177
+
178
+ // Log navigation paths for debugging
179
+ // Uncomment this line when debugging:
180
+ // logNavigationPaths(mdFilePath, currentPagePathForNav, normalizedPath);
181
+
182
+ const navigationHtml = await generateNavigationHtml(
183
+ config.navigation,
184
+ currentPagePathForNav,
185
+ relativePathToRoot,
186
+ config
187
+ );
188
+
189
+ // Find current page in navigation for prev/next links
190
+ let prevPage = null;
191
+ let nextPage = null;
192
+ let currentPageIndex = -1;
193
+
194
+ // Find the current page in flatNavigation
195
+ currentPageIndex = flatNavigation.findIndex(item => {
196
+ // Direct path match
197
+ if (item.path === normalizedPath) {
198
+ return true;
199
+ }
200
+
201
+ // Special handling for parent folders
202
+ if (isIndexFile && item.path.endsWith('/')) {
203
+ // Remove trailing slash for comparison
204
+ const itemPathWithoutSlash = item.path.slice(0, -1);
205
+ return itemPathWithoutSlash === normalizedPath;
206
+ }
207
+
208
+ return false;
209
+ });
210
+
211
+ if (currentPageIndex >= 0) {
212
+ // Get previous and next pages if they exist
213
+ if (currentPageIndex > 0) {
214
+ prevPage = flatNavigation[currentPageIndex - 1];
215
+ }
216
+
217
+ if (currentPageIndex < flatNavigation.length - 1) {
218
+ nextPage = flatNavigation[currentPageIndex + 1];
219
+ }
220
+ }
221
+
222
+ // Convert page paths to proper URLs for links
223
+ if (prevPage) {
224
+ // Format the previous page URL, avoiding double slashes
225
+ if (prevPage.path === '/') {
226
+ prevPage.url = relativePathToRoot + 'index.html';
227
+ } else {
228
+ // Remove leading slash and ensure clean path
229
+ const cleanPath = prevPage.path.substring(1).replace(/\/+$/, '');
230
+ prevPage.url = relativePathToRoot + cleanPath + '/';
231
+ }
232
+ }
233
+
234
+ if (nextPage) {
235
+ // Format the next page URL, avoiding double slashes
236
+ if (nextPage.path === '/') {
237
+ nextPage.url = relativePathToRoot + 'index.html';
238
+ } else {
239
+ // Remove leading slash and ensure clean path
240
+ const cleanPath = nextPage.path.substring(1).replace(/\/+$/, '');
241
+ nextPage.url = relativePathToRoot + cleanPath + '/';
242
+ }
243
+ }
244
+
245
+ const pageDataForTemplate = {
246
+ content: htmlContent,
247
+ pageTitle: frontmatter.title || 'Untitled',
248
+ siteTitle: config.siteTitle,
249
+ navigationHtml,
250
+ relativePathToRoot: relativePathToRoot,
251
+ config: config, // Pass full config
252
+ frontmatter: frontmatter,
253
+ outputPath: outputHtmlPath, // Relative path from outputDir root
254
+ prettyUrl: true, // Flag to indicate we're using pretty URLs
255
+ prevPage: prevPage, // Previous page in navigation
256
+ nextPage: nextPage, // Next page in navigation
257
+ currentPagePath: normalizedPath, // Pass the normalized path for active state detection
258
+ headings: headings || [], // Pass headings for TOC
259
+ };
260
+
261
+ const pageHtml = await generateHtmlPage(pageDataForTemplate);
262
+
263
+ await fs.ensureDir(path.dirname(finalOutputHtmlPath));
264
+ await fs.writeFile(finalOutputHtmlPath, pageHtml);
265
+
266
+ // Add to processed pages for sitemap
267
+ processedPages.push({
268
+ outputPath: isIndexFile
269
+ ? (path.dirname(relativeMdPath) === '.' ? 'index.html' : path.dirname(relativeMdPath) + '/')
270
+ : outputHtmlPath.replace(/\\/g, '/').replace(/\/index\.html$/, '/'),
271
+ frontmatter: frontmatter
272
+ });
273
+ }
274
+
275
+ // Generate sitemap if enabled in config
276
+ if (config.plugins?.sitemap !== false) {
277
+ try {
278
+ await generateSitemap(config, processedPages, OUTPUT_DIR);
279
+ } catch (error) {
280
+ console.error(`โŒ Error generating sitemap: ${error.message}`);
281
+ }
282
+ }
283
+ }
284
+
285
+ // findMarkdownFiles function remains the same
286
+ async function findMarkdownFiles(dir) {
287
+ let files = [];
288
+ const items = await fs.readdir(dir, { withFileTypes: true });
289
+ for (const item of items) {
290
+ const fullPath = path.join(dir, item.name);
291
+ if (item.isDirectory()) {
292
+ files = files.concat(await findMarkdownFiles(fullPath));
293
+ } else if (item.isFile() && (item.name.endsWith('.md') || item.name.endsWith('.markdown'))) {
294
+ files.push(fullPath);
295
+ }
296
+ }
297
+ return files;
298
+ }
299
+
300
+ module.exports = { buildSite };
@@ -0,0 +1,182 @@
1
+ // src/commands/dev.js
2
+ const express = require('express');
3
+ const http = require('http');
4
+ const WebSocket = require('ws');
5
+ const chokidar = require('chokidar');
6
+ const path = require('path');
7
+ const fs = require('fs-extra');
8
+ const { buildSite } = require('./build'); // Re-use the build logic
9
+ const { loadConfig } = require('../core/config-loader');
10
+
11
+ async function startDevServer(configPathOption) {
12
+ let config = await loadConfig(configPathOption); // Load initial config
13
+ const CWD = process.cwd(); // Current Working Directory where user runs `docmd dev`
14
+
15
+ // Function to resolve paths based on current config
16
+ const resolveConfigPaths = (currentConfig) => {
17
+ return {
18
+ outputDir: path.resolve(CWD, currentConfig.outputDir),
19
+ srcDirToWatch: path.resolve(CWD, currentConfig.srcDir),
20
+ configFileToWatch: path.resolve(CWD, configPathOption), // Path to the config file itself
21
+ };
22
+ };
23
+
24
+ let paths = resolveConfigPaths(config);
25
+
26
+ // docmd's internal templates and assets (for live dev of docmd itself)
27
+ const DOCMD_TEMPLATES_DIR = path.resolve(__dirname, '..', 'templates');
28
+ const DOCMD_ASSETS_DIR = path.resolve(__dirname, '..', 'assets');
29
+
30
+ const app = express();
31
+ const server = http.createServer(app);
32
+ const wss = new WebSocket.Server({ server });
33
+
34
+ let wsClients = new Set();
35
+ wss.on('connection', (ws) => {
36
+ wsClients.add(ws);
37
+ // console.log('Client connected to WebSocket. Total clients:', wsClients.size);
38
+ ws.on('close', () => {
39
+ wsClients.delete(ws);
40
+ // console.log('Client disconnected. Total clients:', wsClients.size);
41
+ });
42
+ ws.on('error', (error) => {
43
+ console.error('WebSocket error on client:', error);
44
+ });
45
+ });
46
+ wss.on('error', (error) => {
47
+ console.error('WebSocket Server error:', error);
48
+ });
49
+
50
+
51
+ function broadcastReload() {
52
+ // console.log('Broadcasting reload to', wsClients.size, 'clients');
53
+ wsClients.forEach(client => {
54
+ if (client.readyState === WebSocket.OPEN) {
55
+ client.send('reload');
56
+ }
57
+ });
58
+ }
59
+
60
+ // Inject live reload script into HTML
61
+ app.use((req, res, next) => {
62
+ if (req.path.endsWith('.html')) {
63
+ const originalSend = res.send;
64
+ res.send = function (body) {
65
+ if (typeof body === 'string') {
66
+ const liveReloadScript = `
67
+ <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'); };
73
+ </script>
74
+ `;
75
+ body = body.replace('</body>', `${liveReloadScript}</body>`);
76
+ }
77
+ originalSend.call(this, body);
78
+ };
79
+ }
80
+ next();
81
+ });
82
+
83
+ // Serve static files from the output directory
84
+ // This middleware needs to be dynamic if outputDir changes
85
+ let staticMiddleware = express.static(paths.outputDir);
86
+ app.use((req, res, next) => staticMiddleware(req, res, next));
87
+
88
+ // Initial build
89
+ console.log('๐Ÿš€ Performing initial build for dev server...');
90
+ try {
91
+ await buildSite(configPathOption, { isDev: true }); // Use the original config path option
92
+ console.log('โœ… Initial build complete.');
93
+ } catch (error) {
94
+ console.error('โŒ Initial build failed:', error.message, error.stack);
95
+ // Optionally, don't start server if initial build fails, or serve a specific error page.
96
+ }
97
+
98
+
99
+ // Watch for changes
100
+ const watchedPaths = [
101
+ paths.srcDirToWatch,
102
+ paths.configFileToWatch,
103
+ DOCMD_TEMPLATES_DIR,
104
+ DOCMD_ASSETS_DIR
105
+ ];
106
+
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
+ `);
113
+
114
+ const watcher = chokidar.watch(watchedPaths, {
115
+ ignored: /(^|[\/\\])\../, // ignore dotfiles
116
+ persistent: true,
117
+ ignoreInitial: true, // Don't trigger for initial scan
118
+ awaitWriteFinish: { // Helps with rapid saves or large file writes
119
+ stabilityThreshold: 100,
120
+ pollInterval: 100
121
+ }
122
+ });
123
+
124
+ watcher.on('all', async (event, filePath) => {
125
+ const relativeFilePath = path.relative(CWD, filePath);
126
+ console.log(`๐Ÿ”„ Detected ${event} in ${relativeFilePath}. Rebuilding...`);
127
+ try {
128
+ if (filePath === paths.configFileToWatch) {
129
+ console.log('Config file changed. Reloading configuration...');
130
+ config = await loadConfig(configPathOption); // Reload config
131
+ const newPaths = resolveConfigPaths(config);
132
+
133
+ // Update watcher if srcDir changed - Chokidar doesn't easily support dynamic path changes after init.
134
+ // For simplicity, we might need to restart the watcher or inform user to restart dev server if srcDir/outputDir change.
135
+ // For now, we'll at least update the static server path.
136
+ if (newPaths.outputDir !== paths.outputDir) {
137
+ console.log(`Output directory changed from ${paths.outputDir} to ${newPaths.outputDir}. Updating static server.`);
138
+ staticMiddleware = express.static(newPaths.outputDir);
139
+ }
140
+ // If srcDirToWatch changes, chokidar won't automatically pick it up.
141
+ // A full dev server restart would be more robust for such config changes.
142
+ // For now, the old srcDir will still be watched.
143
+ paths = newPaths; // Update paths for next build reference
144
+ }
145
+
146
+ await buildSite(configPathOption, { isDev: true }); // Re-build using the potentially updated config path
147
+ broadcastReload();
148
+ console.log('โœ… Rebuild complete. Browser should refresh.');
149
+ } catch (error) {
150
+ console.error('โŒ Rebuild failed:', error.message, error.stack);
151
+ }
152
+ });
153
+
154
+ watcher.on('error', error => console.error(`Watcher error: ${error}`));
155
+
156
+ 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
+ });
168
+
169
+ // Graceful shutdown
170
+ process.on('SIGINT', () => {
171
+ console.log('\n๐Ÿ›‘ Shutting down dev server...');
172
+ watcher.close();
173
+ wss.close(() => {
174
+ server.close(() => {
175
+ console.log('Server closed.');
176
+ process.exit(0);
177
+ });
178
+ });
179
+ });
180
+ }
181
+
182
+ module.exports = { startDevServer };
@@ -0,0 +1,51 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+
4
+ const defaultConfigContent = `// config.js
5
+ module.exports = {
6
+ siteTitle: 'My Awesome Project Docs',
7
+ srcDir: 'docs',
8
+ outputDir: 'site',
9
+ theme: {
10
+ defaultMode: 'light', // 'light' or 'dark'
11
+ },
12
+ navigation: [
13
+ { title: 'Home', path: '/' }, // Corresponds to docs/index.md
14
+ // {
15
+ // title: 'Category',
16
+ // children: [
17
+ // { title: 'Page 1', path: '/category/page1' },
18
+ // ],
19
+ // },
20
+ ],
21
+ };
22
+ `;
23
+
24
+ const defaultIndexMdContent = `---
25
+ title: "Welcome"
26
+ description: "Your documentation starts here."
27
+ ---
28
+
29
+ # Hello, docmd!
30
+
31
+ Start writing your Markdown content here.
32
+ `;
33
+
34
+ async function initProject() {
35
+ const baseDir = process.cwd();
36
+ const docsDir = path.join(baseDir, 'docs');
37
+ const configFile = path.join(baseDir, 'config.js');
38
+ const indexMdFile = path.join(docsDir, 'index.md');
39
+
40
+ if (await fs.pathExists(configFile) || await fs.pathExists(docsDir)) {
41
+ console.warn('โš ๏ธ `docs/` directory or `config.js` already exists. Skipping creation to avoid overwriting.');
42
+ } else {
43
+ await fs.ensureDir(docsDir);
44
+ await fs.writeFile(configFile, defaultConfigContent, 'utf8');
45
+ await fs.writeFile(indexMdFile, defaultIndexMdContent, 'utf8');
46
+ console.log('๐Ÿ“„ Created `config.js`');
47
+ console.log('๐Ÿ“ Created `docs/` directory with a sample `index.md`');
48
+ }
49
+ }
50
+
51
+ module.exports = { initProject };
@@ -0,0 +1,28 @@
1
+ const path = require('path');
2
+ const fs = require('fs-extra');
3
+
4
+ async function loadConfig(configPath) {
5
+ const absoluteConfigPath = path.resolve(process.cwd(), configPath);
6
+ if (!await fs.pathExists(absoluteConfigPath)) {
7
+ throw new Error(`Configuration file not found: ${absoluteConfigPath}`);
8
+ }
9
+ try {
10
+ // Clear require cache to always get the freshest config
11
+ delete require.cache[require.resolve(absoluteConfigPath)];
12
+ const config = require(absoluteConfigPath);
13
+
14
+ // Basic validation and defaults
15
+ if (!config.siteTitle) throw new Error('`siteTitle` is missing in config.js');
16
+ config.srcDir = config.srcDir || 'docs';
17
+ config.outputDir = config.outputDir || 'site';
18
+ config.theme = config.theme || {};
19
+ config.theme.defaultMode = config.theme.defaultMode || 'light';
20
+ config.navigation = config.navigation || [{ title: 'Home', path: '/' }];
21
+
22
+ return config;
23
+ } catch (e) {
24
+ throw new Error(`Error loading or parsing config file ${absoluteConfigPath}: ${e.message}`);
25
+ }
26
+ }
27
+
28
+ module.exports = { loadConfig };