@mgks/docmd 0.3.6 → 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 (53) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +86 -77
  3. package/bin/docmd.js +13 -16
  4. package/bin/postinstall.js +4 -4
  5. package/package.json +19 -16
  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 +14 -6
  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 +199 -72
  21. package/src/commands/init.js +135 -134
  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/docmd.config.js +0 -175
  50. package/scripts/build-live.js +0 -157
  51. package/scripts/test-live.js +0 -54
  52. package/src/assets/images/docmd-logo.png +0 -0
  53. package/src/live/templates.js +0 -9
@@ -1,162 +1,165 @@
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');
3
+ const fs = require('../core/fs-utils');
4
4
  const path = require('path');
5
5
  const readline = require('readline');
6
+ const { version } = require('../../package.json');
6
7
 
7
- const defaultConfigContent = `// docmd.config.js: basic config for docmd
8
+ const defaultConfigContent = `// docmd.config.js
8
9
  module.exports = {
9
- // Core Site Metadata
10
- siteTitle: 'docmd',
11
- // Define a base URL for your site, crucial for SEO and absolute paths
12
- // No trailing slash
13
- siteUrl: '', // Replace with your actual deployed URL
10
+ // --- Core Metadata ---
11
+ siteTitle: 'My Documentation',
12
+ siteUrl: '', // e.g. https://mysite.com (Critical for SEO/Sitemap)
14
13
 
15
- // Logo Configuration
14
+ // --- Branding ---
16
15
  logo: {
17
- light: 'assets/images/docmd-logo-light.png', // Path relative to outputDir root
18
- dark: 'assets/images/docmd-logo-dark.png', // Path relative to outputDir root
19
- alt: 'docmd logo', // Alt text for the logo
20
- href: './', // Link for the logo, defaults to site root
16
+ light: 'assets/images/docmd-logo-dark.png',
17
+ dark: 'assets/images/docmd-logo-light.png',
18
+ alt: 'Logo',
19
+ href: './',
21
20
  },
21
+ favicon: 'assets/favicon.ico',
22
22
 
23
- // Directory Configuration
24
- srcDir: 'docs', // Source directory for Markdown files
25
- outputDir: 'site', // Directory for generated static site
26
-
27
- // Search Configuration
28
- search: true, // Enable/disable search functionality
29
-
30
- // Build Options
31
- minify: true, // Enable/disable HTML/CSS/JS minification
23
+ // --- Source & Output ---
24
+ srcDir: 'docs',
25
+ outputDir: 'site',
32
26
 
33
- // Sidebar Configuration
34
- sidebar: {
35
- collapsible: true, // or false to disable
36
- defaultCollapsed: false, // or true to start collapsed
37
- },
38
-
39
- // Theme Configuration
27
+ // --- Theme & Layout ---
40
28
  theme: {
41
- name: 'sky', // Themes: 'default', 'sky'
42
- defaultMode: 'light', // Initial color mode: 'light' or 'dark'
43
- enableModeToggle: true, // Show UI button to toggle light/dark modes
44
- positionMode: 'top', // 'top' or 'bottom' for the theme toggle
45
- codeHighlight: true, // Enable/disable codeblock highlighting and import of highlight.js
46
- customCss: [ // Array of paths to custom CSS files
47
- // 'assets/css/custom.css', // Custom TOC styles
48
- ]
29
+ name: 'sky', // Options: 'default', 'sky', 'ruby', 'retro'
30
+ defaultMode: 'system', // 'light', 'dark', or 'system'
31
+ enableModeToggle: true, // Show mode toggle button
32
+ positionMode: 'top', // 'top' or 'bottom'
33
+ codeHighlight: true, // Enable Highlight.js
34
+ customCss: [], // e.g. ['assets/css/custom.css']
49
35
  },
50
36
 
51
- // Custom JavaScript Files
52
- customJs: [ // Array of paths to custom JS files, loaded at end of body
53
- // 'assets/js/custom-script.js', // Paths relative to outputDir root
54
- 'assets/js/docmd-image-lightbox.js', // Image lightbox functionality
55
- ],
37
+ // --- Features ---
38
+ search: true, // Built-in offline search
39
+ minify: true, // Minify HTML/CSS/JS in build
40
+ autoTitleFromH1: true, // Auto-generate page title from first H1
41
+ copyCode: true, // Show "copy" button on code blocks
42
+ pageNavigation: true, // Prev/Next buttons at bottom
56
43
 
57
- // Content Processing
58
- autoTitleFromH1: true, // Set to true to automatically use the first H1 as page title
59
- copyCode: true, // Enable/disable the copy code button on code blocks
44
+ // --- Navigation (Sidebar) ---
45
+ navigation: [
46
+ { title: 'Introduction', path: '/', icon: 'home' },
47
+ {
48
+ title: 'Guide',
49
+ icon: 'book-open',
50
+ collapsible: true,
51
+ children: [
52
+ { title: 'Getting Started', path: 'https://docs.docmd.io/getting-started/installation', icon: 'rocket', external: true },
53
+ { title: 'Configuration', path: 'https://docs.docmd.io/configuration', icon: 'settings', external: true },
54
+ ],
55
+ },
56
+ { title: 'Live Editor', path: 'https://live.docmd.io', icon: 'pencil-ruler', external: true },
57
+ { title: 'GitHub', path: 'https://github.com/docmd-io/docmd', icon: 'github', external: true },
58
+ ],
60
59
 
61
- // Plugins Configuration
62
- // Plugins are configured here. docmd will look for these keys.
60
+ // --- Plugins ---
63
61
  plugins: {
64
- // SEO Plugin Configuration
65
- // Most SEO data is pulled from page frontmatter (title, description, image, etc.)
66
- // These are fallbacks or site-wide settings.
67
62
  seo: {
68
- // Default meta description if a page doesn't have one in its frontmatter
69
- defaultDescription: 'docmd is a Node.js command-line tool for generating beautiful, lightweight static documentation sites from Markdown files.',
70
- openGraph: { // For Facebook, LinkedIn, etc.
71
- // siteName: 'docmd Documentation', // Optional, defaults to config.siteTitle
72
- // Default image for og:image if not specified in page frontmatter
73
- // Path relative to outputDir root
74
- defaultImage: 'assets/images/docmd-preview.png',
63
+ defaultDescription: 'Documentation built with docmd.',
64
+ openGraph: {
65
+ defaultImage: '', // e.g. 'assets/images/og-image.png'
75
66
  },
76
- twitter: { // For Twitter Cards
77
- cardType: 'summary_large_image', // 'summary', 'summary_large_image'
78
- // siteUsername: '@docmd_handle', // Your site's Twitter handle (optional)
79
- // creatorUsername: '@your_handle', // Default author handle (optional, can be overridden in frontmatter)
67
+ twitter: {
68
+ cardType: 'summary_large_image',
80
69
  }
81
70
  },
82
- // Analytics Plugin Configuration
83
71
  analytics: {
84
- // Google Analytics 4 (GA4)
85
72
  googleV4: {
86
- measurementId: 'G-8QVBDQ4KM1' // Replace with your actual GA4 Measurement ID
73
+ measurementId: 'G-X9WTDL262N' // Replace with your Google Analytics Measurement ID
87
74
  }
88
75
  },
89
- // Enable Sitemap plugin
90
76
  sitemap: {
91
- defaultChangefreq: 'weekly',
92
- defaultPriority: 0.8
77
+ defaultChangefreq: 'weekly', // e.g. 'daily', 'weekly', 'monthly'
78
+ defaultPriority: 0.8 // Priority between 0.0 and 1.0
93
79
  }
94
- // Add other future plugin configurations here by their key
95
80
  },
96
81
 
97
- // "Edit this page" Link Configuration
82
+ // --- Footer ---
83
+ footer: '© ' + new Date().getFullYear() + ' My Project. Built with [docmd](https://docmd.io).',
84
+
85
+ // --- Edit Link ---
98
86
  editLink: {
99
87
  enabled: false,
100
- // The URL to the folder containing your docs in the git repo
101
- // Note: It usually ends with /edit/main/docs or /blob/main/docs
102
- baseUrl: 'https://github.com/mgks/docmd/edit/main/docs',
103
- text: 'Edit this page on GitHub'
104
- },
105
-
106
- // Navigation Structure (Sidebar)
107
- // Icons are kebab-case names from Lucide Icons (https://lucide.dev/)
108
- navigation: [
109
- { title: 'Welcome', path: '/', icon: 'home' }, // Corresponds to docs/index.md
110
- {
111
- title: 'Getting Started',
112
- icon: 'rocket',
113
- path: '#',
114
- collapsible: true, // This makes the menu section collapsible
115
- children: [
116
- { title: 'Documentation', path: 'https://docmd.mgks.dev', icon: 'scroll', external: true },
117
- { title: 'Installation', path: 'https://docmd.mgks.dev/getting-started/installation', icon: 'download', external: true },
118
- { title: 'Basic Usage', path: 'https://docmd.mgks.dev/getting-started/basic-usage', icon: 'play', external: true },
119
- { title: 'Content', path: 'https://docmd.mgks.dev/content', icon: 'layout-template', external: true },
120
- ],
121
- },
122
- // External links:
123
- { title: 'GitHub', path: 'https://github.com/mgks/docmd', icon: 'github', external: true },
124
- { title: 'Support the Project', path: 'https://github.com/sponsors/mgks', icon: 'heart', external: true },
125
- ],
126
-
127
- pageNavigation: true, // Enable previous / next page navigation at the bottom of each page
128
-
129
- // Sponsor Ribbon Configuration
130
- Sponsor: {
131
- enabled: false,
132
- title: 'Support docmd',
133
- link: 'https://github.com/sponsors/mgks',
134
- },
135
-
136
- // Footer Configuration
137
- // Markdown is supported here.
138
- footer: '© ' + new Date().getFullYear() + ' Project.',
139
-
140
- // Favicon Configuration
141
- // Path relative to outputDir root
142
- favicon: 'assets/favicon.ico',
88
+ baseUrl: 'https://github.com/USERNAME/REPO/edit/main/docs',
89
+ text: 'Edit this page'
90
+ }
143
91
  };
144
92
  `;
145
93
 
146
94
  const defaultIndexMdContent = `---
147
95
  title: "Welcome"
148
- description: "Your documentation starts here."
96
+ description: "Welcome to your new documentation site."
149
97
  ---
150
98
 
151
- # Hello, docmd!
99
+ # Welcome to Your Docs
100
+
101
+ Congratulations! You have successfully initialized a new **docmd** project.
102
+
103
+ ## 🚀 Quick Start
104
+
105
+ You are currently viewing the content of \`docs/index.md\`.
152
106
 
153
- Start writing your Markdown content here.
107
+ \`\`\`bash
108
+ npm start # Start the dev server
109
+ docmd build # Build for production
110
+ \`\`\`
111
+
112
+ ## ✨ Features Demo
113
+
114
+ docmd comes with built-in components to make your documentation beautiful.
115
+
116
+ ::: callout tip
117
+ **Try this:** Edit this file and save it. The browser will live reload instantly!
118
+ :::
119
+
120
+ ### Container Examples
121
+
122
+ ::: card Flexible Structure
123
+ **Organize your way.**
124
+ Create Markdown files in the \`docs/\` folder and map them in \`docmd.config.js\`.
125
+ :::
126
+
127
+ ::: tabs
128
+ == tab "Simple"
129
+ This is a simple tab content.
130
+
131
+ == tab "Nested"
132
+ ::: callout info
133
+ You can even nest other components inside tabs!
134
+ :::
135
+ :::
136
+
137
+ ## 📚 Next Steps
138
+
139
+ * [Check the Official Documentation](https://docs.docmd.io)
140
+ * [Customize your Theme](https://docs.docmd.io/theming)
141
+ * [Deploy to GitHub Pages](https://docs.docmd.io/deployment)
154
142
  `;
155
143
 
144
+ const defaultPackageJson = {
145
+ name: "my-docs",
146
+ version: "0.0.1",
147
+ private: true,
148
+ scripts: {
149
+ "dev": "docmd dev",
150
+ "build": "docmd build",
151
+ "preview": "npx serve site"
152
+ },
153
+ dependencies: {
154
+ "@mgks/docmd": `^${version}`
155
+ }
156
+ };
157
+
156
158
  async function initProject() {
157
159
  const baseDir = process.cwd();
158
- const docsDir = path.join(baseDir, 'docs');
160
+ const packageJsonFile = path.join(baseDir, 'package.json');
159
161
  const configFile = path.join(baseDir, 'docmd.config.js');
162
+ const docsDir = path.join(baseDir, 'docs');
160
163
  const indexMdFile = path.join(docsDir, 'index.md');
161
164
  const assetsDir = path.join(baseDir, 'assets');
162
165
  const assetsCssDir = path.join(assetsDir, 'css');
@@ -169,19 +172,28 @@ async function initProject() {
169
172
  assets: false
170
173
  };
171
174
 
175
+ // Check if package.json exists
176
+ if (!await fs.pathExists(packageJsonFile)) {
177
+ await fs.writeJson(packageJsonFile, defaultPackageJson, { spaces: 2 });
178
+ console.log('📦 Created `package.json` (Deployment Ready)');
179
+ } else {
180
+ console.log('⏭️ Skipped existing `package.json`');
181
+ }
182
+
172
183
  // Check each file individually
173
184
  if (await fs.pathExists(configFile)) {
174
185
  existingFiles.push('docmd.config.js');
175
186
  }
176
- // Check for the config.js as well to warn the user
187
+
188
+ // Check for the legacy config.js
177
189
  const oldConfigFile = path.join(baseDir, 'config.js');
178
190
  if (await fs.pathExists(oldConfigFile)) {
179
191
  existingFiles.push('config.js');
180
192
  }
181
193
 
194
+ // Check if docs directory exists
182
195
  if (await fs.pathExists(docsDir)) {
183
196
  dirExists.docs = true;
184
-
185
197
  if (await fs.pathExists(indexMdFile)) {
186
198
  existingFiles.push('docs/index.md');
187
199
  }
@@ -234,21 +246,9 @@ async function initProject() {
234
246
  } else {
235
247
  console.log('📁 Using existing `assets/` directory');
236
248
 
237
- // Create subdirectories if they don't exist
238
- if (!await fs.pathExists(assetsCssDir)) {
239
- await fs.ensureDir(assetsCssDir);
240
- console.log('📁 Created `assets/css/` directory');
241
- }
242
-
243
- if (!await fs.pathExists(assetsJsDir)) {
244
- await fs.ensureDir(assetsJsDir);
245
- console.log('📁 Created `assets/js/` directory');
246
- }
247
-
248
- if (!await fs.pathExists(assetsImagesDir)) {
249
- await fs.ensureDir(assetsImagesDir);
250
- console.log('📁 Created `assets/images/` directory');
251
- }
249
+ if (!await fs.pathExists(assetsCssDir)) await fs.ensureDir(assetsCssDir);
250
+ if (!await fs.pathExists(assetsJsDir)) await fs.ensureDir(assetsJsDir);
251
+ if (!await fs.pathExists(assetsImagesDir)) await fs.ensureDir(assetsImagesDir);
252
252
  }
253
253
 
254
254
  // Write config file if it doesn't exist or user confirmed override
@@ -260,7 +260,7 @@ async function initProject() {
260
260
  }
261
261
 
262
262
  // Write index.md file if it doesn't exist or user confirmed override
263
- if (!await fs.pathExists(indexMdFile)) {
263
+ if (!await fs.pathExists(indexMdFile) || shouldOverride) {
264
264
  await fs.writeFile(indexMdFile, defaultIndexMdContent, 'utf8');
265
265
  console.log('📄 Created `docs/index.md`');
266
266
  } else if (shouldOverride) {
@@ -271,6 +271,7 @@ async function initProject() {
271
271
  }
272
272
 
273
273
  console.log('✅ Project initialization complete!');
274
+ console.log('👉 Run `npm install` to setup dependencies.');
274
275
  }
275
276
 
276
277
  module.exports = { initProject };
@@ -0,0 +1,145 @@
1
+ // Source file from the docmd project — https://github.com/docmd-io/docmd
2
+
3
+ const fs = require('../core/fs-utils'); // Native wrapper
4
+ const path = require('path');
5
+ const esbuild = require('esbuild');
6
+ const { processAssets } = require('../core/asset-manager');
7
+
8
+ async function build() {
9
+ console.log('📦 Building @docmd/live core...');
10
+
11
+ // Resolve paths relative to src/commands/live.js
12
+ const SRC_DIR = path.resolve(__dirname, '..'); // Points to /src
13
+ const LIVE_SRC_DIR = path.join(SRC_DIR, 'live');
14
+ const DIST_DIR = path.resolve(__dirname, '../../dist'); // Points to /dist in root
15
+
16
+ // 1. Clean/Create dist
17
+ if (await fs.exists(DIST_DIR)) {
18
+ await fs.remove(DIST_DIR);
19
+ }
20
+ await fs.ensureDir(DIST_DIR);
21
+
22
+ // 2. Generate Shim for Buffer (Browser compatibility)
23
+ const shimPath = path.join(LIVE_SRC_DIR, 'shims.js');
24
+ await fs.writeFile(shimPath, `import { Buffer } from 'buffer'; globalThis.Buffer = Buffer;`);
25
+
26
+ // 3. Define the Virtual Template Plugin
27
+ // This reads EJS files from disk and bundles them as a JSON object string
28
+ const templatePlugin = {
29
+ name: 'docmd-templates',
30
+ setup(build) {
31
+ build.onResolve({ filter: /^virtual:docmd-templates$/ }, args => ({
32
+ path: args.path,
33
+ namespace: 'docmd-templates-ns',
34
+ }));
35
+
36
+ build.onLoad({ filter: /.*/, namespace: 'docmd-templates-ns' }, async () => {
37
+ const templatesDir = path.join(SRC_DIR, 'templates');
38
+ const files = await fs.readdir(templatesDir);
39
+ const templates = {};
40
+
41
+ for (const file of files) {
42
+ if (file.endsWith('.ejs')) {
43
+ const content = await fs.readFile(path.join(templatesDir, file), 'utf8');
44
+ templates[file] = content;
45
+ }
46
+ }
47
+
48
+ return {
49
+ contents: `module.exports = ${JSON.stringify(templates)};`,
50
+ loader: 'js',
51
+ };
52
+ });
53
+ },
54
+ };
55
+
56
+ // 4. Define Node Modules Shim Plugin
57
+ // Stubs out fs/path requires so the browser bundle doesn't crash
58
+ const nodeShimPlugin = {
59
+ name: 'node-deps-shim',
60
+ setup(build) {
61
+ build.onResolve({ filter: /^(node:)?path$/ }, args => ({ path: args.path, namespace: 'path-shim' }));
62
+ build.onLoad({ filter: /.*/, namespace: 'path-shim' }, () => ({
63
+ contents: `
64
+ module.exports = {
65
+ join: (...args) => args.filter(Boolean).join('/'),
66
+ resolve: (...args) => '/' + args.filter(Boolean).join('/'),
67
+ basename: (p) => p ? p.split(/[\\\\/]/).pop() : '',
68
+ dirname: (p) => p ? p.split(/[\\\\/]/).slice(0, -1).join('/') || '.' : '.',
69
+ extname: (p) => { if (!p) return ''; const parts = p.split('.'); return parts.length > 1 ? '.' + parts.pop() : ''; },
70
+ isAbsolute: (p) => p.startsWith('/'),
71
+ normalize: (p) => p,
72
+ sep: '/'
73
+ };
74
+ `,
75
+ loader: 'js'
76
+ }));
77
+
78
+ build.onResolve({ filter: /^(node:)?fs(\/promises)?|fs-extra$/ }, args => ({ path: args.path, namespace: 'fs-shim' }));
79
+ build.onLoad({ filter: /.*/, namespace: 'fs-shim' }, () => ({
80
+ contents: `
81
+ module.exports = {
82
+ existsSync: () => false,
83
+ readFileSync: () => '',
84
+ statSync: () => ({ isFile: () => true, isDirectory: () => false }),
85
+ constants: { F_OK: 0, R_OK: 4 },
86
+ promises: {}
87
+ };
88
+ `,
89
+ loader: 'js'
90
+ }));
91
+ }
92
+ };
93
+
94
+ // 5. Bundle JS
95
+ console.log('⚡ Bundling & Compressing JS...');
96
+ try {
97
+ await esbuild.build({
98
+ entryPoints: [path.join(LIVE_SRC_DIR, 'core.js')],
99
+ bundle: true,
100
+ outfile: path.join(DIST_DIR, 'docmd-live.js'),
101
+ platform: 'browser',
102
+ format: 'iife',
103
+ globalName: 'docmd',
104
+ minify: true,
105
+ define: { 'process.env.NODE_ENV': '"production"' },
106
+ inject: [shimPath],
107
+ plugins: [templatePlugin, nodeShimPlugin]
108
+ });
109
+
110
+ // 6. Copy & Minify Static Assets using Universal Manager
111
+ console.log('📂 Processing static assets...');
112
+ const assetsSrc = path.join(SRC_DIR, 'assets');
113
+ const assetsDest = path.join(DIST_DIR, 'assets');
114
+
115
+ if (await fs.exists(assetsSrc)) {
116
+ await processAssets(assetsSrc, assetsDest, { minify: true });
117
+ }
118
+
119
+ // 7. Copy HTML Wrapper & Minify Live CSS
120
+ await fs.copy(path.join(LIVE_SRC_DIR, 'index.html'), path.join(DIST_DIR, 'index.html'));
121
+
122
+ const liveCss = await fs.readFile(path.join(LIVE_SRC_DIR, 'live.css'), 'utf8');
123
+ const minifiedLiveCss = await esbuild.transform(liveCss, { loader: 'css', minify: true });
124
+ await fs.writeFile(path.join(DIST_DIR, 'live.css'), minifiedLiveCss.code);
125
+
126
+ // 8. Copy Favicon to Root
127
+ const internalFavicon = path.join(assetsSrc, 'favicon.ico');
128
+ if (await fs.exists(internalFavicon)) {
129
+ await fs.copy(internalFavicon, path.join(DIST_DIR, 'favicon.ico'));
130
+ }
131
+
132
+ console.log('✅ Live Editor build complete!');
133
+
134
+ } catch (e) {
135
+ console.error('❌ Build failed:', e);
136
+ process.exit(1);
137
+ }
138
+ }
139
+
140
+ // Allow direct execution (via scripts/failsafe.js)
141
+ if (require.main === module) {
142
+ build();
143
+ }
144
+
145
+ module.exports = { build };
@@ -0,0 +1,72 @@
1
+ // Source file from the docmd project — https://github.com/docmd-io/docmd
2
+
3
+ const path = require('path');
4
+ const fs = require('./fs-utils');
5
+ const esbuild = require('esbuild');
6
+
7
+ const DOCMD_HEADER = `/*! Source file from the docmd project — https://github.com/docmd-io/docmd */\n\n`;
8
+
9
+ /**
10
+ * Recursively copies assets from src to dest.
11
+ * Minifies CSS/JS files and adds docmd header.
12
+ *
13
+ * @param {string} srcDir - Source directory
14
+ * @param {string} destDir - Destination directory
15
+ * @param {object} options - { minify: boolean }
16
+ */
17
+ async function processAssets(srcDir, destDir, options = { minify: true }) {
18
+ // Ensure source exists
19
+ if (!await fs.exists(srcDir)) return;
20
+
21
+ // Ensure dest exists
22
+ await fs.ensureDir(destDir);
23
+
24
+ const entries = await fs.readdir(srcDir, { withFileTypes: true });
25
+
26
+ for (const entry of entries) {
27
+ const srcPath = path.join(srcDir, entry.name);
28
+ const destPath = path.join(destDir, entry.name);
29
+
30
+ if (entry.isDirectory()) {
31
+ await processAssets(srcPath, destPath, options);
32
+ } else {
33
+ const ext = path.extname(entry.name).toLowerCase();
34
+
35
+ // Handle CSS & JS: Minify + docmd Header
36
+ if (ext === '.css' || ext === '.js') {
37
+ try {
38
+ const content = await fs.readFile(srcPath, 'utf8');
39
+
40
+ if (options.minify) {
41
+ // Minify using esbuild
42
+ const result = await esbuild.transform(content, {
43
+ loader: ext.slice(1), // 'css' or 'js'
44
+ minify: true,
45
+ banner: ext === '.css' || ext === '.js' ? DOCMD_HEADER : ''
46
+ });
47
+ await fs.writeFile(destPath, result.code);
48
+ } else {
49
+ // Just add banner if not minifying (Dev mode)
50
+ const contentWithBanner = `${DOCMD_HEADER}\n${content}`;
51
+ await fs.writeFile(destPath, contentWithBanner);
52
+ }
53
+ } catch (e) {
54
+ console.warn(`⚠️ Processing failed for ${entry.name}, copying original.`);
55
+ await fs.copy(srcPath, destPath);
56
+ }
57
+ }
58
+ // Handle HTML Files (if any in assets): HTML Comment Header
59
+ else if (ext === '.html') {
60
+ const content = await fs.readFile(srcPath, 'utf8');
61
+ const htmlBanner = `<!-- Generated by docmd - https://docmd.io -->\n\n`;
62
+ await fs.writeFile(destPath, htmlBanner + content);
63
+ }
64
+ // Everything else (Images, Fonts): Direct Copy
65
+ else {
66
+ await fs.copy(srcPath, destPath);
67
+ }
68
+ }
69
+ }
70
+ }
71
+
72
+ module.exports = { processAssets, DOCMD_HEADER };
@@ -1,7 +1,7 @@
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
3
  const path = require('path');
4
- const fs = require('fs-extra');
4
+ const fs = require('./fs-utils');
5
5
  const { validateConfig } = require('./config-validator');
6
6
 
7
7
  async function loadConfig(configPath) {
@@ -1,4 +1,4 @@
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
3
  const chalk = require('chalk');
4
4
 
@@ -1,14 +1,18 @@
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');
3
+ const fs = require('./fs-utils');
4
4
  const path = require('path');
5
5
  const matter = require('gray-matter');
6
6
  const { createMarkdownItInstance } = require('./markdown/setup');
7
- const striptags = require('striptags');
8
7
 
9
8
  function decodeHtmlEntities(html) {
10
9
  return html.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, "'").replace(/ /g, ' ');
11
10
  }
11
+
12
+ function stripHtmlTags(str) {
13
+ if (!str) return '';
14
+ return str.replace(/<[^>]*>?/gm, '');
15
+ }
12
16
 
13
17
  function extractHeadingsFromHtml(htmlContent) {
14
18
  const headings = [];
@@ -65,12 +69,12 @@ function processMarkdownContent(rawContent, md, config, filePath = 'memory') {
65
69
 
66
70
  let searchData = null;
67
71
  if (!frontmatter.noindex) {
68
- const rawText = decodeHtmlEntities(striptags(htmlContent));
69
- searchData = {
70
- title: frontmatter.title || 'Untitled',
71
- content: rawText.slice(0, 5000), // Safety cap to prevent massive JSON
72
- headings: headings.map(h => h.text)
73
- };
72
+ const rawText = decodeHtmlEntities(stripHtmlTags(htmlContent));
73
+ searchData = {
74
+ title: frontmatter.title || 'Untitled',
75
+ content: rawText.slice(0, 5000), // Safety cap to prevent massive JSON
76
+ headings: headings.map(h => h.text)
77
+ };
74
78
  }
75
79
 
76
80
  return { frontmatter, htmlContent, headings, searchData };
@@ -0,0 +1,40 @@
1
+ // Source file from the docmd project — https://github.com/docmd-io/docmd
2
+
3
+ const fs = require('node:fs/promises');
4
+ const path = require('node:path');
5
+
6
+ async function ensureDir(dirPath) {
7
+ await fs.mkdir(dirPath, { recursive: true });
8
+ }
9
+
10
+ async function remove(dirPath) {
11
+ await fs.rm(dirPath, { recursive: true, force: true });
12
+ }
13
+
14
+ async function copy(src, dest) {
15
+ await fs.cp(src, dest, { recursive: true });
16
+ }
17
+
18
+ async function exists(filePath) {
19
+ try {
20
+ await fs.access(filePath);
21
+ return true;
22
+ } catch {
23
+ return false;
24
+ }
25
+ }
26
+
27
+ async function writeJson(file, object, options = {}) {
28
+ const content = JSON.stringify(object, null, options.spaces || 2);
29
+ await fs.writeFile(file, content, 'utf8');
30
+ }
31
+
32
+ module.exports = {
33
+ ...fs,
34
+ ensureDir,
35
+ remove,
36
+ copy,
37
+ pathExists: exists,
38
+ exists,
39
+ writeJson
40
+ };