@mgks/docmd 0.3.7 → 0.3.8
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/LICENSE +1 -1
- package/README.md +61 -63
- package/bin/docmd.js +13 -16
- package/bin/postinstall.js +4 -4
- package/package.json +12 -10
- package/src/assets/css/docmd-highlight-dark.css +86 -1
- package/src/assets/css/docmd-highlight-light.css +86 -1
- package/src/assets/css/docmd-main.css +544 -464
- package/src/assets/css/docmd-theme-retro.css +105 -106
- package/src/assets/css/docmd-theme-ruby.css +92 -92
- package/src/assets/css/docmd-theme-sky.css +63 -64
- package/src/assets/favicon.ico +0 -0
- package/src/assets/images/docmd-logo-dark.png +0 -0
- package/src/assets/images/docmd-logo-light.png +0 -0
- package/src/assets/js/docmd-image-lightbox.js +2 -2
- package/src/assets/js/docmd-main.js +1 -1
- package/src/assets/js/docmd-mermaid.js +1 -1
- package/src/assets/js/docmd-search.js +1 -1
- package/src/commands/build.js +71 -370
- package/src/commands/dev.js +141 -80
- package/src/commands/init.js +107 -132
- package/src/commands/live.js +145 -0
- package/src/core/asset-manager.js +72 -0
- package/src/core/config-loader.js +2 -2
- package/src/core/config-validator.js +1 -1
- package/src/core/file-processor.js +13 -9
- package/src/core/fs-utils.js +40 -0
- package/src/core/html-formatter.js +97 -0
- package/src/core/html-generator.js +61 -65
- package/src/core/icon-renderer.js +1 -1
- package/src/core/logger.js +1 -1
- package/src/core/markdown/containers.js +1 -1
- package/src/core/markdown/renderers.js +1 -1
- package/src/core/markdown/rules.js +1 -2
- package/src/core/markdown/setup.js +1 -1
- package/src/core/navigation-helper.js +1 -1
- package/src/index.js +12 -0
- package/src/live/core.js +5 -1
- package/src/live/index.html +16 -1
- package/src/live/live.css +157 -68
- package/src/plugins/analytics.js +1 -1
- package/src/plugins/seo.js +26 -36
- package/src/plugins/sitemap.js +2 -2
- package/src/templates/layout.ejs +50 -81
- package/src/templates/navigation.ejs +23 -76
- package/src/templates/no-style.ejs +115 -129
- package/src/templates/partials/theme-init.js +1 -1
- package/src/templates/toc.ejs +6 -35
- package/dist/assets/css/docmd-highlight-dark.css +0 -1
- package/dist/assets/css/docmd-highlight-light.css +0 -1
- package/dist/assets/css/docmd-main.css +0 -1627
- package/dist/assets/css/docmd-theme-retro.css +0 -868
- package/dist/assets/css/docmd-theme-ruby.css +0 -629
- package/dist/assets/css/docmd-theme-sky.css +0 -618
- package/dist/assets/favicon.ico +0 -0
- package/dist/assets/images/docmd-logo-dark.png +0 -0
- package/dist/assets/images/docmd-logo-light.png +0 -0
- package/dist/assets/images/docmd-logo.png +0 -0
- package/dist/assets/js/docmd-image-lightbox.js +0 -74
- package/dist/assets/js/docmd-main.js +0 -222
- package/dist/assets/js/docmd-mermaid.js +0 -205
- package/dist/assets/js/docmd-search.js +0 -218
- package/dist/assets/js/mermaid.min.js +0 -2811
- package/dist/assets/js/minisearch.js +0 -2013
- package/dist/docmd-live.js +0 -30748
- package/dist/index.html +0 -201
- package/dist/live.css +0 -167
- package/docmd.config.js +0 -175
- package/scripts/build-live.js +0 -157
- package/scripts/failsafe.js +0 -37
- package/scripts/test-live.js +0 -54
- package/src/assets/images/docmd-logo.png +0 -0
- package/src/live/templates.js +0 -9
package/src/commands/build.js
CHANGED
|
@@ -1,57 +1,27 @@
|
|
|
1
|
-
// Source file from the docmd project — https://github.com/
|
|
1
|
+
// Source file from the docmd project — https://github.com/docmd-io/docmd
|
|
2
2
|
|
|
3
|
-
const fs = require('fs-extra');
|
|
4
3
|
const path = require('path');
|
|
4
|
+
const fs = require('../core/fs-utils');
|
|
5
|
+
const MiniSearch = require('minisearch');
|
|
5
6
|
const { loadConfig } = require('../core/config-loader');
|
|
6
|
-
const { createMarkdownItInstance,
|
|
7
|
+
const { createMarkdownItInstance, findMarkdownFiles, processMarkdownFile } = require('../core/file-processor');
|
|
7
8
|
const { generateHtmlPage, generateNavigationHtml } = require('../core/html-generator');
|
|
8
|
-
const {
|
|
9
|
+
const { clearWarnedIcons } = require('../core/icon-renderer');
|
|
9
10
|
const { findPageNeighbors } = require('../core/navigation-helper');
|
|
10
11
|
const { generateSitemap } = require('../plugins/sitemap');
|
|
11
|
-
const {
|
|
12
|
-
const matter = require('gray-matter');
|
|
13
|
-
const MarkdownIt = require('markdown-it');
|
|
14
|
-
const hljs = require('highlight.js');
|
|
15
|
-
const CleanCSS = require('clean-css');
|
|
16
|
-
const esbuild = require('esbuild');
|
|
17
|
-
const MiniSearch = require('minisearch');
|
|
18
|
-
|
|
19
|
-
// Debug function to log navigation information
|
|
20
|
-
function logNavigationPaths(pagePath, navPath, normalizedPath) {
|
|
21
|
-
console.log(`\nPage: ${pagePath}`);
|
|
22
|
-
console.log(`Navigation Path: ${navPath}`);
|
|
23
|
-
console.log(`Normalized Path: ${normalizedPath}`);
|
|
24
|
-
}
|
|
12
|
+
const { processAssets } = require('../core/asset-manager');
|
|
25
13
|
|
|
26
14
|
// Add a global or scoped flag to track if the warning has been shown in the current dev session
|
|
27
15
|
let highlightWarningShown = false;
|
|
28
16
|
|
|
29
|
-
// Asset version metadata - update this when making significant changes to assets
|
|
30
|
-
const ASSET_VERSIONS = {
|
|
31
|
-
'css/docmd-main.css': { version: version, description: 'Core styles' },
|
|
32
|
-
'css/docmd-theme-sky.css': { version: version, description: 'Sky theme' },
|
|
33
|
-
'css/docmd-highlight-light.css': { version: version, description: 'Light syntax highlighting' },
|
|
34
|
-
'css/docmd-highlight-dark.css': { version: version, description: 'Dark syntax highlighting' },
|
|
35
|
-
'js/docmd-theme-toggle.js': { version: version, description: 'Theme toggle functionality' },
|
|
36
|
-
// Add other assets here with their versions
|
|
37
|
-
};
|
|
38
|
-
|
|
39
17
|
/**
|
|
40
18
|
* Format paths for display to make them relative to CWD
|
|
41
|
-
* @param {string} absolutePath - The absolute path to format
|
|
42
|
-
* @param {string} cwd - Current working directory
|
|
43
|
-
* @returns {string} - Formatted relative path
|
|
44
19
|
*/
|
|
45
20
|
function formatPathForDisplay(absolutePath, cwd) {
|
|
46
|
-
// Get the relative path from CWD
|
|
47
21
|
const relativePath = path.relative(cwd, absolutePath);
|
|
48
|
-
|
|
49
|
-
// If it's not a subdirectory, prefix with ./ for clarity
|
|
50
22
|
if (!relativePath.startsWith('..') && !path.isAbsolute(relativePath)) {
|
|
51
23
|
return `./${relativePath}`;
|
|
52
24
|
}
|
|
53
|
-
|
|
54
|
-
// Return the relative path
|
|
55
25
|
return relativePath;
|
|
56
26
|
}
|
|
57
27
|
|
|
@@ -68,16 +38,15 @@ async function buildSite(configPath, options = { isDev: false, preserve: false,
|
|
|
68
38
|
const searchIndexData = [];
|
|
69
39
|
const isOfflineMode = options.offline === true;
|
|
70
40
|
|
|
71
|
-
if (!await fs.
|
|
41
|
+
if (!await fs.exists(SRC_DIR)) {
|
|
72
42
|
throw new Error(`Source directory not found: ${formatPathForDisplay(SRC_DIR, CWD)}`);
|
|
73
43
|
}
|
|
74
44
|
|
|
75
45
|
// Create output directory if it doesn't exist
|
|
76
46
|
await fs.ensureDir(OUTPUT_DIR);
|
|
77
47
|
|
|
78
|
-
//
|
|
79
|
-
|
|
80
|
-
if (await fs.pathExists(OUTPUT_DIR)) {
|
|
48
|
+
// Clean HTML files
|
|
49
|
+
if (await fs.exists(OUTPUT_DIR)) {
|
|
81
50
|
const cleanupFiles = await findFilesToCleanup(OUTPUT_DIR);
|
|
82
51
|
for (const file of cleanupFiles) {
|
|
83
52
|
await fs.remove(file);
|
|
@@ -87,246 +56,103 @@ async function buildSite(configPath, options = { isDev: false, preserve: false,
|
|
|
87
56
|
}
|
|
88
57
|
}
|
|
89
58
|
|
|
90
|
-
//
|
|
91
|
-
const preservedFiles = [];
|
|
92
|
-
const userAssetsCopied = [];
|
|
93
|
-
|
|
94
|
-
// Function to process and copy a single asset
|
|
95
|
-
const processAndCopyAsset = async (srcPath, destPath) => {
|
|
96
|
-
const ext = path.extname(srcPath).toLowerCase();
|
|
97
|
-
|
|
98
|
-
if (process.env.DOCMD_DEBUG) {
|
|
99
|
-
console.log(`[Asset Debug] Processing: ${path.basename(srcPath)} | Minify: ${shouldMinify} | Ext: ${ext}`);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
if (shouldMinify && ext === '.css') {
|
|
103
|
-
try {
|
|
104
|
-
const content = await fs.readFile(srcPath, 'utf8');
|
|
105
|
-
const output = new CleanCSS({}).minify(content);
|
|
106
|
-
if (output.errors.length > 0) {
|
|
107
|
-
console.warn(`⚠️ CSS Minification error for ${path.basename(srcPath)}, using original.`, output.errors);
|
|
108
|
-
await fs.copyFile(srcPath, destPath);
|
|
109
|
-
} else {
|
|
110
|
-
await fs.writeFile(destPath, output.styles);
|
|
111
|
-
}
|
|
112
|
-
} catch (e) {
|
|
113
|
-
console.warn(`⚠️ CSS processing failed: ${e.message}`);
|
|
114
|
-
await fs.copyFile(srcPath, destPath);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
else if (shouldMinify && ext === '.js') {
|
|
118
|
-
try {
|
|
119
|
-
const content = await fs.readFile(srcPath, 'utf8');
|
|
120
|
-
// Simple minification transform
|
|
121
|
-
const result = await esbuild.transform(content, {
|
|
122
|
-
minify: true,
|
|
123
|
-
loader: 'js',
|
|
124
|
-
target: 'es2015' // Ensure compatibility
|
|
125
|
-
});
|
|
126
|
-
await fs.writeFile(destPath, result.code);
|
|
127
|
-
} catch (e) {
|
|
128
|
-
console.warn(`⚠️ JS Minification failed: ${e.message}`);
|
|
129
|
-
await fs.copyFile(srcPath, destPath);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
else {
|
|
133
|
-
// Images, fonts, or dev mode -> standard copy
|
|
134
|
-
await fs.copyFile(srcPath, destPath);
|
|
135
|
-
}
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
// Copy user assets from root assets/ directory if it exists
|
|
139
|
-
if (await fs.pathExists(USER_ASSETS_DIR)) {
|
|
140
|
-
const assetsDestDir = path.join(OUTPUT_DIR, 'assets');
|
|
141
|
-
await fs.ensureDir(assetsDestDir);
|
|
142
|
-
|
|
143
|
-
if (!options.isDev) {
|
|
144
|
-
console.log(`📂 Copying user assets from ${formatPathForDisplay(USER_ASSETS_DIR, CWD)} to ${formatPathForDisplay(assetsDestDir, CWD)}...`);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
const userAssetFiles = await getAllFiles(USER_ASSETS_DIR);
|
|
148
|
-
for (const srcFile of userAssetFiles) {
|
|
149
|
-
const relativePath = path.relative(USER_ASSETS_DIR, srcFile);
|
|
150
|
-
const destFile = path.join(assetsDestDir, relativePath);
|
|
151
|
-
|
|
152
|
-
await fs.ensureDir(path.dirname(destFile));
|
|
153
|
-
await processAndCopyAsset(srcFile, destFile);
|
|
154
|
-
|
|
155
|
-
userAssetsCopied.push(relativePath);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
if (!options.isDev && userAssetsCopied.length > 0) {
|
|
159
|
-
console.log(`📦 Copied ${userAssetsCopied.length} user assets`);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Copy assets
|
|
164
|
-
const assetsSrcDir = path.join(__dirname, '..', 'assets');
|
|
59
|
+
// --- ASSET PROCESSING ---
|
|
165
60
|
const assetsDestDir = path.join(OUTPUT_DIR, 'assets');
|
|
61
|
+
await fs.ensureDir(assetsDestDir);
|
|
166
62
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
// Create destination directory if it doesn't exist
|
|
173
|
-
await fs.ensureDir(assetsDestDir);
|
|
174
|
-
|
|
175
|
-
// Get all files from source directory recursively
|
|
176
|
-
const assetFiles = await getAllFiles(assetsSrcDir);
|
|
177
|
-
for (const srcFile of assetFiles) {
|
|
178
|
-
const relativePath = path.relative(assetsSrcDir, srcFile);
|
|
179
|
-
const destFile = path.join(assetsDestDir, relativePath);
|
|
180
|
-
const fileExists = await fs.pathExists(destFile);
|
|
181
|
-
|
|
182
|
-
if (fileExists && (options.preserve || userAssetsCopied.includes(relativePath))) {
|
|
183
|
-
preservedFiles.push(relativePath);
|
|
184
|
-
if (!options.isDev && options.preserve) {
|
|
185
|
-
console.log(` Preserving existing file: ${relativePath}`);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
} else {
|
|
189
|
-
await fs.ensureDir(path.dirname(destFile));
|
|
190
|
-
await processAndCopyAsset(srcFile, destFile);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
63
|
+
// 1. Process Internal Assets First (Base)
|
|
64
|
+
const assetsSrcDir = path.join(__dirname, '..', 'assets');
|
|
65
|
+
if (await fs.exists(assetsSrcDir)) {
|
|
66
|
+
if (!options.isDev) console.log(`📂 Processing docmd assets...`);
|
|
67
|
+
await processAssets(assetsSrcDir, assetsDestDir, { minify: shouldMinify });
|
|
194
68
|
} else {
|
|
195
69
|
console.warn(`⚠️ Assets source directory not found: ${formatPathForDisplay(assetsSrcDir, CWD)}`);
|
|
196
70
|
}
|
|
197
71
|
|
|
198
|
-
//
|
|
199
|
-
|
|
200
|
-
|
|
72
|
+
// 2. Process User Assets Second (Overrides Internal)
|
|
73
|
+
if (await fs.exists(USER_ASSETS_DIR)) {
|
|
74
|
+
if (!options.isDev) console.log(`📂 Processing user assets...`);
|
|
75
|
+
await processAssets(USER_ASSETS_DIR, assetsDestDir, { minify: shouldMinify });
|
|
201
76
|
|
|
202
|
-
|
|
77
|
+
// Copy favicon to root if exists
|
|
78
|
+
const userFavicon = path.join(USER_ASSETS_DIR, 'favicon.ico');
|
|
79
|
+
if (await fs.exists(userFavicon)) {
|
|
80
|
+
await fs.copy(userFavicon, path.join(OUTPUT_DIR, 'favicon.ico'));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
203
83
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
84
|
+
// Check for Highlight.js themes presence (sanity check)
|
|
85
|
+
const lightThemePath = path.join(__dirname, '..', 'assets', 'css', 'docmd-highlight-light.css');
|
|
86
|
+
if (!await fs.exists(lightThemePath)) {
|
|
207
87
|
if (!options.isDev || (options.isDev && !highlightWarningShown)) {
|
|
208
|
-
console.warn(`⚠️ Highlight.js themes not found in assets.
|
|
209
|
-
|
|
210
|
-
- ${path.relative(CWD, darkThemePath)}
|
|
211
|
-
Syntax highlighting may not work correctly.`);
|
|
212
|
-
if (options.isDev) {
|
|
213
|
-
highlightWarningShown = true; // Mark as shown for this dev session
|
|
214
|
-
}
|
|
88
|
+
console.warn(`⚠️ Highlight.js themes not found in assets. Syntax highlighting may break.`);
|
|
89
|
+
if (options.isDev) highlightWarningShown = true;
|
|
215
90
|
}
|
|
216
91
|
}
|
|
217
92
|
|
|
218
|
-
//
|
|
93
|
+
// 3. Process Markdown Content
|
|
219
94
|
const processedPages = [];
|
|
220
|
-
|
|
221
|
-
// Set to track processed files for dev mode
|
|
222
95
|
const processedFiles = new Set();
|
|
223
|
-
|
|
224
|
-
// Find all Markdown files in the source directory
|
|
225
96
|
const markdownFiles = await findMarkdownFiles(SRC_DIR);
|
|
226
|
-
if (!options.isDev) {
|
|
227
|
-
console.log(`📄 Found ${markdownFiles.length} markdown files.`);
|
|
228
|
-
}
|
|
97
|
+
if (!options.isDev) console.log(`📄 Found ${markdownFiles.length} markdown files.`);
|
|
229
98
|
|
|
230
|
-
// Process each Markdown file
|
|
231
99
|
for (const filePath of markdownFiles) {
|
|
232
100
|
try {
|
|
233
101
|
const relativePath = path.relative(SRC_DIR, filePath);
|
|
234
102
|
|
|
235
103
|
// Skip file if already processed in this dev build cycle
|
|
236
|
-
if (options.noDoubleProcessing && processedFiles.has(relativePath))
|
|
237
|
-
continue;
|
|
238
|
-
}
|
|
104
|
+
if (options.noDoubleProcessing && processedFiles.has(relativePath)) continue;
|
|
239
105
|
processedFiles.add(relativePath);
|
|
240
106
|
|
|
241
|
-
// Pass the md instance to the processor
|
|
242
107
|
const processedData = await processMarkdownFile(filePath, md, config);
|
|
108
|
+
if (!processedData) continue;
|
|
243
109
|
|
|
244
|
-
// If processing failed (e.g., bad frontmatter), skip this file.
|
|
245
|
-
if (!processedData) {
|
|
246
|
-
continue;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// Destructure the valid data
|
|
250
110
|
const { frontmatter: pageFrontmatter, htmlContent, headings, searchData } = processedData;
|
|
251
|
-
|
|
252
111
|
const isIndexFile = path.basename(relativePath) === 'index.md';
|
|
253
112
|
|
|
254
113
|
let outputHtmlPath;
|
|
255
114
|
if (isIndexFile) {
|
|
256
|
-
|
|
257
|
-
outputHtmlPath = path.join(dirPath, 'index.html');
|
|
115
|
+
outputHtmlPath = path.join(path.dirname(relativePath), 'index.html');
|
|
258
116
|
} else {
|
|
259
117
|
outputHtmlPath = relativePath.replace(/\.md$/, '/index.html');
|
|
260
118
|
}
|
|
261
119
|
|
|
262
120
|
const finalOutputHtmlPath = path.join(OUTPUT_DIR, outputHtmlPath);
|
|
263
|
-
|
|
121
|
+
|
|
264
122
|
let relativePathToRoot = path.relative(path.dirname(finalOutputHtmlPath), OUTPUT_DIR);
|
|
265
|
-
|
|
266
|
-
relativePathToRoot = './';
|
|
267
|
-
} else {
|
|
268
|
-
relativePathToRoot = relativePathToRoot.replace(/\\/g, '/') + '/';
|
|
269
|
-
}
|
|
123
|
+
relativePathToRoot = relativePathToRoot === '' ? './' : relativePathToRoot.replace(/\\/g, '/') + '/';
|
|
270
124
|
|
|
271
125
|
let normalizedPath = path.relative(SRC_DIR, filePath).replace(/\\/g, '/');
|
|
272
126
|
if (path.basename(normalizedPath) === 'index.md') {
|
|
273
127
|
normalizedPath = path.dirname(normalizedPath);
|
|
274
|
-
if (normalizedPath === '.')
|
|
275
|
-
normalizedPath = '';
|
|
276
|
-
}
|
|
128
|
+
if (normalizedPath === '.') normalizedPath = '';
|
|
277
129
|
} else {
|
|
278
130
|
normalizedPath = normalizedPath.replace(/\.md$/, '');
|
|
279
131
|
}
|
|
280
132
|
|
|
281
|
-
if (!normalizedPath.startsWith('/'))
|
|
282
|
-
|
|
283
|
-
}
|
|
284
|
-
if (normalizedPath.length > 1 && !normalizedPath.endsWith('/')) {
|
|
285
|
-
normalizedPath += '/';
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
const currentPagePathForNav = normalizedPath;
|
|
133
|
+
if (!normalizedPath.startsWith('/')) normalizedPath = '/' + normalizedPath;
|
|
134
|
+
if (normalizedPath.length > 1 && !normalizedPath.endsWith('/')) normalizedPath += '/';
|
|
289
135
|
|
|
290
136
|
const navigationHtml = await generateNavigationHtml(
|
|
291
|
-
config.navigation,
|
|
292
|
-
normalizedPath,
|
|
293
|
-
relativePathToRoot,
|
|
294
|
-
config,
|
|
295
|
-
isOfflineMode
|
|
137
|
+
config.navigation, normalizedPath, relativePathToRoot, config, isOfflineMode
|
|
296
138
|
);
|
|
297
139
|
|
|
298
|
-
// Find previous and next pages for navigation
|
|
299
140
|
const { prevPage, nextPage } = findPageNeighbors(config.navigation, normalizedPath);
|
|
300
141
|
|
|
301
|
-
if (prevPage)
|
|
302
|
-
|
|
303
|
-
prevPage.url = relativePathToRoot + cleanPath;
|
|
304
|
-
}
|
|
142
|
+
if (prevPage) prevPage.url = relativePathToRoot + prevPage.path.substring(1);
|
|
143
|
+
if (nextPage) nextPage.url = relativePathToRoot + nextPage.path.substring(1);
|
|
305
144
|
|
|
306
|
-
if (nextPage) {
|
|
307
|
-
const cleanPath = nextPage.path.substring(1);
|
|
308
|
-
nextPage.url = relativePathToRoot + cleanPath;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
// Log navigation paths for debugging
|
|
312
145
|
const pageDataForTemplate = {
|
|
313
146
|
content: htmlContent,
|
|
314
147
|
pageTitle: pageFrontmatter.title || 'Untitled',
|
|
315
148
|
siteTitle: config.siteTitle,
|
|
316
|
-
navigationHtml,
|
|
317
|
-
relativePathToRoot: relativePathToRoot,
|
|
318
|
-
config: config,
|
|
319
|
-
frontmatter: pageFrontmatter,
|
|
149
|
+
navigationHtml, relativePathToRoot, config, frontmatter: pageFrontmatter,
|
|
320
150
|
outputPath: outputHtmlPath.replace(/\\/g, '/'),
|
|
321
|
-
prevPage:
|
|
322
|
-
|
|
323
|
-
currentPagePath: normalizedPath,
|
|
324
|
-
headings: headings || [],
|
|
325
|
-
isOfflineMode,
|
|
151
|
+
prevPage, nextPage, currentPagePath: normalizedPath,
|
|
152
|
+
headings: headings || [], isOfflineMode,
|
|
326
153
|
};
|
|
327
154
|
|
|
328
155
|
const pageHtml = await generateHtmlPage(pageDataForTemplate, isOfflineMode);
|
|
329
|
-
|
|
330
156
|
await fs.ensureDir(path.dirname(finalOutputHtmlPath));
|
|
331
157
|
await fs.writeFile(finalOutputHtmlPath, pageHtml);
|
|
332
158
|
|
|
@@ -342,26 +168,22 @@ async function buildSite(configPath, options = { isDev: false, preserve: false,
|
|
|
342
168
|
// Collect Search Data
|
|
343
169
|
if (searchData) {
|
|
344
170
|
let pageUrl = outputHtmlPath.replace(/\\/g, '/');
|
|
345
|
-
if (pageUrl.endsWith('/index.html'))
|
|
346
|
-
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
// Add to index array
|
|
171
|
+
if (pageUrl.endsWith('/index.html')) pageUrl = pageUrl.substring(0, pageUrl.length - 10);
|
|
172
|
+
|
|
350
173
|
searchIndexData.push({
|
|
351
|
-
id: pageUrl,
|
|
352
|
-
title: searchData.title,
|
|
353
|
-
text: searchData.content,
|
|
174
|
+
id: pageUrl,
|
|
175
|
+
title: searchData.title,
|
|
176
|
+
text: searchData.content,
|
|
354
177
|
headings: searchData.headings.join(' ')
|
|
355
178
|
});
|
|
356
179
|
}
|
|
357
180
|
|
|
358
181
|
} catch (error) {
|
|
359
|
-
console.error(`❌
|
|
182
|
+
console.error(`❌ Error processing ${path.relative(CWD, filePath)}:`, error);
|
|
360
183
|
}
|
|
361
|
-
|
|
362
184
|
}
|
|
363
185
|
|
|
364
|
-
// Generate
|
|
186
|
+
// 4. Generate Sitemap
|
|
365
187
|
if (config.plugins?.sitemap !== false) {
|
|
366
188
|
try {
|
|
367
189
|
await generateSitemap(config, processedPages, OUTPUT_DIR, { isDev: options.isDev });
|
|
@@ -370,167 +192,46 @@ async function buildSite(configPath, options = { isDev: false, preserve: false,
|
|
|
370
192
|
}
|
|
371
193
|
}
|
|
372
194
|
|
|
373
|
-
// Generate
|
|
195
|
+
// 5. Generate Search Index
|
|
374
196
|
if (config.search !== false) {
|
|
375
|
-
if (!options.isDev)
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
// Create MiniSearch instance
|
|
197
|
+
if (!options.isDev) console.log('🔍 Generating search index...');
|
|
198
|
+
|
|
199
|
+
// MiniSearch build process (server-side)
|
|
380
200
|
const miniSearch = new MiniSearch({
|
|
381
|
-
fields: ['title', 'headings', 'text'],
|
|
382
|
-
storeFields: ['title', 'id', 'text'],
|
|
383
|
-
searchOptions: {
|
|
384
|
-
boost: { title: 2, headings: 1.5 }, // title matches are more important
|
|
385
|
-
fuzzy: 0.2
|
|
386
|
-
}
|
|
201
|
+
fields: ['title', 'headings', 'text'],
|
|
202
|
+
storeFields: ['title', 'id', 'text'],
|
|
203
|
+
searchOptions: { boost: { title: 2, headings: 1.5 }, fuzzy: 0.2 }
|
|
387
204
|
});
|
|
388
|
-
|
|
389
|
-
// Add documents
|
|
205
|
+
|
|
390
206
|
miniSearch.addAll(searchIndexData);
|
|
391
|
-
|
|
392
|
-
//
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
if (!options.isDev) {
|
|
398
|
-
console.log(`✅ Search index generated (${(jsonIndex.length / 1024).toFixed(1)} KB)`);
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
// Print summary of preserved files at the end of build
|
|
403
|
-
if (preservedFiles.length > 0 && !options.isDev) {
|
|
404
|
-
console.log(`\n📋 Build Summary: ${preservedFiles.length} existing files were preserved:`);
|
|
405
|
-
preservedFiles.forEach(file => console.log(` - assets/${file}`));
|
|
406
|
-
console.log(`\nTo update these files in future builds, run without the --preserve flag.`);
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
if (userAssetsCopied.length > 0 && !options.isDev) {
|
|
410
|
-
console.log(`\n📋 User Assets: ${userAssetsCopied.length} files were copied from your assets/ directory:`);
|
|
411
|
-
if (userAssetsCopied.length <= 10) {
|
|
412
|
-
userAssetsCopied.forEach(file => console.log(` - assets/${file}`));
|
|
413
|
-
} else {
|
|
414
|
-
userAssetsCopied.slice(0, 5).forEach(file => console.log(` - assets/${file}`));
|
|
415
|
-
console.log(` - ... and ${userAssetsCopied.length - 5} more files`);
|
|
416
|
-
}
|
|
207
|
+
|
|
208
|
+
// Save the index JSON
|
|
209
|
+
await fs.writeFile(path.join(OUTPUT_DIR, 'search-index.json'), JSON.stringify(miniSearch.toJSON()));
|
|
210
|
+
|
|
211
|
+
if (!options.isDev) console.log(`✅ Search index generated.`);
|
|
417
212
|
}
|
|
418
213
|
|
|
419
|
-
|
|
420
|
-
const copyLibrary = async (packageName, fileToBundle, destFileName) => {
|
|
421
|
-
try {
|
|
422
|
-
let srcPath;
|
|
423
|
-
|
|
424
|
-
// 1. Resolve Source Path
|
|
425
|
-
try {
|
|
426
|
-
srcPath = require.resolve(`${packageName}/${fileToBundle}`);
|
|
427
|
-
} catch (e) {
|
|
428
|
-
const mainPath = require.resolve(packageName);
|
|
429
|
-
let currentDir = path.dirname(mainPath);
|
|
430
|
-
let packageRoot = null;
|
|
431
|
-
|
|
432
|
-
for (let i = 0; i < 5; i++) {
|
|
433
|
-
if (await fs.pathExists(path.join(currentDir, 'package.json'))) {
|
|
434
|
-
packageRoot = currentDir;
|
|
435
|
-
break;
|
|
436
|
-
}
|
|
437
|
-
currentDir = path.dirname(currentDir);
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
if (packageRoot) {
|
|
441
|
-
srcPath = path.join(packageRoot, fileToBundle);
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
// 2. Process and Write
|
|
446
|
-
if (srcPath && await fs.pathExists(srcPath)) {
|
|
447
|
-
const destPath = path.join(OUTPUT_DIR, 'assets/js', destFileName);
|
|
448
|
-
|
|
449
|
-
// Read content
|
|
450
|
-
let content = await fs.readFile(srcPath, 'utf8');
|
|
451
|
-
|
|
452
|
-
// This prevents the browser from looking for index.js.map or similar files we didn't copy
|
|
453
|
-
content = content.replace(/\/\/# sourceMappingURL=.*$/gm, '');
|
|
454
|
-
|
|
455
|
-
// Minify if production build
|
|
456
|
-
if (shouldMinify) {
|
|
457
|
-
try {
|
|
458
|
-
const result = await esbuild.transform(content, {
|
|
459
|
-
minify: true,
|
|
460
|
-
loader: 'js',
|
|
461
|
-
target: 'es2015'
|
|
462
|
-
});
|
|
463
|
-
await fs.writeFile(destPath, result.code);
|
|
464
|
-
} catch (minErr) {
|
|
465
|
-
console.warn(`⚠️ Minification failed for ${packageName}, using sanitized original.`, minErr.message);
|
|
466
|
-
await fs.writeFile(destPath, content);
|
|
467
|
-
}
|
|
468
|
-
} else {
|
|
469
|
-
// Write sanitized original in dev mode
|
|
470
|
-
await fs.writeFile(destPath, content);
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
} else {
|
|
474
|
-
console.warn(`⚠️ Could not locate ${fileToBundle} in ${packageName}`);
|
|
475
|
-
}
|
|
476
|
-
} catch (e) {
|
|
477
|
-
console.warn(`⚠️ Failed to bundle ${packageName}: ${e.message}`);
|
|
478
|
-
}
|
|
479
|
-
};
|
|
480
|
-
|
|
481
|
-
// Bundle MiniSearch
|
|
482
|
-
await copyLibrary('minisearch', 'dist/umd/index.js', 'minisearch.js');
|
|
483
|
-
|
|
484
|
-
// Bundle Mermaid
|
|
485
|
-
await copyLibrary('mermaid', 'dist/mermaid.min.js', 'mermaid.min.js');
|
|
486
|
-
|
|
487
|
-
return {
|
|
488
|
-
config,
|
|
489
|
-
processedPages,
|
|
490
|
-
markdownFiles,
|
|
491
|
-
};
|
|
214
|
+
return { config, processedPages, markdownFiles };
|
|
492
215
|
}
|
|
493
216
|
|
|
494
217
|
// Helper function to find HTML files and sitemap.xml to clean up
|
|
495
218
|
async function findFilesToCleanup(dir) {
|
|
496
|
-
|
|
219
|
+
let filesToRemove = [];
|
|
497
220
|
const items = await fs.readdir(dir, { withFileTypes: true });
|
|
498
221
|
|
|
499
222
|
for (const item of items) {
|
|
500
223
|
const fullPath = path.join(dir, item.name);
|
|
501
224
|
|
|
502
225
|
if (item.isDirectory()) {
|
|
503
|
-
// Don't delete the assets directory
|
|
504
226
|
if (item.name !== 'assets') {
|
|
505
227
|
const subDirFiles = await findFilesToCleanup(fullPath);
|
|
506
|
-
filesToRemove.
|
|
228
|
+
filesToRemove = filesToRemove.concat(subDirFiles);
|
|
507
229
|
}
|
|
508
|
-
} else if (
|
|
509
|
-
item.name.endsWith('.html') ||
|
|
510
|
-
item.name === 'sitemap.xml'
|
|
511
|
-
) {
|
|
230
|
+
} else if (item.name.endsWith('.html') || item.name === 'sitemap.xml') {
|
|
512
231
|
filesToRemove.push(fullPath);
|
|
513
232
|
}
|
|
514
233
|
}
|
|
515
|
-
|
|
516
234
|
return filesToRemove;
|
|
517
235
|
}
|
|
518
236
|
|
|
519
|
-
// Helper function to recursively get all files in a directory
|
|
520
|
-
async function getAllFiles(dir) {
|
|
521
|
-
const files = [];
|
|
522
|
-
const items = await fs.readdir(dir, { withFileTypes: true });
|
|
523
|
-
|
|
524
|
-
for (const item of items) {
|
|
525
|
-
const fullPath = path.join(dir, item.name);
|
|
526
|
-
if (item.isDirectory()) {
|
|
527
|
-
files.push(...await getAllFiles(fullPath));
|
|
528
|
-
} else {
|
|
529
|
-
files.push(fullPath);
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
return files;
|
|
534
|
-
}
|
|
535
|
-
|
|
536
237
|
module.exports = { buildSite };
|