@mgks/docmd 0.1.1 → 0.1.2
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/bin/docmd.js +4 -2
- 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 +45 -4
- package/src/commands/dev.js +100 -14
- package/src/commands/init.js +117 -6
- package/src/plugins/sitemap.js +10 -3
- package/src/templates/layout.ejs +1 -1
|
@@ -2,7 +2,7 @@ name: Publish Package to NPM and GitHub Packages
|
|
|
2
2
|
|
|
3
3
|
on:
|
|
4
4
|
release:
|
|
5
|
-
types: [created, published]
|
|
5
|
+
types: [created] # [created, published] Triggers when a new GitHub Release is created or published
|
|
6
6
|
workflow_dispatch: # Allows manual triggering for testing
|
|
7
7
|
|
|
8
8
|
jobs:
|
package/bin/docmd.js
CHANGED
|
@@ -30,7 +30,8 @@ program
|
|
|
30
30
|
.command('build')
|
|
31
31
|
.description('Build the static site from Markdown files and config.js')
|
|
32
32
|
.option('-c, --config <path>', 'Path to config.js file', 'config.js')
|
|
33
|
-
.option('-p, --preserve', 'Preserve existing asset files instead of updating them'
|
|
33
|
+
.option('-p, --preserve', 'Preserve existing asset files instead of updating them')
|
|
34
|
+
.option('--no-preserve', 'Force update all asset files, overwriting existing ones')
|
|
34
35
|
.action(async (options) => {
|
|
35
36
|
try {
|
|
36
37
|
console.log('🚀 Starting build process...');
|
|
@@ -49,7 +50,8 @@ program
|
|
|
49
50
|
.command('dev')
|
|
50
51
|
.description('Start a live preview development server')
|
|
51
52
|
.option('-c, --config <path>', 'Path to config.js file', 'config.js')
|
|
52
|
-
.option('-p, --preserve', 'Preserve existing asset files instead of updating them'
|
|
53
|
+
.option('-p, --preserve', 'Preserve existing asset files instead of updating them')
|
|
54
|
+
.option('--no-preserve', 'Force update all asset files, overwriting existing ones')
|
|
53
55
|
.action(async (options) => {
|
|
54
56
|
try {
|
|
55
57
|
await startDevServer(options.config, { preserve: options.preserve });
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Assets Management"
|
|
3
|
+
description: "Learn how to manage and customize your assets (CSS, JavaScript, images) in your docmd site."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Assets Management
|
|
7
|
+
|
|
8
|
+
Managing your custom assets (CSS, JavaScript, images) is an important part of customizing your documentation site. `docmd` provides flexible ways to include and manage these assets.
|
|
9
|
+
|
|
10
|
+
## Project Structure
|
|
11
|
+
|
|
12
|
+
When you initialize a new project with `docmd init`, it creates the following structure:
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
your-project/
|
|
16
|
+
├── assets/ # User assets directory
|
|
17
|
+
│ ├── css/ # Custom CSS files
|
|
18
|
+
│ ├── js/ # Custom JavaScript files
|
|
19
|
+
│ └── images/ # Custom images
|
|
20
|
+
├── docs/ # Markdown content
|
|
21
|
+
├── config.js
|
|
22
|
+
└── ...
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
This structure makes it easy to organize and manage your custom assets.
|
|
26
|
+
|
|
27
|
+
## How Assets Are Handled
|
|
28
|
+
|
|
29
|
+
There are two main ways to manage assets in your docmd site:
|
|
30
|
+
|
|
31
|
+
### 1. Root-Level Assets Directory (Recommended)
|
|
32
|
+
|
|
33
|
+
The simplest and recommended approach is to use the `assets/` directory in your project root:
|
|
34
|
+
|
|
35
|
+
**How it works:**
|
|
36
|
+
- During the build process, docmd automatically copies everything from your root `assets/` directory to the output `site/assets/` directory
|
|
37
|
+
- Your custom assets take precedence over docmd's built-in assets with the same name
|
|
38
|
+
- This approach is ideal for GitHub Pages deployments and other hosting scenarios
|
|
39
|
+
|
|
40
|
+
**Example workflow:**
|
|
41
|
+
1. Create or modify files in your project's `assets/` directory:
|
|
42
|
+
```
|
|
43
|
+
assets/css/custom-styles.css
|
|
44
|
+
assets/js/interactive-features.js
|
|
45
|
+
assets/images/logo.png
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
2. Reference these files in your `config.js`:
|
|
49
|
+
```javascript
|
|
50
|
+
module.exports = {
|
|
51
|
+
// ...
|
|
52
|
+
theme: {
|
|
53
|
+
// ...
|
|
54
|
+
customCss: [
|
|
55
|
+
'/assets/css/custom-styles.css',
|
|
56
|
+
],
|
|
57
|
+
},
|
|
58
|
+
customJs: [
|
|
59
|
+
'/assets/js/interactive-features.js',
|
|
60
|
+
],
|
|
61
|
+
// ...
|
|
62
|
+
};
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
3. Use images in your Markdown content:
|
|
66
|
+
```markdown
|
|
67
|
+

|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
4. Build your site:
|
|
71
|
+
```bash
|
|
72
|
+
docmd build
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 2. Customizing Default Assets
|
|
76
|
+
|
|
77
|
+
If you want to modify docmd's default assets:
|
|
78
|
+
|
|
79
|
+
1. First, build your site normally to generate all assets:
|
|
80
|
+
```bash
|
|
81
|
+
docmd build
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
2. Modify the generated files in the `site/assets` directory as needed.
|
|
85
|
+
|
|
86
|
+
3. When rebuilding, use the `--preserve` flag to keep your customized files:
|
|
87
|
+
```bash
|
|
88
|
+
docmd build --preserve
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
4. If you want to update to the latest docmd assets (for example, after updating the package), run without the preserve flag:
|
|
92
|
+
```bash
|
|
93
|
+
docmd build
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
This approach allows you to:
|
|
97
|
+
- Get the latest assets by default when you update the package
|
|
98
|
+
- Preserve your customizations when needed with `--preserve`
|
|
99
|
+
- See which files are being preserved during the build process
|
|
100
|
+
|
|
101
|
+
The preservation behavior works with both `build` and `dev` commands:
|
|
102
|
+
```bash
|
|
103
|
+
# Preserve custom assets during development
|
|
104
|
+
docmd dev --preserve
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Asset Precedence
|
|
108
|
+
|
|
109
|
+
When multiple assets with the same name exist, docmd follows this precedence order:
|
|
110
|
+
|
|
111
|
+
1. **User assets** from the root `assets/` directory (highest priority)
|
|
112
|
+
2. **Preserved assets** from previous builds (if `--preserve` is enabled, which is the default)
|
|
113
|
+
3. **Built-in assets** from the docmd package (lowest priority)
|
|
114
|
+
|
|
115
|
+
This ensures your custom assets always take precedence over default ones.
|
|
116
|
+
|
|
117
|
+
## GitHub Pages Deployment
|
|
118
|
+
|
|
119
|
+
When deploying to GitHub Pages, your assets structure is preserved. If you're using a custom domain or GitHub Pages URL, make sure your asset paths are correctly configured.
|
|
120
|
+
|
|
121
|
+
For more information on deployment, see the [Deployment](/deployment/) documentation.
|
|
122
|
+
|
|
123
|
+
## Related Topics
|
|
124
|
+
|
|
125
|
+
- [Custom CSS & JS](/theming/custom-css-js/) - Learn how to configure custom CSS and JavaScript
|
|
126
|
+
- [Theming](/theming/) - Explore other theming options for your documentation site
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: "Custom
|
|
2
|
+
title: "Custom Styles & Scripts"
|
|
3
3
|
description: "Learn how to add your own custom CSS and JavaScript to your docmd site for advanced customization."
|
|
4
4
|
---
|
|
5
5
|
|
|
@@ -30,41 +30,8 @@ module.exports = {
|
|
|
30
30
|
**How it works:**
|
|
31
31
|
* Each string in the `customCss` array should be an absolute path from the root of your generated `site/` directory (e.g., if your file is `site/assets/css/my-branding.css`, the path is `/assets/css/my-branding.css`).
|
|
32
32
|
* These `<link rel="stylesheet">` tags will be added to the `<head>` of every page *after* the main theme CSS and `highlight.js` CSS. This allows your custom styles to override the default theme styles.
|
|
33
|
-
* You are responsible for ensuring these CSS files exist at the specified locations in your final `site/` output. Typically, you would:
|
|
34
|
-
1. Create your custom CSS files (e.g., `my-branding.css`).
|
|
35
|
-
2. Place them in a folder within your project (e.g., `my-project/static-assets/css/`).
|
|
36
|
-
3. Ensure that this folder (or its contents) is copied to the correct location in your `site/` directory during `docmd`'s asset copying process. If `docmd` copies a top-level `assets/` folder from your source, place them there.
|
|
37
33
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
By default, `docmd` will always update assets to the latest version when you run `build` or `dev` commands. This ensures your site benefits from the latest improvements and fixes.
|
|
41
|
-
|
|
42
|
-
### Customizing Default Assets
|
|
43
|
-
|
|
44
|
-
If you want to customize default assets (like theme CSS files or scripts):
|
|
45
|
-
|
|
46
|
-
1. First, build your site normally to generate all assets:
|
|
47
|
-
```bash
|
|
48
|
-
docmd build
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
2. Modify the generated files in the `site/assets` directory as needed.
|
|
52
|
-
|
|
53
|
-
3. When rebuilding, use the `--preserve` flag to keep your customized files:
|
|
54
|
-
```bash
|
|
55
|
-
docmd build --preserve
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
This approach allows you to:
|
|
59
|
-
- Always get the latest assets when you want them (default behavior)
|
|
60
|
-
- Preserve your customizations when needed (with `--preserve`)
|
|
61
|
-
- Easily see which files are being preserved during the build process
|
|
62
|
-
|
|
63
|
-
The `--preserve` flag works with both `build` and `dev` commands:
|
|
64
|
-
```bash
|
|
65
|
-
# Preserve custom assets during development
|
|
66
|
-
docmd dev --preserve
|
|
67
|
-
```
|
|
34
|
+
> **Note:** For information on how to manage your custom asset files (CSS, JS, images), see the [Assets Management](/theming/assets-management/) documentation.
|
|
68
35
|
|
|
69
36
|
**Use Cases for Custom CSS:**
|
|
70
37
|
* **Overriding CSS Variables:** The `default` theme uses CSS variables extensively. You can redefine these in your custom CSS.
|
|
@@ -104,7 +71,6 @@ module.exports = {
|
|
|
104
71
|
**How it works:**
|
|
105
72
|
* Each string in the `customJs` array should be an absolute path from the root of your generated `site/` directory.
|
|
106
73
|
* These `<script src="..."></script>` tags will be added just before the closing `</body>` tag on every page. This ensures the DOM is loaded before your scripts run and is generally better for page performance.
|
|
107
|
-
* Similar to custom CSS, you are responsible for ensuring these JavaScript files exist at the specified locations in your final `site/` output.
|
|
108
74
|
|
|
109
75
|
**Use Cases for Custom JS:**
|
|
110
76
|
* Adding interactive elements (e.g., custom modals, tabs not provided by `docmd`).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mgks/docmd",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Generate beautiful, lightweight static documentation sites directly from your Markdown files. Zero clutter, just content.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -35,7 +35,6 @@
|
|
|
35
35
|
},
|
|
36
36
|
"homepage": "https://github.com/mgks/docmd#readme",
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@mgks/docmd": "^0.1.0",
|
|
39
38
|
"chokidar": "^3.6.0",
|
|
40
39
|
"commander": "^12.0.0",
|
|
41
40
|
"ejs": "^3.1.9",
|
package/src/commands/build.js
CHANGED
|
@@ -35,6 +35,7 @@ async function buildSite(configPath, options = { isDev: false, preserve: false }
|
|
|
35
35
|
const CWD = process.cwd();
|
|
36
36
|
const SRC_DIR = path.resolve(CWD, config.srcDir);
|
|
37
37
|
const OUTPUT_DIR = path.resolve(CWD, config.outputDir);
|
|
38
|
+
const USER_ASSETS_DIR = path.resolve(CWD, 'assets'); // User's custom assets directory
|
|
38
39
|
|
|
39
40
|
if (!await fs.pathExists(SRC_DIR)) {
|
|
40
41
|
throw new Error(`Source directory not found: ${SRC_DIR}`);
|
|
@@ -57,6 +58,33 @@ async function buildSite(configPath, options = { isDev: false, preserve: false }
|
|
|
57
58
|
|
|
58
59
|
// Track preserved files for summary report
|
|
59
60
|
const preservedFiles = [];
|
|
61
|
+
const userAssetsCopied = [];
|
|
62
|
+
|
|
63
|
+
// Copy user assets from root assets/ directory if it exists
|
|
64
|
+
if (await fs.pathExists(USER_ASSETS_DIR)) {
|
|
65
|
+
const assetsDestDir = path.join(OUTPUT_DIR, 'assets');
|
|
66
|
+
await fs.ensureDir(assetsDestDir);
|
|
67
|
+
|
|
68
|
+
if (!options.isDev) {
|
|
69
|
+
console.log(`📂 Copying user assets from ${USER_ASSETS_DIR} to ${assetsDestDir}...`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const userAssetFiles = await getAllFiles(USER_ASSETS_DIR);
|
|
73
|
+
|
|
74
|
+
for (const srcFile of userAssetFiles) {
|
|
75
|
+
const relativePath = path.relative(USER_ASSETS_DIR, srcFile);
|
|
76
|
+
const destFile = path.join(assetsDestDir, relativePath);
|
|
77
|
+
|
|
78
|
+
// Ensure directory exists
|
|
79
|
+
await fs.ensureDir(path.dirname(destFile));
|
|
80
|
+
await fs.copyFile(srcFile, destFile);
|
|
81
|
+
userAssetsCopied.push(relativePath);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (!options.isDev && userAssetsCopied.length > 0) {
|
|
85
|
+
console.log(`📦 Copied ${userAssetsCopied.length} user assets`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
60
88
|
|
|
61
89
|
// Copy assets
|
|
62
90
|
const assetsSrcDir = path.join(__dirname, '..', 'assets');
|
|
@@ -64,7 +92,7 @@ async function buildSite(configPath, options = { isDev: false, preserve: false }
|
|
|
64
92
|
|
|
65
93
|
if (await fs.pathExists(assetsSrcDir)) {
|
|
66
94
|
if (!options.isDev) {
|
|
67
|
-
console.log(`📂 Copying assets to ${assetsDestDir}...`);
|
|
95
|
+
console.log(`📂 Copying docmd assets to ${assetsDestDir}...`);
|
|
68
96
|
}
|
|
69
97
|
|
|
70
98
|
// Create destination directory if it doesn't exist
|
|
@@ -81,10 +109,13 @@ async function buildSite(configPath, options = { isDev: false, preserve: false }
|
|
|
81
109
|
// Check if destination file already exists
|
|
82
110
|
const fileExists = await fs.pathExists(destFile);
|
|
83
111
|
|
|
84
|
-
if
|
|
112
|
+
// Skip if the file exists and either:
|
|
113
|
+
// 1. The preserve flag is set, OR
|
|
114
|
+
// 2. The file was copied from user assets (user assets take precedence)
|
|
115
|
+
if (fileExists && (options.preserve || userAssetsCopied.includes(relativePath))) {
|
|
85
116
|
// Skip file and add to preserved list
|
|
86
117
|
preservedFiles.push(relativePath);
|
|
87
|
-
if (!options.isDev) {
|
|
118
|
+
if (!options.isDev && options.preserve) {
|
|
88
119
|
console.log(` Preserving existing file: ${relativePath}`);
|
|
89
120
|
}
|
|
90
121
|
} else {
|
|
@@ -327,7 +358,7 @@ async function buildSite(configPath, options = { isDev: false, preserve: false }
|
|
|
327
358
|
// Generate sitemap if enabled in config
|
|
328
359
|
if (config.plugins?.sitemap !== false) {
|
|
329
360
|
try {
|
|
330
|
-
await generateSitemap(config, processedPages, OUTPUT_DIR);
|
|
361
|
+
await generateSitemap(config, processedPages, OUTPUT_DIR, { isDev: options.isDev });
|
|
331
362
|
} catch (error) {
|
|
332
363
|
console.error(`❌ Error generating sitemap: ${error.message}`);
|
|
333
364
|
}
|
|
@@ -339,6 +370,16 @@ async function buildSite(configPath, options = { isDev: false, preserve: false }
|
|
|
339
370
|
preservedFiles.forEach(file => console.log(` - assets/${file}`));
|
|
340
371
|
console.log(`\nTo update these files in future builds, run without the --preserve flag.`);
|
|
341
372
|
}
|
|
373
|
+
|
|
374
|
+
if (userAssetsCopied.length > 0 && !options.isDev) {
|
|
375
|
+
console.log(`\n📋 User Assets: ${userAssetsCopied.length} files were copied from your assets/ directory:`);
|
|
376
|
+
if (userAssetsCopied.length <= 10) {
|
|
377
|
+
userAssetsCopied.forEach(file => console.log(` - assets/${file}`));
|
|
378
|
+
} else {
|
|
379
|
+
userAssetsCopied.slice(0, 5).forEach(file => console.log(` - assets/${file}`));
|
|
380
|
+
console.log(` - ... and ${userAssetsCopied.length - 5} more files`);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
342
383
|
}
|
|
343
384
|
|
|
344
385
|
// Helper function to find HTML files and sitemap.xml to clean up
|
package/src/commands/dev.js
CHANGED
|
@@ -18,6 +18,7 @@ async function startDevServer(configPathOption, options = { preserve: false }) {
|
|
|
18
18
|
outputDir: path.resolve(CWD, currentConfig.outputDir),
|
|
19
19
|
srcDirToWatch: path.resolve(CWD, currentConfig.srcDir),
|
|
20
20
|
configFileToWatch: path.resolve(CWD, configPathOption), // Path to the config file itself
|
|
21
|
+
userAssetsDir: path.resolve(CWD, 'assets'), // User's assets directory
|
|
21
22
|
};
|
|
22
23
|
};
|
|
23
24
|
|
|
@@ -65,11 +66,72 @@ async function startDevServer(configPathOption, options = { preserve: false }) {
|
|
|
65
66
|
if (typeof body === 'string') {
|
|
66
67
|
const liveReloadScript = `
|
|
67
68
|
<script>
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
(function() {
|
|
70
|
+
// More robust WebSocket connection with automatic reconnection
|
|
71
|
+
let socket;
|
|
72
|
+
let reconnectAttempts = 0;
|
|
73
|
+
const maxReconnectAttempts = 5;
|
|
74
|
+
const reconnectDelay = 1000; // Start with 1 second delay
|
|
75
|
+
|
|
76
|
+
function connect() {
|
|
77
|
+
socket = new WebSocket(\`ws://\${window.location.host}\`);
|
|
78
|
+
|
|
79
|
+
socket.onmessage = function(event) {
|
|
80
|
+
if (event.data === 'reload') {
|
|
81
|
+
console.log('Received reload signal. Refreshing page...');
|
|
82
|
+
window.location.reload();
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
socket.onopen = function() {
|
|
87
|
+
console.log('Live reload connected.');
|
|
88
|
+
reconnectAttempts = 0; // Reset reconnect counter on successful connection
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
socket.onclose = function() {
|
|
92
|
+
if (reconnectAttempts < maxReconnectAttempts) {
|
|
93
|
+
reconnectAttempts++;
|
|
94
|
+
const delay = reconnectDelay * Math.pow(1.5, reconnectAttempts - 1); // Exponential backoff
|
|
95
|
+
console.log(\`Live reload disconnected. Reconnecting in \${delay/1000} seconds...\`);
|
|
96
|
+
setTimeout(connect, delay);
|
|
97
|
+
} else {
|
|
98
|
+
console.log('Live reload disconnected. Max reconnect attempts reached.');
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
socket.onerror = function(error) {
|
|
103
|
+
console.error('WebSocket error:', error);
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Initial connection
|
|
108
|
+
connect();
|
|
109
|
+
|
|
110
|
+
// Backup reload mechanism using polling for browsers with WebSocket issues
|
|
111
|
+
let lastModified = new Date().getTime();
|
|
112
|
+
const pollInterval = 2000; // Poll every 2 seconds
|
|
113
|
+
|
|
114
|
+
function checkForChanges() {
|
|
115
|
+
fetch(window.location.href, { method: 'HEAD', cache: 'no-store' })
|
|
116
|
+
.then(response => {
|
|
117
|
+
const serverLastModified = new Date(response.headers.get('Last-Modified')).getTime();
|
|
118
|
+
if (serverLastModified > lastModified) {
|
|
119
|
+
console.log('Change detected via polling. Refreshing page...');
|
|
120
|
+
window.location.reload();
|
|
121
|
+
}
|
|
122
|
+
lastModified = serverLastModified;
|
|
123
|
+
})
|
|
124
|
+
.catch(error => console.error('Error checking for changes:', error));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Only use polling as a fallback if WebSocket fails
|
|
128
|
+
setTimeout(() => {
|
|
129
|
+
if (socket.readyState !== WebSocket.OPEN) {
|
|
130
|
+
console.log('WebSocket not connected. Falling back to polling.');
|
|
131
|
+
setInterval(checkForChanges, pollInterval);
|
|
132
|
+
}
|
|
133
|
+
}, 5000);
|
|
134
|
+
})();
|
|
73
135
|
</script>
|
|
74
136
|
`;
|
|
75
137
|
body = body.replace('</body>', `${liveReloadScript}</body>`);
|
|
@@ -80,6 +142,12 @@ async function startDevServer(configPathOption, options = { preserve: false }) {
|
|
|
80
142
|
next();
|
|
81
143
|
});
|
|
82
144
|
|
|
145
|
+
// Add Last-Modified header to all responses for polling fallback
|
|
146
|
+
app.use((req, res, next) => {
|
|
147
|
+
res.setHeader('Last-Modified', new Date().toUTCString());
|
|
148
|
+
next();
|
|
149
|
+
});
|
|
150
|
+
|
|
83
151
|
// Serve static files from the output directory
|
|
84
152
|
// This middleware needs to be dynamic if outputDir changes
|
|
85
153
|
let staticMiddleware = express.static(paths.outputDir);
|
|
@@ -95,21 +163,38 @@ async function startDevServer(configPathOption, options = { preserve: false }) {
|
|
|
95
163
|
// Optionally, don't start server if initial build fails, or serve a specific error page.
|
|
96
164
|
}
|
|
97
165
|
|
|
166
|
+
// Check if user assets directory exists
|
|
167
|
+
const userAssetsDirExists = await fs.pathExists(paths.userAssetsDir);
|
|
98
168
|
|
|
99
169
|
// Watch for changes
|
|
100
170
|
const watchedPaths = [
|
|
101
171
|
paths.srcDirToWatch,
|
|
102
172
|
paths.configFileToWatch,
|
|
103
|
-
DOCMD_TEMPLATES_DIR,
|
|
104
|
-
DOCMD_ASSETS_DIR
|
|
105
173
|
];
|
|
106
174
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
175
|
+
// Add user assets directory to watched paths if it exists
|
|
176
|
+
if (userAssetsDirExists) {
|
|
177
|
+
watchedPaths.push(paths.userAssetsDir);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Add internal paths for docmd development (not shown to end users)
|
|
181
|
+
const internalPaths = [DOCMD_TEMPLATES_DIR, DOCMD_ASSETS_DIR];
|
|
182
|
+
|
|
183
|
+
// Only in development environments, we might want to watch internal files too
|
|
184
|
+
if (process.env.DOCMD_DEV === 'true') {
|
|
185
|
+
watchedPaths.push(...internalPaths);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
console.log(`👀 Watching for changes in:`);
|
|
189
|
+
console.log(` - Source: ${paths.srcDirToWatch}`);
|
|
190
|
+
console.log(` - Config: ${paths.configFileToWatch}`);
|
|
191
|
+
if (userAssetsDirExists) {
|
|
192
|
+
console.log(` - Assets: ${paths.userAssetsDir}`);
|
|
193
|
+
}
|
|
194
|
+
if (process.env.DOCMD_DEV === 'true') {
|
|
195
|
+
console.log(` - docmd Templates: ${DOCMD_TEMPLATES_DIR} (internal)`);
|
|
196
|
+
console.log(` - docmd Assets: ${DOCMD_ASSETS_DIR} (internal)`);
|
|
197
|
+
}
|
|
113
198
|
|
|
114
199
|
const watcher = chokidar.watch(watchedPaths, {
|
|
115
200
|
ignored: /(^|[\/\\])\../, // ignore dotfiles
|
|
@@ -145,7 +230,7 @@ async function startDevServer(configPathOption, options = { preserve: false }) {
|
|
|
145
230
|
|
|
146
231
|
await buildSite(configPathOption, { isDev: true, preserve: options.preserve }); // Re-build using the potentially updated config path
|
|
147
232
|
broadcastReload();
|
|
148
|
-
console.log('✅ Rebuild complete. Browser
|
|
233
|
+
console.log('✅ Rebuild complete. Browser will refresh automatically.');
|
|
149
234
|
} catch (error) {
|
|
150
235
|
console.error('❌ Rebuild failed:', error.message, error.stack);
|
|
151
236
|
}
|
|
@@ -164,6 +249,7 @@ async function startDevServer(configPathOption, options = { preserve: false }) {
|
|
|
164
249
|
}
|
|
165
250
|
console.log(`🎉 Dev server started at http://localhost:${PORT}`);
|
|
166
251
|
console.log(`Serving content from: ${paths.outputDir}`);
|
|
252
|
+
console.log(`Live reload is active. Browser will refresh automatically when files change.`);
|
|
167
253
|
});
|
|
168
254
|
|
|
169
255
|
// Graceful shutdown
|
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 };
|
package/src/plugins/sitemap.js
CHANGED
|
@@ -6,11 +6,15 @@ const path = require('path');
|
|
|
6
6
|
* @param {Object} config - The full configuration object
|
|
7
7
|
* @param {Array} pages - Array of page objects with data about each processed page
|
|
8
8
|
* @param {string} outputDir - Path to the output directory
|
|
9
|
+
* @param {Object} options - Additional options
|
|
10
|
+
* @param {boolean} options.isDev - Whether running in development mode
|
|
9
11
|
*/
|
|
10
|
-
async function generateSitemap(config, pages, outputDir) {
|
|
12
|
+
async function generateSitemap(config, pages, outputDir, options = { isDev: false }) {
|
|
11
13
|
// Skip if no siteUrl is defined (sitemap needs absolute URLs)
|
|
12
14
|
if (!config.siteUrl) {
|
|
13
|
-
|
|
15
|
+
if (!options.isDev) {
|
|
16
|
+
console.warn('⚠️ No siteUrl defined in config. Skipping sitemap generation.');
|
|
17
|
+
}
|
|
14
18
|
return;
|
|
15
19
|
}
|
|
16
20
|
|
|
@@ -94,7 +98,10 @@ async function generateSitemap(config, pages, outputDir) {
|
|
|
94
98
|
const sitemapPath = path.join(outputDir, 'sitemap.xml');
|
|
95
99
|
await fs.writeFile(sitemapPath, sitemapXml);
|
|
96
100
|
|
|
97
|
-
|
|
101
|
+
// Only show sitemap generation message in production mode or if DOCMD_DEV is true
|
|
102
|
+
if (!options.isDev || process.env.DOCMD_DEV === 'true') {
|
|
103
|
+
console.log(`✅ Generated sitemap at ${sitemapPath}`);
|
|
104
|
+
}
|
|
98
105
|
}
|
|
99
106
|
|
|
100
107
|
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>
|