@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.
Files changed (73) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +61 -63
  3. package/bin/docmd.js +13 -16
  4. package/bin/postinstall.js +4 -4
  5. package/package.json +12 -10
  6. package/src/assets/css/docmd-highlight-dark.css +86 -1
  7. package/src/assets/css/docmd-highlight-light.css +86 -1
  8. package/src/assets/css/docmd-main.css +544 -464
  9. package/src/assets/css/docmd-theme-retro.css +105 -106
  10. package/src/assets/css/docmd-theme-ruby.css +92 -92
  11. package/src/assets/css/docmd-theme-sky.css +63 -64
  12. package/src/assets/favicon.ico +0 -0
  13. package/src/assets/images/docmd-logo-dark.png +0 -0
  14. package/src/assets/images/docmd-logo-light.png +0 -0
  15. package/src/assets/js/docmd-image-lightbox.js +2 -2
  16. package/src/assets/js/docmd-main.js +1 -1
  17. package/src/assets/js/docmd-mermaid.js +1 -1
  18. package/src/assets/js/docmd-search.js +1 -1
  19. package/src/commands/build.js +71 -370
  20. package/src/commands/dev.js +141 -80
  21. package/src/commands/init.js +107 -132
  22. package/src/commands/live.js +145 -0
  23. package/src/core/asset-manager.js +72 -0
  24. package/src/core/config-loader.js +2 -2
  25. package/src/core/config-validator.js +1 -1
  26. package/src/core/file-processor.js +13 -9
  27. package/src/core/fs-utils.js +40 -0
  28. package/src/core/html-formatter.js +97 -0
  29. package/src/core/html-generator.js +61 -65
  30. package/src/core/icon-renderer.js +1 -1
  31. package/src/core/logger.js +1 -1
  32. package/src/core/markdown/containers.js +1 -1
  33. package/src/core/markdown/renderers.js +1 -1
  34. package/src/core/markdown/rules.js +1 -2
  35. package/src/core/markdown/setup.js +1 -1
  36. package/src/core/navigation-helper.js +1 -1
  37. package/src/index.js +12 -0
  38. package/src/live/core.js +5 -1
  39. package/src/live/index.html +16 -1
  40. package/src/live/live.css +157 -68
  41. package/src/plugins/analytics.js +1 -1
  42. package/src/plugins/seo.js +26 -36
  43. package/src/plugins/sitemap.js +2 -2
  44. package/src/templates/layout.ejs +50 -81
  45. package/src/templates/navigation.ejs +23 -76
  46. package/src/templates/no-style.ejs +115 -129
  47. package/src/templates/partials/theme-init.js +1 -1
  48. package/src/templates/toc.ejs +6 -35
  49. package/dist/assets/css/docmd-highlight-dark.css +0 -1
  50. package/dist/assets/css/docmd-highlight-light.css +0 -1
  51. package/dist/assets/css/docmd-main.css +0 -1627
  52. package/dist/assets/css/docmd-theme-retro.css +0 -868
  53. package/dist/assets/css/docmd-theme-ruby.css +0 -629
  54. package/dist/assets/css/docmd-theme-sky.css +0 -618
  55. package/dist/assets/favicon.ico +0 -0
  56. package/dist/assets/images/docmd-logo-dark.png +0 -0
  57. package/dist/assets/images/docmd-logo-light.png +0 -0
  58. package/dist/assets/images/docmd-logo.png +0 -0
  59. package/dist/assets/js/docmd-image-lightbox.js +0 -74
  60. package/dist/assets/js/docmd-main.js +0 -222
  61. package/dist/assets/js/docmd-mermaid.js +0 -205
  62. package/dist/assets/js/docmd-search.js +0 -218
  63. package/dist/assets/js/mermaid.min.js +0 -2811
  64. package/dist/assets/js/minisearch.js +0 -2013
  65. package/dist/docmd-live.js +0 -30748
  66. package/dist/index.html +0 -201
  67. package/dist/live.css +0 -167
  68. package/docmd.config.js +0 -175
  69. package/scripts/build-live.js +0 -157
  70. package/scripts/failsafe.js +0 -37
  71. package/scripts/test-live.js +0 -54
  72. package/src/assets/images/docmd-logo.png +0 -0
  73. package/src/live/templates.js +0 -9
@@ -1,57 +1,27 @@
1
- // Source file from the docmd project — https://github.com/mgks/docmd
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, processMarkdownFile, findMarkdownFiles } = require('../core/file-processor');
7
+ const { createMarkdownItInstance, findMarkdownFiles, processMarkdownFile } = require('../core/file-processor');
7
8
  const { generateHtmlPage, generateNavigationHtml } = require('../core/html-generator');
8
- const { renderIcon, clearWarnedIcons } = require('../core/icon-renderer');
9
+ const { clearWarnedIcons } = require('../core/icon-renderer');
9
10
  const { findPageNeighbors } = require('../core/navigation-helper');
10
11
  const { generateSitemap } = require('../plugins/sitemap');
11
- const { version } = require('../../package.json');
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.pathExists(SRC_DIR)) {
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
- // Instead of emptying the entire directory, we'll selectively clean up HTML files
79
- // This preserves custom assets while ensuring we don't have stale HTML files
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
- // Track preserved files for summary report
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
- if (await fs.pathExists(assetsSrcDir)) {
168
- if (!options.isDev) {
169
- console.log(`📂 Copying docmd assets to ${formatPathForDisplay(assetsDestDir, CWD)}...`);
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
- // Check for Highlight.js themes
199
- const lightThemePath = path.join(__dirname, '..', 'assets', 'css', 'docmd-highlight-light.css');
200
- const darkThemePath = path.join(__dirname, '..', 'assets', 'css', 'docmd-highlight-dark.css');
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
- const themesMissing = !await fs.pathExists(lightThemePath) || !await fs.pathExists(darkThemePath);
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
- if (themesMissing) {
205
- // For 'docmd build', always show.
206
- // For 'docmd dev', show only once per session if not already shown.
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. Please ensure these files exist:
209
- - ${path.relative(CWD, lightThemePath)}
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
- // Array to collect information about all processed pages for sitemap
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
- const dirPath = path.dirname(relativePath);
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
- if (relativePathToRoot === '') {
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
- normalizedPath = '/' + normalizedPath;
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
- const cleanPath = prevPage.path.substring(1);
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: prevPage,
322
- nextPage: nextPage,
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
- pageUrl = pageUrl.substring(0, pageUrl.length - 10); // remove index.html
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, // URL is the ID
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(`❌ An unexpected error occurred while processing file ${path.relative(CWD, filePath)}:`, error);
182
+ console.error(`❌ Error processing ${path.relative(CWD, filePath)}:`, error);
360
183
  }
361
-
362
184
  }
363
185
 
364
- // Generate sitemap if enabled in config
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 search index if enabled
195
+ // 5. Generate Search Index
374
196
  if (config.search !== false) {
375
- if (!options.isDev) {
376
- console.log('🔍 Generating search index...');
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'], // fields to index for full-text search
382
- storeFields: ['title', 'id', 'text'], // fields to return with search results (don't store full text to keep JSON small)
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
- // Serialize to JSON
393
- const jsonIndex = JSON.stringify(miniSearch.toJSON());
394
- const searchIndexPath = path.join(OUTPUT_DIR, 'search-index.json');
395
- await fs.writeFile(searchIndexPath, jsonIndex);
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
- // Bundle third-party libraries into assets
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
- const filesToRemove = [];
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.push(...subDirFiles);
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 };