markopress 0.0.1
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/bin/cli.js +16 -0
- package/dist/build/index.d.ts +83 -0
- package/dist/build/index.d.ts.map +1 -0
- package/dist/build/index.js +1553 -0
- package/dist/build/index.js.map +1 -0
- package/dist/build/manifest-generator.d.ts +34 -0
- package/dist/build/manifest-generator.d.ts.map +1 -0
- package/dist/build/manifest-generator.js +86 -0
- package/dist/build/manifest-generator.js.map +1 -0
- package/dist/build/security.test.d.ts +6 -0
- package/dist/build/security.test.d.ts.map +1 -0
- package/dist/build/security.test.js +88 -0
- package/dist/build/security.test.js.map +1 -0
- package/dist/build/types.d.ts +21 -0
- package/dist/build/types.d.ts.map +1 -0
- package/dist/build/types.js +5 -0
- package/dist/build/types.js.map +1 -0
- package/dist/build/vite-config.test.d.ts +2 -0
- package/dist/build/vite-config.test.d.ts.map +1 -0
- package/dist/build/vite-config.test.js +53 -0
- package/dist/build/vite-config.test.js.map +1 -0
- package/dist/build/vite-markdown-plugin.d.ts +13 -0
- package/dist/build/vite-markdown-plugin.d.ts.map +1 -0
- package/dist/build/vite-markdown-plugin.js +134 -0
- package/dist/build/vite-markdown-plugin.js.map +1 -0
- package/dist/build/vite-markdown-plugin.test.d.ts +2 -0
- package/dist/build/vite-markdown-plugin.test.d.ts.map +1 -0
- package/dist/build/vite-markdown-plugin.test.js +41 -0
- package/dist/build/vite-markdown-plugin.test.js.map +1 -0
- package/dist/cli/index.d.ts +6 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +75 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/config/app-root.d.ts +11 -0
- package/dist/config/app-root.d.ts.map +1 -0
- package/dist/config/app-root.js +24 -0
- package/dist/config/app-root.js.map +1 -0
- package/dist/config/app-root.test.d.ts +2 -0
- package/dist/config/app-root.test.d.ts.map +1 -0
- package/dist/config/app-root.test.js +71 -0
- package/dist/config/app-root.test.js.map +1 -0
- package/dist/config/index.d.ts +6 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +6 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/loader.d.ts +25 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +187 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/config/loader.test.d.ts +2 -0
- package/dist/config/loader.test.d.ts.map +1 -0
- package/dist/config/loader.test.js +24 -0
- package/dist/config/loader.test.js.map +1 -0
- package/dist/config/types.d.ts +149 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +5 -0
- package/dist/config/types.js.map +1 -0
- package/dist/config/validation.d.ts +188 -0
- package/dist/config/validation.d.ts.map +1 -0
- package/dist/config/validation.js +139 -0
- package/dist/config/validation.js.map +1 -0
- package/dist/content/index.d.ts +6 -0
- package/dist/content/index.d.ts.map +1 -0
- package/dist/content/index.js +6 -0
- package/dist/content/index.js.map +1 -0
- package/dist/content/registry.d.ts +29 -0
- package/dist/content/registry.d.ts.map +1 -0
- package/dist/content/registry.js +45 -0
- package/dist/content/registry.js.map +1 -0
- package/dist/content/scanner.d.ts +9 -0
- package/dist/content/scanner.d.ts.map +1 -0
- package/dist/content/scanner.js +115 -0
- package/dist/content/scanner.js.map +1 -0
- package/dist/content/types.d.ts +41 -0
- package/dist/content/types.d.ts.map +1 -0
- package/dist/content/types.js +5 -0
- package/dist/content/types.js.map +1 -0
- package/dist/dev/index.d.ts +18 -0
- package/dist/dev/index.d.ts.map +1 -0
- package/dist/dev/index.js +93 -0
- package/dist/dev/index.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/markdown/code.d.ts +79 -0
- package/dist/markdown/code.d.ts.map +1 -0
- package/dist/markdown/code.js +305 -0
- package/dist/markdown/code.js.map +1 -0
- package/dist/markdown/containers.d.ts +17 -0
- package/dist/markdown/containers.d.ts.map +1 -0
- package/dist/markdown/containers.js +143 -0
- package/dist/markdown/containers.js.map +1 -0
- package/dist/markdown/includes.d.ts +18 -0
- package/dist/markdown/includes.d.ts.map +1 -0
- package/dist/markdown/includes.js +9 -0
- package/dist/markdown/includes.js.map +1 -0
- package/dist/markdown/index.d.ts +8 -0
- package/dist/markdown/index.d.ts.map +1 -0
- package/dist/markdown/index.js +8 -0
- package/dist/markdown/index.js.map +1 -0
- package/dist/markdown/loader.d.ts +31 -0
- package/dist/markdown/loader.d.ts.map +1 -0
- package/dist/markdown/loader.js +325 -0
- package/dist/markdown/loader.js.map +1 -0
- package/dist/markdown/preserve-tags.d.ts +16 -0
- package/dist/markdown/preserve-tags.d.ts.map +1 -0
- package/dist/markdown/preserve-tags.js +233 -0
- package/dist/markdown/preserve-tags.js.map +1 -0
- package/dist/markdown/renderer.d.ts +28 -0
- package/dist/markdown/renderer.d.ts.map +1 -0
- package/dist/markdown/renderer.js +146 -0
- package/dist/markdown/renderer.js.map +1 -0
- package/dist/markdown/tag-validator.d.ts +64 -0
- package/dist/markdown/tag-validator.d.ts.map +1 -0
- package/dist/markdown/tag-validator.js +118 -0
- package/dist/markdown/tag-validator.js.map +1 -0
- package/dist/markdown/types.d.ts +44 -0
- package/dist/markdown/types.d.ts.map +1 -0
- package/dist/markdown/types.js +5 -0
- package/dist/markdown/types.js.map +1 -0
- package/dist/plugin/compat.d.ts +14 -0
- package/dist/plugin/compat.d.ts.map +1 -0
- package/dist/plugin/compat.js +78 -0
- package/dist/plugin/compat.js.map +1 -0
- package/dist/plugin/context.d.ts +38 -0
- package/dist/plugin/context.d.ts.map +1 -0
- package/dist/plugin/context.js +103 -0
- package/dist/plugin/context.js.map +1 -0
- package/dist/plugin/index.d.ts +6 -0
- package/dist/plugin/index.d.ts.map +1 -0
- package/dist/plugin/index.js +6 -0
- package/dist/plugin/index.js.map +1 -0
- package/dist/plugin/manager.d.ts +112 -0
- package/dist/plugin/manager.d.ts.map +1 -0
- package/dist/plugin/manager.js +385 -0
- package/dist/plugin/manager.js.map +1 -0
- package/dist/plugin/types.d.ts +182 -0
- package/dist/plugin/types.d.ts.map +1 -0
- package/dist/plugin/types.js +5 -0
- package/dist/plugin/types.js.map +1 -0
- package/dist/plugins/blog-index/index.d.ts +18 -0
- package/dist/plugins/blog-index/index.d.ts.map +1 -0
- package/dist/plugins/blog-index/index.js +158 -0
- package/dist/plugins/blog-index/index.js.map +1 -0
- package/dist/plugins/sidenav/index.d.ts +36 -0
- package/dist/plugins/sidenav/index.d.ts.map +1 -0
- package/dist/plugins/sidenav/index.js +86 -0
- package/dist/plugins/sidenav/index.js.map +1 -0
- package/dist/plugins/toc/index.d.ts +38 -0
- package/dist/plugins/toc/index.d.ts.map +1 -0
- package/dist/plugins/toc/index.js +79 -0
- package/dist/plugins/toc/index.js.map +1 -0
- package/dist/preview/index.d.ts +7 -0
- package/dist/preview/index.d.ts.map +1 -0
- package/dist/preview/index.js +25 -0
- package/dist/preview/index.js.map +1 -0
- package/dist/theme/default/build/generate-all.d.ts +9 -0
- package/dist/theme/default/build/generate-all.d.ts.map +1 -0
- package/dist/theme/default/build/generate-all.js +85 -0
- package/dist/theme/default/build/generate-all.js.map +1 -0
- package/dist/theme/default/build/generate-css.d.ts +19 -0
- package/dist/theme/default/build/generate-css.d.ts.map +1 -0
- package/dist/theme/default/build/generate-css.js +199 -0
- package/dist/theme/default/build/generate-css.js.map +1 -0
- package/dist/theme/default/build/index.d.ts +5 -0
- package/dist/theme/default/build/index.d.ts.map +1 -0
- package/dist/theme/default/build/index.js +5 -0
- package/dist/theme/default/build/index.js.map +1 -0
- package/dist/theme/default/design-systems/default.d.ts +12 -0
- package/dist/theme/default/design-systems/default.d.ts.map +1 -0
- package/dist/theme/default/design-systems/default.js +289 -0
- package/dist/theme/default/design-systems/default.js.map +1 -0
- package/dist/theme/default/design-systems/docusaurus.d.ts +12 -0
- package/dist/theme/default/design-systems/docusaurus.d.ts.map +1 -0
- package/dist/theme/default/design-systems/docusaurus.js +299 -0
- package/dist/theme/default/design-systems/docusaurus.js.map +1 -0
- package/dist/theme/default/design-systems/index.d.ts +50 -0
- package/dist/theme/default/design-systems/index.d.ts.map +1 -0
- package/dist/theme/default/design-systems/index.js +54 -0
- package/dist/theme/default/design-systems/index.js.map +1 -0
- package/dist/theme/default/design-systems/rspress.d.ts +12 -0
- package/dist/theme/default/design-systems/rspress.d.ts.map +1 -0
- package/dist/theme/default/design-systems/rspress.js +299 -0
- package/dist/theme/default/design-systems/rspress.js.map +1 -0
- package/dist/theme/default/design-systems/types.d.ts +238 -0
- package/dist/theme/default/design-systems/types.d.ts.map +1 -0
- package/dist/theme/default/design-systems/types.js +6 -0
- package/dist/theme/default/design-systems/types.js.map +1 -0
- package/dist/theme/default/design-systems/vitepress.d.ts +12 -0
- package/dist/theme/default/design-systems/vitepress.d.ts.map +1 -0
- package/dist/theme/default/design-systems/vitepress.js +299 -0
- package/dist/theme/default/design-systems/vitepress.js.map +1 -0
- package/dist/theme/default/index.d.ts +60 -0
- package/dist/theme/default/index.d.ts.map +1 -0
- package/dist/theme/default/index.js +44 -0
- package/dist/theme/default/index.js.map +1 -0
- package/dist/theme/default/theme.d.ts +14 -0
- package/dist/theme/default/theme.d.ts.map +1 -0
- package/dist/theme/default/theme.js +58 -0
- package/dist/theme/default/theme.js.map +1 -0
- package/dist/theme/index.d.ts +6 -0
- package/dist/theme/index.d.ts.map +1 -0
- package/dist/theme/index.js +6 -0
- package/dist/theme/index.js.map +1 -0
- package/dist/theme/loader.d.ts +21 -0
- package/dist/theme/loader.d.ts.map +1 -0
- package/dist/theme/loader.js +125 -0
- package/dist/theme/loader.js.map +1 -0
- package/dist/theme/types.d.ts +73 -0
- package/dist/theme/types.d.ts.map +1 -0
- package/dist/theme/types.js +5 -0
- package/dist/theme/types.js.map +1 -0
- package/dist/vite/index.d.ts +6 -0
- package/dist/vite/index.d.ts.map +1 -0
- package/dist/vite/index.js +6 -0
- package/dist/vite/index.js.map +1 -0
- package/dist/vite/markdownPlugin.d.ts +15 -0
- package/dist/vite/markdownPlugin.d.ts.map +1 -0
- package/dist/vite/markdownPlugin.js +111 -0
- package/dist/vite/markdownPlugin.js.map +1 -0
- package/dist/vite/plugin.d.ts +18 -0
- package/dist/vite/plugin.d.ts.map +1 -0
- package/dist/vite/plugin.js +94 -0
- package/dist/vite/plugin.js.map +1 -0
- package/marko.json +3 -0
- package/package.json +109 -0
- package/src/theme/default/build/generate-all.ts +99 -0
- package/src/theme/default/build/generate-css.ts +234 -0
- package/src/theme/default/build/index.ts +5 -0
- package/src/theme/default/components/doc-footer.marko +180 -0
- package/src/theme/default/components/footer.marko +32 -0
- package/src/theme/default/components/header.marko +49 -0
- package/src/theme/default/components/nav-bar.marko +191 -0
- package/src/theme/default/components/page-header.marko +20 -0
- package/src/theme/default/components/reading-progress.marko +36 -0
- package/src/theme/default/components/search.marko +239 -0
- package/src/theme/default/components/sidebar.marko +211 -0
- package/src/theme/default/components/site-footer.marko +211 -0
- package/src/theme/default/components/skip-link.marko +49 -0
- package/src/theme/default/components/theme/theme-aside-bottom.marko +1 -0
- package/src/theme/default/components/theme/theme-aside-top.marko +1 -0
- package/src/theme/default/components/theme/theme-body-bottom.marko +1 -0
- package/src/theme/default/components/theme/theme-body-top.marko +1 -0
- package/src/theme/default/components/theme/theme-doc-bottom.marko +1 -0
- package/src/theme/default/components/theme/theme-doc-footer-after.marko +1 -0
- package/src/theme/default/components/theme/theme-doc-footer-before.marko +1 -0
- package/src/theme/default/components/theme/theme-doc-top.marko +1 -0
- package/src/theme/default/components/theme/theme-head-bottom.marko +1 -0
- package/src/theme/default/components/theme/theme-head-top.marko +1 -0
- package/src/theme/default/components/theme/theme-home-features-after.marko +1 -0
- package/src/theme/default/components/theme/theme-home-hero-after.marko +1 -0
- package/src/theme/default/components/theme/theme-home-hero-before.marko +1 -0
- package/src/theme/default/components/theme/theme-navbar-center.marko +5 -0
- package/src/theme/default/components/theme/theme-navbar-end.marko +30 -0
- package/src/theme/default/components/theme/theme-navbar-start.marko +1 -0
- package/src/theme/default/components/theme/theme-page-bottom.marko +1 -0
- package/src/theme/default/components/theme/theme-page-top.marko +1 -0
- package/src/theme/default/components/theme/theme-sidebar-bottom.marko +1 -0
- package/src/theme/default/components/theme/theme-sidebar-top.marko +1 -0
- package/src/theme/default/components/theme/theme-toc-item.marko +1 -0
- package/src/theme/default/components/theme-toggle.marko +122 -0
- package/src/theme/default/components/toc.marko +140 -0
- package/src/theme/default/design-systems/default.ts +331 -0
- package/src/theme/default/design-systems/docusaurus.ts +341 -0
- package/src/theme/default/design-systems/index.ts +67 -0
- package/src/theme/default/design-systems/rspress.ts +341 -0
- package/src/theme/default/design-systems/types.ts +296 -0
- package/src/theme/default/design-systems/vitepress.ts +341 -0
- package/src/theme/default/index.ts +107 -0
- package/src/theme/default/layouts/blog.marko +65 -0
- package/src/theme/default/layouts/content-page.marko +41 -0
- package/src/theme/default/layouts/default.marko +209 -0
- package/src/theme/default/layouts/docs.marko +81 -0
- package/src/theme/default/layouts/home-page.marko +19 -0
- package/src/theme/default/layouts/page.marko +51 -0
- package/src/theme/default/public/theme-default.css +1081 -0
- package/src/theme/default/public/theme-docusaurus.css +1081 -0
- package/src/theme/default/public/theme-vitepress.css +1081 -0
- package/src/theme/default/public/theme.css +2 -0
- package/src/theme/default/routes/+layout.marko +57 -0
- package/src/theme/default/styles/main.css +249 -0
- package/src/theme/default/styles-base.css +757 -0
- package/src/theme/default/styles.css +899 -0
- package/src/theme/default/taglib.json +18 -0
- package/src/theme/default/tags/doc-footer.marko +180 -0
- package/src/theme/default/tags/footer.marko +32 -0
- package/src/theme/default/tags/header.marko +49 -0
- package/src/theme/default/tags/nav-bar.marko +191 -0
- package/src/theme/default/tags/page-header.marko +20 -0
- package/src/theme/default/tags/reading-progress.marko +36 -0
- package/src/theme/default/tags/search.marko +239 -0
- package/src/theme/default/tags/sidebar.marko +211 -0
- package/src/theme/default/tags/site-footer.marko +211 -0
- package/src/theme/default/tags/skip-link.marko +49 -0
- package/src/theme/default/tags/theme-aside-bottom.marko +1 -0
- package/src/theme/default/tags/theme-aside-top.marko +1 -0
- package/src/theme/default/tags/theme-body-bottom.marko +1 -0
- package/src/theme/default/tags/theme-body-top.marko +1 -0
- package/src/theme/default/tags/theme-doc-bottom.marko +1 -0
- package/src/theme/default/tags/theme-doc-footer-after.marko +1 -0
- package/src/theme/default/tags/theme-doc-footer-before.marko +1 -0
- package/src/theme/default/tags/theme-doc-top.marko +1 -0
- package/src/theme/default/tags/theme-head-bottom.marko +1 -0
- package/src/theme/default/tags/theme-head-top.marko +1 -0
- package/src/theme/default/tags/theme-home-features-after.marko +1 -0
- package/src/theme/default/tags/theme-home-hero-after.marko +1 -0
- package/src/theme/default/tags/theme-home-hero-before.marko +1 -0
- package/src/theme/default/tags/theme-navbar-center.marko +5 -0
- package/src/theme/default/tags/theme-navbar-end.marko +30 -0
- package/src/theme/default/tags/theme-navbar-start.marko +1 -0
- package/src/theme/default/tags/theme-page-bottom.marko +1 -0
- package/src/theme/default/tags/theme-page-top.marko +1 -0
- package/src/theme/default/tags/theme-sidebar-bottom.marko +1 -0
- package/src/theme/default/tags/theme-sidebar-top.marko +1 -0
- package/src/theme/default/tags/theme-toc-item.marko +1 -0
- package/src/theme/default/tags/theme-toggle.marko +122 -0
- package/src/theme/default/tags/toc.marko +140 -0
- package/src/theme/default/theme.ts +83 -0
- package/templates/blog-post.marko.template +13 -0
- package/templates/catch-all-handler.js.template +90 -0
- package/templates/catch-all-page.marko.template +69 -0
- package/templates/doc.marko.template +6 -0
- package/templates/example-tags/README.md +212 -0
- package/templates/example-tags/alert-box.marko +98 -0
- package/templates/example-tags/button-primary.marko +28 -0
- package/templates/example-tags/button-secondary.marko +28 -0
- package/templates/example-tags/button.marko +6 -0
- package/templates/example-tags/card-body.marko +8 -0
- package/templates/example-tags/card-footer.marko +7 -0
- package/templates/example-tags/card-header.marko +7 -0
- package/templates/example-tags/card.marko +20 -0
- package/templates/example-tags/icon.marko +149 -0
- package/templates/layout.marko.template +64 -0
- package/templates/page.marko.template +6 -0
|
@@ -0,0 +1,1553 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MarkoPress Build System
|
|
3
|
+
* Generates static HTML from markdown content
|
|
4
|
+
*/
|
|
5
|
+
import { promises as fs } from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { spawn } from 'node:child_process';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
import matter from 'gray-matter';
|
|
10
|
+
import { loadConfig } from '../config/loader.js';
|
|
11
|
+
import { getDesignSystem, getDarkModeOverride } from '../theme/default/design-systems/index.js';
|
|
12
|
+
import { globalTagValidator, formatValidationError } from '../markdown/tag-validator.js';
|
|
13
|
+
import { PluginManager } from '../plugin/manager.js';
|
|
14
|
+
import { loadMarkdownModule, registerMarkdownContent } from './vite-markdown-plugin.js';
|
|
15
|
+
const BUILD_MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
const MARKOPRESS_PACKAGE_ROOT = path.resolve(BUILD_MODULE_DIR, '..', '..');
|
|
17
|
+
const INTERNAL_DEFAULT_THEME_ROOT = path.join(MARKOPRESS_PACKAGE_ROOT, 'src', 'theme', 'default');
|
|
18
|
+
const INTERNAL_DEFAULT_THEME_NAMES = new Set([
|
|
19
|
+
'@markopress/theme-default',
|
|
20
|
+
'theme-default',
|
|
21
|
+
'default',
|
|
22
|
+
]);
|
|
23
|
+
function isInternalDefaultTheme(themeName) {
|
|
24
|
+
return INTERNAL_DEFAULT_THEME_NAMES.has(themeName);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Build the MarkoPress site for production
|
|
28
|
+
*/
|
|
29
|
+
export async function build(options = {}) {
|
|
30
|
+
const { outDir, debug = false, useCatchAllRoutes, root: optionRoot } = options;
|
|
31
|
+
const root = optionRoot || process.cwd();
|
|
32
|
+
const errors = [];
|
|
33
|
+
const timings = new Map();
|
|
34
|
+
const startTimes = new Map();
|
|
35
|
+
/**
|
|
36
|
+
* Timing helper - tracks elapsed time for build steps
|
|
37
|
+
* Uses performance.now() for high-resolution timing
|
|
38
|
+
*/
|
|
39
|
+
const time = (label) => ({
|
|
40
|
+
start: () => {
|
|
41
|
+
startTimes.set(label, performance.now());
|
|
42
|
+
},
|
|
43
|
+
end: () => {
|
|
44
|
+
const start = startTimes.get(label) || 0;
|
|
45
|
+
const elapsed = performance.now() - start;
|
|
46
|
+
timings.set(label, elapsed);
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
try {
|
|
50
|
+
console.log('🚀 Building MarkoPress site...\n');
|
|
51
|
+
// Step 0: Load configuration
|
|
52
|
+
const t0 = time('Config loading');
|
|
53
|
+
t0.start();
|
|
54
|
+
const config = await loadConfig(root, { mode: 'production', command: 'build' });
|
|
55
|
+
t0.end();
|
|
56
|
+
// Step 1: Initialize plugin manager
|
|
57
|
+
let pluginManager;
|
|
58
|
+
if (config.plugins && config.plugins.length > 0) {
|
|
59
|
+
console.log('🔌 Loading plugins...');
|
|
60
|
+
const t1 = time('Plugin loading');
|
|
61
|
+
t1.start();
|
|
62
|
+
pluginManager = new PluginManager(config);
|
|
63
|
+
await pluginManager.loadPlugins(config.plugins);
|
|
64
|
+
t1.end();
|
|
65
|
+
console.log('');
|
|
66
|
+
}
|
|
67
|
+
// Step 2: Execute loadContent hooks (for backward compatibility)
|
|
68
|
+
if (pluginManager) {
|
|
69
|
+
console.log('📦 Loading plugin content...');
|
|
70
|
+
const t2 = time('Plugin loadContent hooks');
|
|
71
|
+
t2.start();
|
|
72
|
+
await pluginManager.execLoadContentHooks();
|
|
73
|
+
t2.end();
|
|
74
|
+
console.log(' Plugin content loaded\n');
|
|
75
|
+
}
|
|
76
|
+
// Step 3: Create ContentModule objects from content configuration
|
|
77
|
+
// This allows plugins to enhance modules with metadata (sidebar, toc, etc.)
|
|
78
|
+
const manifest = {};
|
|
79
|
+
const modules = [];
|
|
80
|
+
for (const [moduleId, contentConfig] of Object.entries(config.content)) {
|
|
81
|
+
const moduleConfig = typeof contentConfig === 'string'
|
|
82
|
+
? { dir: contentConfig }
|
|
83
|
+
: contentConfig;
|
|
84
|
+
if (!moduleConfig?.dir)
|
|
85
|
+
continue;
|
|
86
|
+
// Create a minimal ContentModule object with enhancement support
|
|
87
|
+
const enhancements = new Map();
|
|
88
|
+
const module = {
|
|
89
|
+
id: moduleId,
|
|
90
|
+
dir: moduleConfig.dir,
|
|
91
|
+
config: moduleConfig,
|
|
92
|
+
// Plugin enhancement API
|
|
93
|
+
enhance(key, data) {
|
|
94
|
+
enhancements.set(key, data);
|
|
95
|
+
},
|
|
96
|
+
getEnhancement(key) {
|
|
97
|
+
return enhancements.get(key);
|
|
98
|
+
},
|
|
99
|
+
// Will be populated with file data during build
|
|
100
|
+
files: [],
|
|
101
|
+
// Store enhancements for serialization
|
|
102
|
+
_enhancements: enhancements,
|
|
103
|
+
};
|
|
104
|
+
// Scan content directory for markdown files
|
|
105
|
+
const contentDir = path.resolve(root, moduleConfig.dir);
|
|
106
|
+
try {
|
|
107
|
+
const entries = await fs.readdir(contentDir, { withFileTypes: true });
|
|
108
|
+
for (const entry of entries) {
|
|
109
|
+
if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
110
|
+
const filePath = path.join(contentDir, entry.name);
|
|
111
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
112
|
+
// Parse frontmatter with gray-matter so quoted strings are normalized.
|
|
113
|
+
let frontmatter = {};
|
|
114
|
+
try {
|
|
115
|
+
frontmatter = matter(content).data;
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
// Ignore parse errors and continue with empty metadata.
|
|
119
|
+
}
|
|
120
|
+
module.files.push({
|
|
121
|
+
id: entry.name.replace('.md', ''),
|
|
122
|
+
slug: entry.name.replace('.md', ''),
|
|
123
|
+
filePath,
|
|
124
|
+
urlPath: entry.name.replace('.md', '') === 'index' ? `/${moduleId}` : `/${moduleId}/${entry.name.replace('.md', '')}`,
|
|
125
|
+
processed: {
|
|
126
|
+
frontmatter,
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
// Directory doesn't exist, skip
|
|
134
|
+
}
|
|
135
|
+
modules.push(module);
|
|
136
|
+
}
|
|
137
|
+
// Step 4: Execute enhanceModules hooks
|
|
138
|
+
// This lets plugins like sidenav enhance modules with sidebar data
|
|
139
|
+
if (pluginManager && modules.length > 0) {
|
|
140
|
+
console.log('🔌 Enhancing modules with plugin metadata...');
|
|
141
|
+
const t2a = time('Module enhancement');
|
|
142
|
+
t2a.start();
|
|
143
|
+
// Debug: Log modules and their files
|
|
144
|
+
for (const module of modules) {
|
|
145
|
+
console.log(` Module: ${module.id} (${module.files.length} files)`);
|
|
146
|
+
if (module.files.length > 0) {
|
|
147
|
+
console.log(` First file has processed: ${!!module.files[0].processed}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
await pluginManager.execEnhanceModulesHooks(modules);
|
|
151
|
+
t2a.end();
|
|
152
|
+
console.log(` Enhanced ${modules.length} module(s)\n`);
|
|
153
|
+
// Write module enhancements to a JSON file for request-time access
|
|
154
|
+
const generatedDir = path.join(root, 'src', '.generated');
|
|
155
|
+
await fs.mkdir(generatedDir, { recursive: true });
|
|
156
|
+
const moduleEnhancements = {};
|
|
157
|
+
for (const module of modules) {
|
|
158
|
+
const enhancements = {};
|
|
159
|
+
const entries = module._enhancements.entries();
|
|
160
|
+
for (const [key, value] of entries) {
|
|
161
|
+
enhancements[key] = value;
|
|
162
|
+
}
|
|
163
|
+
if (Object.keys(enhancements).length > 0) {
|
|
164
|
+
moduleEnhancements[module.id] = enhancements;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
const enhancementsPath = path.join(generatedDir, 'module-enhancements.json');
|
|
168
|
+
await fs.writeFile(enhancementsPath, JSON.stringify(moduleEnhancements, null, 2), 'utf-8');
|
|
169
|
+
console.log(` Wrote module enhancements to src/.generated/module-enhancements.json\n`);
|
|
170
|
+
}
|
|
171
|
+
// Step 5: Initialize tag validator if Marko tags enabled
|
|
172
|
+
// Step 4: Initialize tag validator if Marko tags enabled
|
|
173
|
+
if (config.markdown.markoTags?.enabled) {
|
|
174
|
+
const tagsDir = path.join(root, config.markdown.markoTags.tagsDir || 'src/.markopress/tags');
|
|
175
|
+
console.log('🔍 Scanning tags directory...');
|
|
176
|
+
const t7 = time('Tag validation setup');
|
|
177
|
+
t7.start();
|
|
178
|
+
await globalTagValidator.loadAvailableTags(tagsDir);
|
|
179
|
+
t7.end();
|
|
180
|
+
console.log(` Found ${globalTagValidator.getAvailableTagsCount()} tags\n`);
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
globalTagValidator.reset();
|
|
184
|
+
}
|
|
185
|
+
// Step 5: Ensure routes directory exists
|
|
186
|
+
// Note: Routes must be in src/routes/ for @marko/run compatibility
|
|
187
|
+
const routesDir = path.join(root, 'src', 'routes');
|
|
188
|
+
await fs.mkdir(routesDir, { recursive: true });
|
|
189
|
+
// Step 6: Initialize empty route manifest for plugins to extend
|
|
190
|
+
let routeManifest = {};
|
|
191
|
+
// Step 7: Execute extendRoutes hooks
|
|
192
|
+
if (pluginManager) {
|
|
193
|
+
const t5 = time('Extend routes hooks');
|
|
194
|
+
t5.start();
|
|
195
|
+
routeManifest = await pluginManager.execExtendRoutesHooks(routeManifest);
|
|
196
|
+
t5.end();
|
|
197
|
+
console.log('🔌 Extended routes manifest:', Object.keys(routeManifest).length);
|
|
198
|
+
}
|
|
199
|
+
// Step 8: Generate routes for content
|
|
200
|
+
console.log('📝 Generating routes from content...');
|
|
201
|
+
const t6 = time('Route generation');
|
|
202
|
+
t6.start();
|
|
203
|
+
const routeMode = useCatchAllRoutes ?? config.build.useCatchAllRoutes;
|
|
204
|
+
if (routeMode) {
|
|
205
|
+
await generateCatchAllRoutes(manifest, routesDir, config, modules, debug);
|
|
206
|
+
console.log(' Using catch-all dynamic routes');
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
await generateRoutes(manifest, routesDir, config, modules, debug);
|
|
210
|
+
console.log(' Using static routes');
|
|
211
|
+
}
|
|
212
|
+
t6.end();
|
|
213
|
+
console.log(' Routes generated\n');
|
|
214
|
+
// Step 9: Convert routeManifest entries with handler/component to plugin routes
|
|
215
|
+
// This allows plugins to add custom routes via extendRoutes hook
|
|
216
|
+
const manifestRoutes = [];
|
|
217
|
+
for (const [path, route] of Object.entries(routeManifest)) {
|
|
218
|
+
if (route.handler || route.component) {
|
|
219
|
+
manifestRoutes.push({ path, ...route });
|
|
220
|
+
console.log(` Found plugin route: ${path}`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
console.log(`🔌 Total manifest routes: ${Object.keys(routeManifest).length}, Plugin routes: ${manifestRoutes.length}`);
|
|
224
|
+
// Step 10: Generate plugin routes
|
|
225
|
+
if (pluginManager) {
|
|
226
|
+
const pluginRoutes = pluginManager.getPluginRoutes();
|
|
227
|
+
const allPluginRoutes = [...pluginRoutes, ...manifestRoutes];
|
|
228
|
+
if (allPluginRoutes.length > 0) {
|
|
229
|
+
console.log(`🔌 Generating ${allPluginRoutes.length} plugin routes...`);
|
|
230
|
+
const t7 = time('Plugin route generation');
|
|
231
|
+
t7.start();
|
|
232
|
+
await generatePluginRoutes(allPluginRoutes, routesDir, config, debug);
|
|
233
|
+
t7.end();
|
|
234
|
+
console.log(' Plugin routes generated\n');
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
// Step 11: Generate Vite config for markdown content plugin
|
|
238
|
+
console.log('⚙️ Generating Vite config...');
|
|
239
|
+
const t10 = time('Vite config generation');
|
|
240
|
+
t10.start();
|
|
241
|
+
await generateViteConfig(root, debug);
|
|
242
|
+
t10.end();
|
|
243
|
+
console.log(' Vite config generated\n');
|
|
244
|
+
// Step 12: Execute allContentLoaded hooks
|
|
245
|
+
if (pluginManager) {
|
|
246
|
+
console.log('🔌 Processing plugin allContentLoaded hooks...');
|
|
247
|
+
const t8 = time('AllContentLoaded hooks');
|
|
248
|
+
t8.start();
|
|
249
|
+
await pluginManager.execAllContentLoadedHooks(routeManifest);
|
|
250
|
+
t8.end();
|
|
251
|
+
console.log(' All content processed\n');
|
|
252
|
+
}
|
|
253
|
+
// Step 12: Validate Marko tags if enabled
|
|
254
|
+
if (config.markdown.markoTags?.enabled) {
|
|
255
|
+
console.log('🔍 Validating Marko tags...');
|
|
256
|
+
const t9 = time('Tag validation');
|
|
257
|
+
t9.start();
|
|
258
|
+
const validation = globalTagValidator.validate();
|
|
259
|
+
t9.end();
|
|
260
|
+
if (!validation.success) {
|
|
261
|
+
const errorMessage = formatValidationError(validation.missingTags);
|
|
262
|
+
console.error(`\n${errorMessage}\n`);
|
|
263
|
+
errors.push(errorMessage);
|
|
264
|
+
return {
|
|
265
|
+
success: false,
|
|
266
|
+
outDir: '',
|
|
267
|
+
pages: 0,
|
|
268
|
+
errors,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
console.log(' All tags validated ✓\n');
|
|
272
|
+
}
|
|
273
|
+
// Step 13: Copy theme CSS to public directory
|
|
274
|
+
console.log('🎨 Copying theme CSS...');
|
|
275
|
+
const t11 = time('Theme CSS copy');
|
|
276
|
+
t11.start();
|
|
277
|
+
await copyThemeCSS(root, config, debug);
|
|
278
|
+
t11.end();
|
|
279
|
+
console.log(' Theme CSS copied\n');
|
|
280
|
+
// Step 14: Extract styles from user-defined Marko components
|
|
281
|
+
console.log('🎨 Extracting styles from Marko components...');
|
|
282
|
+
const t12 = time('Marko component styles extraction');
|
|
283
|
+
t12.start();
|
|
284
|
+
await extractStylesFromMarkoTags(root, config, debug);
|
|
285
|
+
t12.end();
|
|
286
|
+
console.log(' Component styles extracted\n');
|
|
287
|
+
// Step 15: Theme components are auto-discovered from the markopress package
|
|
288
|
+
// via marko metadata (marko.json + package exports)
|
|
289
|
+
// console.log('📦 Copying theme components...');
|
|
290
|
+
// const t13 = time('Theme components copy');
|
|
291
|
+
// t13.start();
|
|
292
|
+
// await copyThemeComponents(root, config, debug);
|
|
293
|
+
// t13.end();
|
|
294
|
+
// console.log(' Theme components copied\n');
|
|
295
|
+
// Step 16: Build with @marko/run
|
|
296
|
+
console.log('🔨 Building with @marko/run...');
|
|
297
|
+
const t14 = time('@marko/run build');
|
|
298
|
+
t14.start();
|
|
299
|
+
const resolvedOutDir = outDir || config.build.outDir;
|
|
300
|
+
const buildResult = await runMarkoRunBuild(resolvedOutDir, debug, root);
|
|
301
|
+
t14.end();
|
|
302
|
+
if (!buildResult.success) {
|
|
303
|
+
errors.push(...buildResult.errors);
|
|
304
|
+
return {
|
|
305
|
+
success: false,
|
|
306
|
+
outDir: '',
|
|
307
|
+
pages: 0,
|
|
308
|
+
errors,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
// Step 17: Collect build assets
|
|
312
|
+
const t15 = time('Collect build assets');
|
|
313
|
+
t15.start();
|
|
314
|
+
const assets = await collectBuildAssets(buildResult.outDir);
|
|
315
|
+
t15.end();
|
|
316
|
+
// Step 18: Execute postBuild hooks
|
|
317
|
+
if (pluginManager) {
|
|
318
|
+
console.log('🔌 Processing plugin postBuild hooks...');
|
|
319
|
+
const t16 = time('Post-build hooks');
|
|
320
|
+
t16.start();
|
|
321
|
+
await pluginManager.execPostBuildHooks(buildResult.outDir, routeManifest, assets);
|
|
322
|
+
t16.end();
|
|
323
|
+
console.log(' Post-build hooks completed\n');
|
|
324
|
+
}
|
|
325
|
+
// Step 19: Copy Marko tags directory to output (after build so it doesn't get cleaned)
|
|
326
|
+
console.log('📦 Copying Marko tags directory...');
|
|
327
|
+
const t18 = time('Copy tags directory');
|
|
328
|
+
t18.start();
|
|
329
|
+
await copyTagsDirectory(root, buildResult.outDir, config, debug);
|
|
330
|
+
t18.end();
|
|
331
|
+
console.log(' Tags directory copied\n');
|
|
332
|
+
console.log('\n✅ Build completed successfully!');
|
|
333
|
+
console.log(` Output: ${buildResult.outDir}`);
|
|
334
|
+
console.log(` Pages: Generated dynamically at request time`);
|
|
335
|
+
// Print timing summary
|
|
336
|
+
console.log('\n⏱️ Build timing:');
|
|
337
|
+
const sortedTimings = Array.from(timings.entries()).sort((a, b) => b[1] - a[1]);
|
|
338
|
+
for (const [label, time] of sortedTimings) {
|
|
339
|
+
const seconds = (time / 1000).toFixed(2);
|
|
340
|
+
const bar = '█'.repeat(Math.min(Math.floor(time / 100), 20));
|
|
341
|
+
console.log(` ${bar} ${label}: ${seconds}s`);
|
|
342
|
+
}
|
|
343
|
+
return {
|
|
344
|
+
success: true,
|
|
345
|
+
outDir: buildResult.outDir,
|
|
346
|
+
pages: 0, // Pages generated dynamically at request time
|
|
347
|
+
errors,
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
catch (error) {
|
|
351
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
352
|
+
errors.push(errorMessage);
|
|
353
|
+
console.error('\n❌ Build failed:', errorMessage);
|
|
354
|
+
return {
|
|
355
|
+
success: false,
|
|
356
|
+
outDir: '',
|
|
357
|
+
pages: 0,
|
|
358
|
+
errors,
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Generate plugin-defined routes
|
|
364
|
+
*/
|
|
365
|
+
async function generatePluginRoutes(routes, routesDir, config, debug) {
|
|
366
|
+
for (const route of routes) {
|
|
367
|
+
const routePath = route.path.slice(1); // Remove leading slash
|
|
368
|
+
const dir = path.join(routesDir, routePath, '+page');
|
|
369
|
+
await fs.mkdir(path.dirname(dir), { recursive: true });
|
|
370
|
+
// Generate handler if specified
|
|
371
|
+
if (route.handler) {
|
|
372
|
+
const handlerFile = path.join(path.dirname(dir), '+handler.js');
|
|
373
|
+
await fs.writeFile(handlerFile, route.handler);
|
|
374
|
+
}
|
|
375
|
+
// Generate page if component specified
|
|
376
|
+
if (route.component) {
|
|
377
|
+
const pageFile = dir + '.marko';
|
|
378
|
+
await fs.writeFile(pageFile, route.component);
|
|
379
|
+
}
|
|
380
|
+
if (debug) {
|
|
381
|
+
console.log(` Generated plugin route: ${route.path}`);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Collect build assets from output directory
|
|
387
|
+
*/
|
|
388
|
+
async function collectBuildAssets(outDir) {
|
|
389
|
+
const assets = [];
|
|
390
|
+
try {
|
|
391
|
+
const files = await fs.readdir(outDir, { recursive: true });
|
|
392
|
+
for (const file of files) {
|
|
393
|
+
if (typeof file === 'string' && (file.endsWith('.js') || file.endsWith('.css') || file.endsWith('.json'))) {
|
|
394
|
+
assets.push(file);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
catch (error) {
|
|
399
|
+
// If directory doesn't exist or can't be read, return empty array
|
|
400
|
+
console.warn('Warning: Could not collect build assets:', error);
|
|
401
|
+
}
|
|
402
|
+
return assets;
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Generate route files from content manifest
|
|
406
|
+
* Works with dynamic manifest structure
|
|
407
|
+
*/
|
|
408
|
+
export async function generateRoutes(manifest, routesDir, config, modules, debug) {
|
|
409
|
+
// Clean up old generated routes safely
|
|
410
|
+
// Only delete files in directories we manage, preserving user's custom files
|
|
411
|
+
await cleanupGeneratedRoutes(routesDir, manifest, debug);
|
|
412
|
+
// Collect module IDs from manifest for cleanup
|
|
413
|
+
const moduleIds = [];
|
|
414
|
+
let pageCount = 0;
|
|
415
|
+
let docCount = 0;
|
|
416
|
+
let blogCount = 0;
|
|
417
|
+
// Generate routes for each module dynamically
|
|
418
|
+
for (const [moduleId, files] of Object.entries(manifest)) {
|
|
419
|
+
// Skip non-array entries
|
|
420
|
+
if (!Array.isArray(files))
|
|
421
|
+
continue;
|
|
422
|
+
moduleIds.push(moduleId);
|
|
423
|
+
const contentFiles = files;
|
|
424
|
+
if (moduleId === 'pages') {
|
|
425
|
+
// Generate static page routes (root-level, no prefix)
|
|
426
|
+
for (const page of contentFiles) {
|
|
427
|
+
await generatePageRoute(page, routesDir, config, modules, debug);
|
|
428
|
+
pageCount++;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
else if (moduleId === 'blog') {
|
|
432
|
+
// Generate individual blog routes
|
|
433
|
+
for (const post of contentFiles) {
|
|
434
|
+
await generateBlogRoute(post, routesDir, config, modules, debug);
|
|
435
|
+
blogCount++;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
else {
|
|
439
|
+
// Generate individual doc routes for all other modules
|
|
440
|
+
for (const doc of contentFiles) {
|
|
441
|
+
await generateDocRoute(doc, routesDir, config, modules, debug);
|
|
442
|
+
docCount++;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
// Generate config file for handlers
|
|
447
|
+
await generateConfigFile(routesDir, config, debug);
|
|
448
|
+
// Generate vite.config.js for markdown content virtual module support
|
|
449
|
+
await generateViteConfig(config.root, debug);
|
|
450
|
+
// Generate root layout that wraps all pages with <${input.content}/>
|
|
451
|
+
await generateRootLayout(routesDir, config, debug);
|
|
452
|
+
if (debug) {
|
|
453
|
+
console.log(` Generated ${pageCount} page routes`);
|
|
454
|
+
console.log(` Generated ${docCount} doc routes`);
|
|
455
|
+
console.log(` Generated ${blogCount} blog routes`);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Safely clean up generated routes without deleting user files
|
|
460
|
+
*
|
|
461
|
+
* Security: Only deletes files in specific directories we manage.
|
|
462
|
+
* Preserves user's custom route handlers, middleware, and components.
|
|
463
|
+
*
|
|
464
|
+
* @param routesDir - Root routes directory
|
|
465
|
+
* @param manifest - Content manifest to determine what to keep
|
|
466
|
+
* @param debug - Enable debug logging
|
|
467
|
+
*/
|
|
468
|
+
export async function cleanupGeneratedRoutes(routesDir, manifest, debug) {
|
|
469
|
+
const errors = [];
|
|
470
|
+
// Files that should NEVER be deleted (user's custom files)
|
|
471
|
+
const PRESERVE_PATTERNS = [
|
|
472
|
+
'+layout.marko', // Root layout
|
|
473
|
+
'+middleware.js', // Custom middleware
|
|
474
|
+
'components/**/*', // User's components
|
|
475
|
+
'api/**/*', // User's API routes
|
|
476
|
+
'lib/**/*', // User's utilities
|
|
477
|
+
];
|
|
478
|
+
// Build dynamic managed prefixes from manifest
|
|
479
|
+
// Skip 'pages' (root-level, no directory prefix)
|
|
480
|
+
const MANAGED_PREFIXES = Object.keys(manifest)
|
|
481
|
+
.filter(key => key !== 'pages')
|
|
482
|
+
.map(key => `${key}/`);
|
|
483
|
+
try {
|
|
484
|
+
const allFiles = await fs.readdir(routesDir, {
|
|
485
|
+
recursive: true,
|
|
486
|
+
withFileTypes: true,
|
|
487
|
+
});
|
|
488
|
+
for (const entry of allFiles) {
|
|
489
|
+
if (!entry.isFile())
|
|
490
|
+
continue;
|
|
491
|
+
// Build full path
|
|
492
|
+
const fullPath = path.join(entry.path || entry.parentPath || routesDir, entry.name);
|
|
493
|
+
const relativePath = path.relative(routesDir, fullPath);
|
|
494
|
+
// Check if file is in a managed directory
|
|
495
|
+
const isInManagedDir = MANAGED_PREFIXES.some((prefix) => relativePath.startsWith(prefix));
|
|
496
|
+
if (!isInManagedDir) {
|
|
497
|
+
// Not in a directory we manage, skip
|
|
498
|
+
continue;
|
|
499
|
+
}
|
|
500
|
+
// Check if file matches preserve patterns
|
|
501
|
+
const shouldPreserve = PRESERVE_PATTERNS.some((pattern) => {
|
|
502
|
+
if (pattern.includes('**')) {
|
|
503
|
+
// Glob pattern matching
|
|
504
|
+
const regex = new RegExp(pattern.replace(/\*\*/g, '.*').replace(/\*/g, '[^/]*'));
|
|
505
|
+
return regex.test(relativePath);
|
|
506
|
+
}
|
|
507
|
+
return entry.name === pattern;
|
|
508
|
+
});
|
|
509
|
+
if (shouldPreserve) {
|
|
510
|
+
if (debug)
|
|
511
|
+
console.log(` Preserving: ${relativePath}`);
|
|
512
|
+
continue;
|
|
513
|
+
}
|
|
514
|
+
// Safe to delete this generated file
|
|
515
|
+
try {
|
|
516
|
+
await fs.unlink(fullPath);
|
|
517
|
+
if (debug)
|
|
518
|
+
console.log(` Deleted: ${relativePath}`);
|
|
519
|
+
}
|
|
520
|
+
catch (error) {
|
|
521
|
+
if (error.code !== 'ENOENT') {
|
|
522
|
+
// Only report non-"file not found" errors
|
|
523
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
524
|
+
errors.push(`Failed to delete ${relativePath}: ${errorMessage}`);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
// Clean up empty directories in managed paths
|
|
529
|
+
for (const prefix of MANAGED_PREFIXES) {
|
|
530
|
+
const dirPath = path.join(routesDir, prefix);
|
|
531
|
+
try {
|
|
532
|
+
await cleanupEmptyDirectories(dirPath);
|
|
533
|
+
}
|
|
534
|
+
catch {
|
|
535
|
+
// Directory might not exist, that's okay
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
if (errors.length > 0) {
|
|
539
|
+
console.warn('⚠️ Cleanup warnings:');
|
|
540
|
+
errors.forEach((err) => console.warn(` ${err}`));
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
catch (error) {
|
|
544
|
+
// Routes directory might not exist yet, that's okay for initial build
|
|
545
|
+
if (error.code !== 'ENOENT') {
|
|
546
|
+
throw error;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* Recursively remove empty directories
|
|
552
|
+
* @param dirPath - Directory to clean up
|
|
553
|
+
*/
|
|
554
|
+
async function cleanupEmptyDirectories(dirPath) {
|
|
555
|
+
try {
|
|
556
|
+
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
557
|
+
// Recursively clean subdirectories first
|
|
558
|
+
for (const entry of entries) {
|
|
559
|
+
if (entry.isDirectory()) {
|
|
560
|
+
const subDirPath = path.join(dirPath, entry.name);
|
|
561
|
+
await cleanupEmptyDirectories(subDirPath);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
// Check if directory is now empty
|
|
565
|
+
const updatedEntries = await fs.readdir(dirPath);
|
|
566
|
+
if (updatedEntries.length === 0) {
|
|
567
|
+
await fs.rmdir(dirPath);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
catch {
|
|
571
|
+
// Ignore errors (directory might not exist or not be empty)
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Generate a static page route
|
|
576
|
+
* NOTE: This function is deprecated in favor of catch-all routes with request-time rendering.
|
|
577
|
+
* Kept for backward compatibility with useCatchAllRoutes: false
|
|
578
|
+
*/
|
|
579
|
+
async function generatePageRoute(page, routesDir, config, modules, debug) {
|
|
580
|
+
// Static routes are deprecated - use catch-all routes instead
|
|
581
|
+
if (debug) {
|
|
582
|
+
console.log(` Warning: Static routes deprecated, use catch-all routes`);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Generate a single documentation route
|
|
587
|
+
* NOTE: This function is deprecated in favor of catch-all routes with request-time rendering.
|
|
588
|
+
* Kept for backward compatibility with useCatchAllRoutes: false
|
|
589
|
+
*/
|
|
590
|
+
async function generateDocRoute(doc, routesDir, config, modules, debug) {
|
|
591
|
+
// Static routes are deprecated - use catch-all routes instead
|
|
592
|
+
if (debug) {
|
|
593
|
+
console.log(` Warning: Static routes deprecated, use catch-all routes`);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* Generate a single blog route
|
|
598
|
+
* NOTE: This function is deprecated in favor of catch-all routes with request-time rendering.
|
|
599
|
+
* Kept for backward compatibility with useCatchAllRoutes: false
|
|
600
|
+
*/
|
|
601
|
+
async function generateBlogRoute(post, routesDir, config, modules, debug) {
|
|
602
|
+
// Static routes are deprecated - use catch-all routes instead
|
|
603
|
+
if (debug) {
|
|
604
|
+
console.log(` Warning: Static routes deprecated, use catch-all routes`);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* Generate config file for handlers to import
|
|
609
|
+
* This makes theme options (navbar, footer, etc.) available to route handlers
|
|
610
|
+
*/
|
|
611
|
+
async function generateConfigFile(routesDir, config, debug) {
|
|
612
|
+
const configFile = path.join(routesDir, '_config.js');
|
|
613
|
+
// Export the full config (except root which we add separately)
|
|
614
|
+
// Include all fields needed by handlers and templates
|
|
615
|
+
const handlerConfig = {
|
|
616
|
+
root: config.root,
|
|
617
|
+
site: {
|
|
618
|
+
title: config.site?.title || 'MarkoPress',
|
|
619
|
+
description: config.site?.description || '',
|
|
620
|
+
lang: config.site?.lang || 'en-US',
|
|
621
|
+
head: config.site?.head || [],
|
|
622
|
+
},
|
|
623
|
+
content: config.content,
|
|
624
|
+
theme: {
|
|
625
|
+
name: config.theme?.name || '@markopress/theme-default',
|
|
626
|
+
options: config.theme?.options || {},
|
|
627
|
+
},
|
|
628
|
+
markdown: config.markdown || {},
|
|
629
|
+
build: config.build || {},
|
|
630
|
+
};
|
|
631
|
+
// Export as ES module
|
|
632
|
+
const content = `// Auto-generated by MarkoPress - Do not edit
|
|
633
|
+
export const config = ${JSON.stringify(handlerConfig, null, 2)};
|
|
634
|
+
`;
|
|
635
|
+
await fs.writeFile(configFile, content);
|
|
636
|
+
if (debug) {
|
|
637
|
+
console.log(` Generated: ${configFile}`);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
/**
|
|
641
|
+
* Generate root layout
|
|
642
|
+
*/
|
|
643
|
+
async function generateRootLayout(routesDir, config, debug) {
|
|
644
|
+
const layoutFile = path.join(routesDir, '+layout.marko');
|
|
645
|
+
// Get navbar settings from config
|
|
646
|
+
const navbar = config.theme?.options?.navbar || [];
|
|
647
|
+
const siteTitle = config.site?.title || 'MarkoPress';
|
|
648
|
+
// Get theme style (default, vitepress, or docusaurus)
|
|
649
|
+
const themeStyle = config.theme?.options?.style || 'default';
|
|
650
|
+
// Generate layout using template file
|
|
651
|
+
const template = await loadTemplate('layout.marko.template', {
|
|
652
|
+
SITE_TITLE: siteTitle,
|
|
653
|
+
THEME_STYLE: themeStyle,
|
|
654
|
+
});
|
|
655
|
+
await fs.writeFile(layoutFile, template);
|
|
656
|
+
if (debug) {
|
|
657
|
+
console.log(` Generated: ${layoutFile}`);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* Load a template file and replace placeholders
|
|
662
|
+
*/
|
|
663
|
+
async function loadTemplate(templateName, replacements) {
|
|
664
|
+
// Template files are in the templates/ directory at package root
|
|
665
|
+
// When running from dist/, go up one level to find templates/
|
|
666
|
+
const distDir = path.dirname(new URL(import.meta.url).pathname);
|
|
667
|
+
const packageRoot = path.join(distDir, '..', '..');
|
|
668
|
+
const templatePath = path.join(packageRoot, 'templates', templateName);
|
|
669
|
+
let template = await fs.readFile(templatePath, 'utf-8');
|
|
670
|
+
// Replace all placeholders
|
|
671
|
+
for (const [key, value] of Object.entries(replacements)) {
|
|
672
|
+
template = template.replace(new RegExp(`\\{\\{${key}\\}\\}`, 'g'), value);
|
|
673
|
+
}
|
|
674
|
+
return template;
|
|
675
|
+
}
|
|
676
|
+
/**
|
|
677
|
+
* Validate theme name to prevent path traversal attacks
|
|
678
|
+
*
|
|
679
|
+
* @param name - Theme package name to validate
|
|
680
|
+
* @throws {Error} If theme name is invalid or contains path traversal
|
|
681
|
+
*
|
|
682
|
+
* Security: Only allows valid npm package names:
|
|
683
|
+
* - Scoped: @scope/package-name (forward slash allowed after @)
|
|
684
|
+
* - Unscoped: package-name
|
|
685
|
+
* - Characters: lowercase letters, numbers, hyphens, dots, underscores
|
|
686
|
+
*/
|
|
687
|
+
export function validateThemeName(name) {
|
|
688
|
+
// Valid npm package name pattern (scoped or unscoped)
|
|
689
|
+
// Allows: @scope/package, package-name, @org/my-package
|
|
690
|
+
const validPackageRegex = /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;
|
|
691
|
+
if (!validPackageRegex.test(name)) {
|
|
692
|
+
throw new Error(`Invalid theme name: "${name}". Must be a valid npm package name ` +
|
|
693
|
+
`(e.g., "my-theme" or "@org/my-theme")`);
|
|
694
|
+
}
|
|
695
|
+
// Explicitly block path traversal attempts
|
|
696
|
+
if (name.includes('..')) {
|
|
697
|
+
throw new Error(`Theme name cannot contain traversal sequences (..): "${name}"`);
|
|
698
|
+
}
|
|
699
|
+
// Block backslashes (Windows path separator)
|
|
700
|
+
if (name.includes('\\')) {
|
|
701
|
+
throw new Error(`Theme name cannot contain backslashes: "${name}"`);
|
|
702
|
+
}
|
|
703
|
+
// Block multiple forward slashes (only one allowed in scoped packages)
|
|
704
|
+
const slashCount = (name.match(/\//g) || []).length;
|
|
705
|
+
if (slashCount > 1) {
|
|
706
|
+
throw new Error(`Theme name can only contain one forward slash (for scoped packages): "${name}"`);
|
|
707
|
+
}
|
|
708
|
+
// Block absolute paths (starts with / on Unix or C:\ on Windows)
|
|
709
|
+
if (path.isAbsolute(name)) {
|
|
710
|
+
throw new Error(`Theme name cannot be an absolute path: "${name}"`);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* Generate Vite config that extends @marko/run with markdown content plugin
|
|
715
|
+
*/
|
|
716
|
+
export async function generateViteConfig(rootDir, debug) {
|
|
717
|
+
const viteConfigPath = path.join(rootDir, 'vite.config.js');
|
|
718
|
+
const viteConfigContent = `import { defineConfig } from 'vite';
|
|
719
|
+
import marko from '@marko/run/vite';
|
|
720
|
+
import { markdownContentPlugin } from 'markopress/build';
|
|
721
|
+
|
|
722
|
+
export default defineConfig({
|
|
723
|
+
plugins: [
|
|
724
|
+
marko(),
|
|
725
|
+
markdownContentPlugin(),
|
|
726
|
+
],
|
|
727
|
+
resolve: {
|
|
728
|
+
// Preserve symlinks for pnpm workspace compatibility
|
|
729
|
+
// This allows Marko to properly discover tags from symlinked packages
|
|
730
|
+
preserveSymlinks: true,
|
|
731
|
+
},
|
|
732
|
+
build: {
|
|
733
|
+
outDir: 'dist',
|
|
734
|
+
},
|
|
735
|
+
});
|
|
736
|
+
`;
|
|
737
|
+
try {
|
|
738
|
+
// Check if file already exists and has our plugin
|
|
739
|
+
const existing = await fs.readFile(viteConfigPath, 'utf-8');
|
|
740
|
+
if (existing.includes('markdownContentPlugin')) {
|
|
741
|
+
if (debug) {
|
|
742
|
+
console.log(' Vite config already has markdownContentPlugin');
|
|
743
|
+
}
|
|
744
|
+
return;
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
catch {
|
|
748
|
+
// File doesn't exist, create it
|
|
749
|
+
}
|
|
750
|
+
await fs.writeFile(viteConfigPath, viteConfigContent);
|
|
751
|
+
if (debug) {
|
|
752
|
+
console.log(` Created vite.config.js with markdownContentPlugin`);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
/**
|
|
756
|
+
* Copy theme CSS to public directory
|
|
757
|
+
*/
|
|
758
|
+
export async function copyThemeCSS(rootDir, config, debug) {
|
|
759
|
+
// Create the _markopress/theme directory in public
|
|
760
|
+
const themeDir = path.join(rootDir, 'public', '_markopress', 'theme');
|
|
761
|
+
await fs.mkdir(themeDir, { recursive: true });
|
|
762
|
+
const themeName = config.theme?.name || '@markopress/theme-default';
|
|
763
|
+
// Security: Validate theme name to prevent path traversal
|
|
764
|
+
try {
|
|
765
|
+
validateThemeName(themeName);
|
|
766
|
+
}
|
|
767
|
+
catch (error) {
|
|
768
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
769
|
+
throw new Error(`Security: ${errorMessage}`);
|
|
770
|
+
}
|
|
771
|
+
// Get the style from theme options
|
|
772
|
+
const style = config.theme?.options?.style || 'default';
|
|
773
|
+
const cssFileName = `theme-${style}.css`;
|
|
774
|
+
// Try multiple locations for the pre-generated theme CSS
|
|
775
|
+
const possiblePaths = [
|
|
776
|
+
...(isInternalDefaultTheme(themeName)
|
|
777
|
+
? [path.join(INTERNAL_DEFAULT_THEME_ROOT, 'public', cssFileName)]
|
|
778
|
+
: []),
|
|
779
|
+
// pnpm workspace: root node_modules
|
|
780
|
+
path.join(rootDir, '..', 'node_modules', themeName, 'public', cssFileName),
|
|
781
|
+
// Local node_modules
|
|
782
|
+
path.join(rootDir, 'node_modules', themeName, 'public', cssFileName),
|
|
783
|
+
];
|
|
784
|
+
let themeCSS = null;
|
|
785
|
+
let foundPath = null;
|
|
786
|
+
for (const cssPath of possiblePaths) {
|
|
787
|
+
try {
|
|
788
|
+
await fs.access(cssPath);
|
|
789
|
+
themeCSS = await fs.readFile(cssPath, 'utf-8');
|
|
790
|
+
foundPath = cssPath;
|
|
791
|
+
break;
|
|
792
|
+
}
|
|
793
|
+
catch {
|
|
794
|
+
// Try next path
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
if (!themeCSS) {
|
|
798
|
+
// Fallback: create a minimal CSS file
|
|
799
|
+
console.warn(` Warning: Could not find ${cssFileName}, using minimal fallback`);
|
|
800
|
+
const fallbackCSS = `/* Minimal fallback CSS for style: ${style} */\nbody { font-family: system-ui, sans-serif; margin: 0; padding: 0; }`;
|
|
801
|
+
const outputPath = path.join(themeDir, cssFileName);
|
|
802
|
+
await fs.writeFile(outputPath, fallbackCSS);
|
|
803
|
+
return;
|
|
804
|
+
}
|
|
805
|
+
// Write the CSS file
|
|
806
|
+
const outputPath = path.join(themeDir, cssFileName);
|
|
807
|
+
await fs.writeFile(outputPath, themeCSS);
|
|
808
|
+
if (debug) {
|
|
809
|
+
console.log(` Copied ${cssFileName} from: ${foundPath}`);
|
|
810
|
+
console.log(` Output: ${outputPath}`);
|
|
811
|
+
}
|
|
812
|
+
// Also copy the component styles (styles.css) from the theme package
|
|
813
|
+
const stylesCSSFileName = 'styles.css';
|
|
814
|
+
const stylesPossiblePaths = [
|
|
815
|
+
...(isInternalDefaultTheme(themeName)
|
|
816
|
+
? [path.join(INTERNAL_DEFAULT_THEME_ROOT, stylesCSSFileName)]
|
|
817
|
+
: []),
|
|
818
|
+
path.join(rootDir, '..', 'node_modules', themeName, 'src', stylesCSSFileName),
|
|
819
|
+
path.join(rootDir, 'node_modules', themeName, 'src', stylesCSSFileName),
|
|
820
|
+
];
|
|
821
|
+
for (const cssPath of stylesPossiblePaths) {
|
|
822
|
+
try {
|
|
823
|
+
await fs.access(cssPath);
|
|
824
|
+
const stylesCSS = await fs.readFile(cssPath, 'utf-8');
|
|
825
|
+
const stylesOutputPath = path.join(themeDir, stylesCSSFileName);
|
|
826
|
+
await fs.writeFile(stylesOutputPath, stylesCSS);
|
|
827
|
+
if (debug) {
|
|
828
|
+
console.log(` Copied ${stylesCSSFileName} from: ${cssPath}`);
|
|
829
|
+
console.log(` Output: ${stylesOutputPath}`);
|
|
830
|
+
}
|
|
831
|
+
break;
|
|
832
|
+
}
|
|
833
|
+
catch {
|
|
834
|
+
// Try next path
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
/**
|
|
839
|
+
* Extract styles from user-defined Marko components
|
|
840
|
+
* Scans the tags directory, extracts <style> blocks from .marko files,
|
|
841
|
+
* and combines them into a single CSS file for global loading
|
|
842
|
+
*
|
|
843
|
+
* This is necessary because request-time virtual markdown modules
|
|
844
|
+
* do not emit tag-local CSS assets reliably.
|
|
845
|
+
*/
|
|
846
|
+
export async function extractStylesFromMarkoTags(rootDir, config, debug) {
|
|
847
|
+
const tagsDirConfig = config.markdown?.markoTags?.tagsDir || 'src/.markopress/tags';
|
|
848
|
+
const tagsDir = path.join(rootDir, tagsDirConfig);
|
|
849
|
+
// Check if tags directory exists
|
|
850
|
+
try {
|
|
851
|
+
await fs.access(tagsDir);
|
|
852
|
+
}
|
|
853
|
+
catch {
|
|
854
|
+
// Tags directory doesn't exist, skip extraction
|
|
855
|
+
if (debug) {
|
|
856
|
+
console.log(` No tags directory found at: ${tagsDir}`);
|
|
857
|
+
}
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
// Collect all .marko files recursively
|
|
861
|
+
const markoFiles = [];
|
|
862
|
+
async function collectMarkoFiles(dir) {
|
|
863
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
864
|
+
for (const entry of entries) {
|
|
865
|
+
const fullPath = path.join(dir, entry.name);
|
|
866
|
+
if (entry.isDirectory()) {
|
|
867
|
+
await collectMarkoFiles(fullPath);
|
|
868
|
+
}
|
|
869
|
+
else if (entry.isFile() && entry.name.endsWith('.marko')) {
|
|
870
|
+
markoFiles.push(fullPath);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
await collectMarkoFiles(tagsDir);
|
|
875
|
+
if (markoFiles.length === 0) {
|
|
876
|
+
if (debug) {
|
|
877
|
+
console.log(` No .marko files found in: ${tagsDir}`);
|
|
878
|
+
}
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
// Extract styles from each .marko file
|
|
882
|
+
const cssEntries = [];
|
|
883
|
+
cssEntries.push('/* Custom markdown tag styles');
|
|
884
|
+
cssEntries.push(' * Loaded globally because request-time virtual markdown modules');
|
|
885
|
+
cssEntries.push(' * do not emit tag-local CSS assets reliably. */');
|
|
886
|
+
cssEntries.push('');
|
|
887
|
+
for (const filePath of markoFiles) {
|
|
888
|
+
const relativePath = path.relative(tagsDir, filePath);
|
|
889
|
+
const componentName = path.dirname(relativePath) === ''
|
|
890
|
+
? path.basename(relativePath, '.marko')
|
|
891
|
+
: path.join(path.dirname(relativePath), path.basename(relativePath, '.marko'));
|
|
892
|
+
try {
|
|
893
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
894
|
+
// Extract <style> blocks using regex
|
|
895
|
+
// Match both <style>...</style> and <style>...</style> with attributes
|
|
896
|
+
const styleRegex = /<style\b[^>]*>([\s\S]*?)<\/style>/gi;
|
|
897
|
+
const matches = Array.from(content.matchAll(styleRegex));
|
|
898
|
+
if (matches.length > 0) {
|
|
899
|
+
cssEntries.push(`/* ${componentName}.marko */`);
|
|
900
|
+
for (const match of matches) {
|
|
901
|
+
const styleContent = match[1] || '';
|
|
902
|
+
if (styleContent) {
|
|
903
|
+
// Split into lines and trim leading/trailing empty lines
|
|
904
|
+
const lines = styleContent.split('\n');
|
|
905
|
+
// Find first non-empty line
|
|
906
|
+
let startIndex = 0;
|
|
907
|
+
while (startIndex < lines.length && lines[startIndex].trim() === '') {
|
|
908
|
+
startIndex++;
|
|
909
|
+
}
|
|
910
|
+
// Find last non-empty line
|
|
911
|
+
let endIndex = lines.length - 1;
|
|
912
|
+
while (endIndex >= startIndex && lines[endIndex].trim() === '') {
|
|
913
|
+
endIndex--;
|
|
914
|
+
}
|
|
915
|
+
// Process the non-empty content
|
|
916
|
+
for (let i = startIndex; i <= endIndex; i++) {
|
|
917
|
+
const line = lines[i];
|
|
918
|
+
// Preserve empty lines within the content
|
|
919
|
+
if (line.trim() === '') {
|
|
920
|
+
cssEntries.push('');
|
|
921
|
+
continue;
|
|
922
|
+
}
|
|
923
|
+
// Detect current indentation level and normalize to 2-space increments
|
|
924
|
+
const indentMatch = line.match(/^(\s*)/);
|
|
925
|
+
const currentIndent = indentMatch ? indentMatch[1].length : 0;
|
|
926
|
+
// Normalize: preserve relative indentation but normalize to 2-space base
|
|
927
|
+
// If source uses 4-space indent, convert to 2-space; if 2-space, keep as-is
|
|
928
|
+
const normalizedIndent = Math.floor(currentIndent / 2);
|
|
929
|
+
const newIndent = ' '.repeat(normalizedIndent);
|
|
930
|
+
const content = line.trim();
|
|
931
|
+
// Remove :global() wrappers - they're for CSS Modules, not global CSS
|
|
932
|
+
const strippedGlobal = content.replace(/:global\(([^)]+)\)/g, '$1');
|
|
933
|
+
cssEntries.push(newIndent + strippedGlobal);
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
cssEntries.push('');
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
catch (error) {
|
|
941
|
+
console.warn(` Warning: Could not read file ${filePath}:`, error);
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
// Create public directory if it doesn't exist
|
|
945
|
+
const publicDir = path.join(rootDir, 'public');
|
|
946
|
+
await fs.mkdir(publicDir, { recursive: true });
|
|
947
|
+
// Write the combined CSS file
|
|
948
|
+
const outputPath = path.join(publicDir, 'markopress-components.css');
|
|
949
|
+
const cssContent = cssEntries.join('\n');
|
|
950
|
+
await fs.writeFile(outputPath, cssContent);
|
|
951
|
+
if (debug) {
|
|
952
|
+
console.log(` Extracted styles from ${markoFiles.length} Marko component(s)`);
|
|
953
|
+
console.log(` Output: ${outputPath}`);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
/**
|
|
957
|
+
* Copy theme components to routes/tags directory for Marko auto-discovery
|
|
958
|
+
* This makes theme components (like <theme-navbar-end>, <toc>, etc.) available
|
|
959
|
+
* without requiring explicit imports in templates
|
|
960
|
+
*/
|
|
961
|
+
async function getAllMarkoFiles(dir) {
|
|
962
|
+
const dirents = await fs.readdir(dir, { withFileTypes: true });
|
|
963
|
+
const files = await Promise.all(dirents.map((dirent) => {
|
|
964
|
+
const res = path.resolve(dir, dirent.name);
|
|
965
|
+
return dirent.isDirectory() ? getAllMarkoFiles(res) : res;
|
|
966
|
+
}));
|
|
967
|
+
return Array.prototype.concat(...files).filter(file => file.endsWith('.marko'));
|
|
968
|
+
}
|
|
969
|
+
export async function copyThemeComponents(rootDir, config, debug) {
|
|
970
|
+
const themeName = config.theme?.name || '@markopress/theme-default';
|
|
971
|
+
// Target directory: src/tags/ where Marko can find from both routes and generated files
|
|
972
|
+
const srcDir = path.join(rootDir, 'src');
|
|
973
|
+
const tagsDir = path.join(srcDir, 'tags');
|
|
974
|
+
// Ensure tags directory exists
|
|
975
|
+
await fs.mkdir(tagsDir, { recursive: true });
|
|
976
|
+
// Possible locations for theme components
|
|
977
|
+
const possiblePaths = [
|
|
978
|
+
...(isInternalDefaultTheme(themeName)
|
|
979
|
+
? [path.join(INTERNAL_DEFAULT_THEME_ROOT, 'tags')]
|
|
980
|
+
: []),
|
|
981
|
+
// pnpm workspace: root node_modules (dist tags)
|
|
982
|
+
path.join(rootDir, '..', 'node_modules', themeName, 'dist', 'tags'),
|
|
983
|
+
// Local node_modules (dist tags)
|
|
984
|
+
path.join(rootDir, 'node_modules', themeName, 'dist', 'tags'),
|
|
985
|
+
// pnpm workspace: root node_modules (source components)
|
|
986
|
+
path.join(rootDir, '..', 'node_modules', themeName, 'src', 'components'),
|
|
987
|
+
// Local node_modules (source components)
|
|
988
|
+
path.join(rootDir, 'node_modules', themeName, 'src', 'components'),
|
|
989
|
+
];
|
|
990
|
+
let sourceTagsDir = null;
|
|
991
|
+
for (const dirPath of possiblePaths) {
|
|
992
|
+
try {
|
|
993
|
+
// Check if directory exists
|
|
994
|
+
await fs.access(dirPath);
|
|
995
|
+
sourceTagsDir = dirPath;
|
|
996
|
+
break;
|
|
997
|
+
}
|
|
998
|
+
catch {
|
|
999
|
+
// Directory doesn't exist, continue
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
if (!sourceTagsDir) {
|
|
1003
|
+
if (debug) {
|
|
1004
|
+
console.warn(` Warning: Could not find theme components, skipping`);
|
|
1005
|
+
}
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
1008
|
+
// Get all .marko files recursively
|
|
1009
|
+
const allMarkoFiles = await getAllMarkoFiles(sourceTagsDir);
|
|
1010
|
+
let copiedCount = 0;
|
|
1011
|
+
for (const file of allMarkoFiles) {
|
|
1012
|
+
const relativePath = path.relative(sourceTagsDir, file);
|
|
1013
|
+
const destPath = path.join(tagsDir, relativePath);
|
|
1014
|
+
let exists = false;
|
|
1015
|
+
try {
|
|
1016
|
+
await fs.access(destPath);
|
|
1017
|
+
exists = true;
|
|
1018
|
+
}
|
|
1019
|
+
catch {
|
|
1020
|
+
// file does not exist
|
|
1021
|
+
}
|
|
1022
|
+
if (exists) {
|
|
1023
|
+
if (debug) {
|
|
1024
|
+
console.log(` Skipped component (user override exists): ${relativePath}`);
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
else {
|
|
1028
|
+
// File doesn't exist, so copy it
|
|
1029
|
+
await fs.mkdir(path.dirname(destPath), { recursive: true });
|
|
1030
|
+
await fs.copyFile(file, destPath);
|
|
1031
|
+
copiedCount++;
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
if (debug) {
|
|
1035
|
+
console.log(` Copied ${copiedCount} theme components from: ${sourceTagsDir}`);
|
|
1036
|
+
console.log(` Output: ${tagsDir}`);
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
/**
|
|
1040
|
+
* Copy Marko tags directory to output directory for component discovery
|
|
1041
|
+
* This allows Marko runtime to find and render custom components
|
|
1042
|
+
*/
|
|
1043
|
+
export async function copyTagsDirectory(rootDir, outDir, config, debug) {
|
|
1044
|
+
const tagsDirConfig = config.markdown?.markoTags?.tagsDir || 'src/.markopress/tags';
|
|
1045
|
+
const tagsDir = path.join(rootDir, tagsDirConfig);
|
|
1046
|
+
const distTagsDir = path.join(outDir, 'tags');
|
|
1047
|
+
// Check if tags directory exists
|
|
1048
|
+
try {
|
|
1049
|
+
await fs.access(tagsDir);
|
|
1050
|
+
}
|
|
1051
|
+
catch {
|
|
1052
|
+
// Tags directory doesn't exist, skip copying
|
|
1053
|
+
if (debug) {
|
|
1054
|
+
console.log(` No tags directory found at: ${tagsDir}`);
|
|
1055
|
+
}
|
|
1056
|
+
return;
|
|
1057
|
+
}
|
|
1058
|
+
// Create destination directory
|
|
1059
|
+
await fs.mkdir(distTagsDir, { recursive: true });
|
|
1060
|
+
// Copy all tag files recursively
|
|
1061
|
+
const files = await fs.readdir(tagsDir, { withFileTypes: true });
|
|
1062
|
+
let copiedCount = 0;
|
|
1063
|
+
for (const file of files) {
|
|
1064
|
+
const srcPath = path.join(tagsDir, file.name);
|
|
1065
|
+
const destPath = path.join(distTagsDir, file.name);
|
|
1066
|
+
if (file.isDirectory()) {
|
|
1067
|
+
// Recursively copy subdirectories
|
|
1068
|
+
await fs.mkdir(destPath, { recursive: true });
|
|
1069
|
+
const subFiles = await fs.readdir(srcPath, { withFileTypes: true });
|
|
1070
|
+
for (const subFile of subFiles) {
|
|
1071
|
+
const subSrcPath = path.join(srcPath, subFile.name);
|
|
1072
|
+
const subDestPath = path.join(destPath, subFile.name);
|
|
1073
|
+
if (!subFile.isDirectory()) {
|
|
1074
|
+
await fs.copyFile(subSrcPath, subDestPath);
|
|
1075
|
+
copiedCount++;
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
else if (file.isFile()) {
|
|
1080
|
+
// Copy tag files (.marko, .js, .ts, etc.)
|
|
1081
|
+
await fs.copyFile(srcPath, destPath);
|
|
1082
|
+
copiedCount++;
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
if (debug) {
|
|
1086
|
+
console.log(` Copied ${copiedCount} tag files from: ${tagsDir}`);
|
|
1087
|
+
console.log(` Output: ${distTagsDir}`);
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
/**
|
|
1091
|
+
* Convert design system tokens to CSS variables
|
|
1092
|
+
*/
|
|
1093
|
+
function designSystemToCSS(ds, darkMode = null) {
|
|
1094
|
+
const lines = [];
|
|
1095
|
+
// Start :root block
|
|
1096
|
+
lines.push(':root {');
|
|
1097
|
+
// Colors
|
|
1098
|
+
lines.push(' /* Brand/Primary Colors */');
|
|
1099
|
+
lines.push(` --color-primary-1: ${ds.colors.primary['1']};`);
|
|
1100
|
+
lines.push(` --color-primary-2: ${ds.colors.primary['2']};`);
|
|
1101
|
+
lines.push(` --color-primary-3: ${ds.colors.primary['3']};`);
|
|
1102
|
+
lines.push(` --color-primary-soft: ${ds.colors.primary.soft};`);
|
|
1103
|
+
lines.push('');
|
|
1104
|
+
lines.push(' /* Semantic Colors */');
|
|
1105
|
+
lines.push(` --color-success-1: ${ds.colors.success['1']};`);
|
|
1106
|
+
lines.push(` --color-success-2: ${ds.colors.success['2']};`);
|
|
1107
|
+
lines.push(` --color-success-3: ${ds.colors.success['3']};`);
|
|
1108
|
+
lines.push(` --color-warning-1: ${ds.colors.warning['1']};`);
|
|
1109
|
+
lines.push(` --color-warning-2: ${ds.colors.warning['2']};`);
|
|
1110
|
+
lines.push(` --color-warning-3: ${ds.colors.warning['3']};`);
|
|
1111
|
+
lines.push(` --color-danger-1: ${ds.colors.danger['1']};`);
|
|
1112
|
+
lines.push(` --color-danger-2: ${ds.colors.danger['2']};`);
|
|
1113
|
+
lines.push(` --color-danger-3: ${ds.colors.danger['3']};`);
|
|
1114
|
+
lines.push(` --color-info-1: ${ds.colors.info['1']};`);
|
|
1115
|
+
lines.push(` --color-info-2: ${ds.colors.info['2']};`);
|
|
1116
|
+
lines.push(` --color-info-3: ${ds.colors.info['3']};`);
|
|
1117
|
+
lines.push('');
|
|
1118
|
+
lines.push(' /* Gray Scale */');
|
|
1119
|
+
lines.push(` --color-gray-1: ${ds.colors.gray['1']};`);
|
|
1120
|
+
lines.push(` --color-gray-2: ${ds.colors.gray['2']};`);
|
|
1121
|
+
lines.push(` --color-gray-3: ${ds.colors.gray['3']};`);
|
|
1122
|
+
lines.push(` --color-gray-soft: ${ds.colors.gray.soft};`);
|
|
1123
|
+
lines.push('');
|
|
1124
|
+
lines.push(' /* Background Colors */');
|
|
1125
|
+
lines.push(` --bg-default: ${ds.colors.bg.default};`);
|
|
1126
|
+
lines.push(` --bg-alt: ${ds.colors.bg.alt};`);
|
|
1127
|
+
lines.push(` --bg-elevated: ${ds.colors.bg.elevated};`);
|
|
1128
|
+
lines.push(` --bg-soft: ${ds.colors.bg.soft};`);
|
|
1129
|
+
lines.push('');
|
|
1130
|
+
lines.push(' /* Text Colors */');
|
|
1131
|
+
lines.push(` --text-1: ${ds.colors.text['1']};`);
|
|
1132
|
+
lines.push(` --text-2: ${ds.colors.text['2']};`);
|
|
1133
|
+
lines.push(` --text-3: ${ds.colors.text['3']};`);
|
|
1134
|
+
lines.push('');
|
|
1135
|
+
lines.push(' /* Border Colors */');
|
|
1136
|
+
lines.push(` --border-default: ${ds.colors.border.default};`);
|
|
1137
|
+
lines.push(` --border-divider: ${ds.colors.border.divider};`);
|
|
1138
|
+
lines.push(` --border-gutter: ${ds.colors.border.gutter};`);
|
|
1139
|
+
lines.push('');
|
|
1140
|
+
lines.push(' /* Typography */');
|
|
1141
|
+
lines.push(` --font-sans: ${ds.typography.fontFamily.sans};`);
|
|
1142
|
+
lines.push(` --font-mono: ${ds.typography.fontFamily.mono};`);
|
|
1143
|
+
lines.push('');
|
|
1144
|
+
lines.push(' /* Font Sizes */');
|
|
1145
|
+
lines.push(` --font-size-xs: ${ds.typography.fontSize.xs};`);
|
|
1146
|
+
lines.push(` --font-size-sm: ${ds.typography.fontSize.sm};`);
|
|
1147
|
+
lines.push(` --font-size-base: ${ds.typography.fontSize.base};`);
|
|
1148
|
+
lines.push(` --font-size-lg: ${ds.typography.fontSize.lg};`);
|
|
1149
|
+
lines.push(` --font-size-xl: ${ds.typography.fontSize.xl};`);
|
|
1150
|
+
lines.push(` --font-size-2xl: ${ds.typography.fontSize['2xl']};`);
|
|
1151
|
+
lines.push(` --font-size-3xl: ${ds.typography.fontSize['3xl']};`);
|
|
1152
|
+
lines.push(` --font-size-4xl: ${ds.typography.fontSize['4xl']};`);
|
|
1153
|
+
lines.push('');
|
|
1154
|
+
lines.push(' /* Font Weights */');
|
|
1155
|
+
lines.push(` --font-weight-normal: ${ds.typography.fontWeight.normal};`);
|
|
1156
|
+
lines.push(` --font-weight-medium: ${ds.typography.fontWeight.medium};`);
|
|
1157
|
+
lines.push(` --font-weight-semibold: ${ds.typography.fontWeight.semibold};`);
|
|
1158
|
+
lines.push(` --font-weight-bold: ${ds.typography.fontWeight.bold};`);
|
|
1159
|
+
lines.push('');
|
|
1160
|
+
lines.push(' /* Line Heights */');
|
|
1161
|
+
lines.push(` --leading-tight: ${ds.typography.lineHeight.tight};`);
|
|
1162
|
+
lines.push(` --leading-normal: ${ds.typography.lineHeight.normal};`);
|
|
1163
|
+
lines.push(` --leading-relaxed: ${ds.typography.lineHeight.relaxed};`);
|
|
1164
|
+
lines.push('');
|
|
1165
|
+
lines.push(' /* Spacing */');
|
|
1166
|
+
lines.push(` --space-xs: ${ds.spacing.scale.xs};`);
|
|
1167
|
+
lines.push(` --space-sm: ${ds.spacing.scale.sm};`);
|
|
1168
|
+
lines.push(` --space-md: ${ds.spacing.scale.md};`);
|
|
1169
|
+
lines.push(` --space-lg: ${ds.spacing.scale.lg};`);
|
|
1170
|
+
lines.push(` --space-xl: ${ds.spacing.scale.xl};`);
|
|
1171
|
+
lines.push(` --space-2xl: ${ds.spacing.scale['2xl']};`);
|
|
1172
|
+
lines.push(` --space-3xl: ${ds.spacing.scale['3xl']};`);
|
|
1173
|
+
lines.push(` --space-4xl: ${ds.spacing.scale['4xl']};`);
|
|
1174
|
+
lines.push('');
|
|
1175
|
+
lines.push(' /* Shadows */');
|
|
1176
|
+
lines.push(` --shadow-1: ${ds.effects.shadows['1']};`);
|
|
1177
|
+
lines.push(` --shadow-2: ${ds.effects.shadows['2']};`);
|
|
1178
|
+
lines.push(` --shadow-3: ${ds.effects.shadows['3']};`);
|
|
1179
|
+
lines.push(` --shadow-4: ${ds.effects.shadows['4']};`);
|
|
1180
|
+
lines.push(` --shadow-5: ${ds.effects.shadows['5']};`);
|
|
1181
|
+
lines.push('');
|
|
1182
|
+
lines.push(' /* Border Radius */');
|
|
1183
|
+
lines.push(` --radius-sm: ${ds.effects.borderRadius.sm};`);
|
|
1184
|
+
lines.push(` --radius-md: ${ds.effects.borderRadius.md};`);
|
|
1185
|
+
lines.push(` --radius-lg: ${ds.effects.borderRadius.lg};`);
|
|
1186
|
+
lines.push('');
|
|
1187
|
+
lines.push(' /* Transitions */');
|
|
1188
|
+
if (ds.effects.transitions?.base)
|
|
1189
|
+
lines.push(` --transition-base: ${ds.effects.transitions.base};`);
|
|
1190
|
+
if (ds.effects.transitions?.fast)
|
|
1191
|
+
lines.push(` --transition-fast: ${ds.effects.transitions.fast};`);
|
|
1192
|
+
if (ds.effects.transitions?.slow)
|
|
1193
|
+
lines.push(` --transition-slow: ${ds.effects.transitions.slow};`);
|
|
1194
|
+
lines.push('');
|
|
1195
|
+
lines.push(' /* Layout */');
|
|
1196
|
+
lines.push(` --layout-max-width: ${ds.layout.maxWidth};`);
|
|
1197
|
+
lines.push(` --navbar-height: ${ds.layout.navbarHeight};`);
|
|
1198
|
+
lines.push(` --sidebar-width: ${ds.layout.sidebarWidth};`);
|
|
1199
|
+
if (ds.layout.tocWidth)
|
|
1200
|
+
lines.push(` --toc-width: ${ds.layout.tocWidth};`);
|
|
1201
|
+
lines.push('');
|
|
1202
|
+
lines.push(' /* Z-Index */');
|
|
1203
|
+
lines.push(` --z-footer: ${ds.layout.zIndex.footer};`);
|
|
1204
|
+
lines.push(` --z-local-nav: ${ds.layout.zIndex.localNav};`);
|
|
1205
|
+
lines.push(` --z-nav: ${ds.layout.zIndex.nav};`);
|
|
1206
|
+
lines.push(` --z-layout-top: ${ds.layout.zIndex.layoutTop};`);
|
|
1207
|
+
lines.push(` --z-backdrop: ${ds.layout.zIndex.backdrop};`);
|
|
1208
|
+
lines.push(` --z-sidebar: ${ds.layout.zIndex.sidebar};`);
|
|
1209
|
+
// Component-specific variables
|
|
1210
|
+
if (ds.components) {
|
|
1211
|
+
if (ds.components.navbar) {
|
|
1212
|
+
lines.push('');
|
|
1213
|
+
lines.push(' /* Navbar Component */');
|
|
1214
|
+
if (ds.components.navbar.height)
|
|
1215
|
+
lines.push(` --navbar-height: ${ds.components.navbar.height};`);
|
|
1216
|
+
if (ds.components.navbar.padding)
|
|
1217
|
+
lines.push(` --navbar-padding: ${ds.components.navbar.padding};`);
|
|
1218
|
+
if (ds.components.navbar.background)
|
|
1219
|
+
lines.push(` --navbar-background: ${ds.components.navbar.background};`);
|
|
1220
|
+
if (ds.components.navbar.border)
|
|
1221
|
+
lines.push(` --navbar-border: ${ds.components.navbar.border};`);
|
|
1222
|
+
if (ds.components.navbar.borderWidth)
|
|
1223
|
+
lines.push(` --navbar-border-width: ${ds.components.navbar.borderWidth};`);
|
|
1224
|
+
if (ds.components.navbar.shadow)
|
|
1225
|
+
lines.push(` --navbar-shadow: ${ds.components.navbar.shadow};`);
|
|
1226
|
+
if (ds.components.navbar.logoHeight)
|
|
1227
|
+
lines.push(` --navbar-logo-height: ${ds.components.navbar.logoHeight};`);
|
|
1228
|
+
if (ds.components.navbar.itemPadding)
|
|
1229
|
+
lines.push(` --navbar-item-padding: ${ds.components.navbar.itemPadding};`);
|
|
1230
|
+
if (ds.components.navbar.itemGap)
|
|
1231
|
+
lines.push(` --navbar-item-gap: ${ds.components.navbar.itemGap};`);
|
|
1232
|
+
}
|
|
1233
|
+
if (ds.components.sidebar) {
|
|
1234
|
+
lines.push('');
|
|
1235
|
+
lines.push(' /* Sidebar Component */');
|
|
1236
|
+
if (ds.components.sidebar.width)
|
|
1237
|
+
lines.push(` --sidebar-width: ${ds.components.sidebar.width};`);
|
|
1238
|
+
if (ds.components.sidebar.padding)
|
|
1239
|
+
lines.push(` --sidebar-padding: ${ds.components.sidebar.padding};`);
|
|
1240
|
+
if (ds.components.sidebar.background)
|
|
1241
|
+
lines.push(` --sidebar-background: ${ds.components.sidebar.background};`);
|
|
1242
|
+
if (ds.components.sidebar.border)
|
|
1243
|
+
lines.push(` --sidebar-border: ${ds.components.sidebar.border};`);
|
|
1244
|
+
if (ds.components.sidebar.itemHeight)
|
|
1245
|
+
lines.push(` --sidebar-item-height: ${ds.components.sidebar.itemHeight};`);
|
|
1246
|
+
if (ds.components.sidebar.itemPadding)
|
|
1247
|
+
lines.push(` --sidebar-item-padding: ${ds.components.sidebar.itemPadding};`);
|
|
1248
|
+
if (ds.components.sidebar.itemGap)
|
|
1249
|
+
lines.push(` --sidebar-item-gap: ${ds.components.sidebar.itemGap};`);
|
|
1250
|
+
if (ds.components.sidebar.itemBorderRadius)
|
|
1251
|
+
lines.push(` --sidebar-item-border-radius: ${ds.components.sidebar.itemBorderRadius};`);
|
|
1252
|
+
if (ds.components.sidebar.itemFontSize)
|
|
1253
|
+
lines.push(` --sidebar-item-font-size: ${ds.components.sidebar.itemFontSize};`);
|
|
1254
|
+
if (ds.components.sidebar.itemFontWeight)
|
|
1255
|
+
lines.push(` --sidebar-item-font-weight: ${ds.components.sidebar.itemFontWeight};`);
|
|
1256
|
+
if (ds.components.sidebar.activeBackground)
|
|
1257
|
+
lines.push(` --sidebar-active-background: ${ds.components.sidebar.activeBackground};`);
|
|
1258
|
+
if (ds.components.sidebar.activeBorder)
|
|
1259
|
+
lines.push(` --sidebar-active-border: ${ds.components.sidebar.activeBorder};`);
|
|
1260
|
+
if (ds.components.sidebar.hoverBackground)
|
|
1261
|
+
lines.push(` --sidebar-hover-background: ${ds.components.sidebar.hoverBackground};`);
|
|
1262
|
+
if (ds.components.sidebar.categoryPadding)
|
|
1263
|
+
lines.push(` --sidebar-category-padding: ${ds.components.sidebar.categoryPadding};`);
|
|
1264
|
+
if (ds.components.sidebar.categoryFontSize)
|
|
1265
|
+
lines.push(` --sidebar-category-font-size: ${ds.components.sidebar.categoryFontSize};`);
|
|
1266
|
+
if (ds.components.sidebar.categoryFontWeight)
|
|
1267
|
+
lines.push(` --sidebar-category-font-weight: ${ds.components.sidebar.categoryFontWeight};`);
|
|
1268
|
+
if (ds.components.sidebar.categoryTextTransform)
|
|
1269
|
+
lines.push(` --sidebar-category-text-transform: ${ds.components.sidebar.categoryTextTransform};`);
|
|
1270
|
+
}
|
|
1271
|
+
if (ds.components.content) {
|
|
1272
|
+
lines.push('');
|
|
1273
|
+
lines.push(' /* Content Component */');
|
|
1274
|
+
if (ds.components.content.maxWidth)
|
|
1275
|
+
lines.push(` --content-max-width: ${ds.components.content.maxWidth};`);
|
|
1276
|
+
if (ds.components.content.padding)
|
|
1277
|
+
lines.push(` --content-padding: ${ds.components.content.padding};`);
|
|
1278
|
+
if (ds.components.content.fontSize)
|
|
1279
|
+
lines.push(` --content-font-size: ${ds.components.content.fontSize};`);
|
|
1280
|
+
if (ds.components.content.lineHeight)
|
|
1281
|
+
lines.push(` --content-line-height: ${ds.components.content.lineHeight};`);
|
|
1282
|
+
}
|
|
1283
|
+
if (ds.components.code) {
|
|
1284
|
+
lines.push('');
|
|
1285
|
+
lines.push(' /* Code Component */');
|
|
1286
|
+
if (ds.components.code.fontSize)
|
|
1287
|
+
lines.push(` --code-font-size: ${ds.components.code.fontSize};`);
|
|
1288
|
+
if (ds.components.code.lineHeight)
|
|
1289
|
+
lines.push(` --code-line-height: ${ds.components.code.lineHeight};`);
|
|
1290
|
+
if (ds.components.code.background)
|
|
1291
|
+
lines.push(` --code-background: ${ds.components.code.background};`);
|
|
1292
|
+
if (ds.components.code.color)
|
|
1293
|
+
lines.push(` --code-color: ${ds.components.code.color};`);
|
|
1294
|
+
if (ds.components.code.borderRadius)
|
|
1295
|
+
lines.push(` --code-border-radius: ${ds.components.code.borderRadius};`);
|
|
1296
|
+
if (ds.components.code.padding)
|
|
1297
|
+
lines.push(` --code-padding: ${ds.components.code.padding};`);
|
|
1298
|
+
if (ds.components.code.blockPadding)
|
|
1299
|
+
lines.push(` --code-block-padding: ${ds.components.code.blockPadding};`);
|
|
1300
|
+
if (ds.components.code.blockBorderRadius)
|
|
1301
|
+
lines.push(` --code-block-border-radius: ${ds.components.code.blockBorderRadius};`);
|
|
1302
|
+
}
|
|
1303
|
+
if (ds.components.heading) {
|
|
1304
|
+
lines.push('');
|
|
1305
|
+
lines.push(' /* Heading Component */');
|
|
1306
|
+
if (ds.components.heading.h1FontSize)
|
|
1307
|
+
lines.push(` --heading-h1-font-size: ${ds.components.heading.h1FontSize};`);
|
|
1308
|
+
if (ds.components.heading.h2FontSize)
|
|
1309
|
+
lines.push(` --heading-h2-font-size: ${ds.components.heading.h2FontSize};`);
|
|
1310
|
+
if (ds.components.heading.h3FontSize)
|
|
1311
|
+
lines.push(` --heading-h3-font-size: ${ds.components.heading.h3FontSize};`);
|
|
1312
|
+
if (ds.components.heading.h4FontSize)
|
|
1313
|
+
lines.push(` --heading-h4-font-size: ${ds.components.heading.h4FontSize};`);
|
|
1314
|
+
if (ds.components.heading.h1FontWeight)
|
|
1315
|
+
lines.push(` --heading-h1-font-weight: ${ds.components.heading.h1FontWeight};`);
|
|
1316
|
+
if (ds.components.heading.h2FontWeight)
|
|
1317
|
+
lines.push(` --heading-h2-font-weight: ${ds.components.heading.h2FontWeight};`);
|
|
1318
|
+
if (ds.components.heading.h3FontWeight)
|
|
1319
|
+
lines.push(` --heading-h3-font-weight: ${ds.components.heading.h3FontWeight};`);
|
|
1320
|
+
if (ds.components.heading.h1LineHeight)
|
|
1321
|
+
lines.push(` --heading-h1-line-height: ${ds.components.heading.h1LineHeight};`);
|
|
1322
|
+
if (ds.components.heading.h2LineHeight)
|
|
1323
|
+
lines.push(` --heading-h2-line-height: ${ds.components.heading.h2LineHeight};`);
|
|
1324
|
+
if (ds.components.heading.h3LineHeight)
|
|
1325
|
+
lines.push(` --heading-h3-line-height: ${ds.components.heading.h3LineHeight};`);
|
|
1326
|
+
if (ds.components.heading.marginTop)
|
|
1327
|
+
lines.push(` --heading-margin-top: ${ds.components.heading.marginTop};`);
|
|
1328
|
+
if (ds.components.heading.marginBottom)
|
|
1329
|
+
lines.push(` --heading-margin-bottom: ${ds.components.heading.marginBottom};`);
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
lines.push('}');
|
|
1333
|
+
// Add dark mode overrides if provided
|
|
1334
|
+
if (darkMode && darkMode.colors) {
|
|
1335
|
+
lines.push('');
|
|
1336
|
+
lines.push('/* Dark Mode */');
|
|
1337
|
+
lines.push('@media (prefers-color-scheme: dark) {');
|
|
1338
|
+
lines.push(' :root {');
|
|
1339
|
+
if (darkMode.colors.primary) {
|
|
1340
|
+
lines.push(' /* Brand/Primary Colors */');
|
|
1341
|
+
lines.push(` --color-primary-1: ${darkMode.colors.primary['1']};`);
|
|
1342
|
+
lines.push(` --color-primary-2: ${darkMode.colors.primary['2']};`);
|
|
1343
|
+
lines.push(` --color-primary-3: ${darkMode.colors.primary['3']};`);
|
|
1344
|
+
lines.push(` --color-primary-soft: ${darkMode.colors.primary.soft};`);
|
|
1345
|
+
}
|
|
1346
|
+
if (darkMode.colors.success) {
|
|
1347
|
+
lines.push('');
|
|
1348
|
+
lines.push(' /* Semantic Colors */');
|
|
1349
|
+
lines.push(` --color-success-1: ${darkMode.colors.success['1']};`);
|
|
1350
|
+
lines.push(` --color-success-2: ${darkMode.colors.success['2']};`);
|
|
1351
|
+
lines.push(` --color-success-3: ${darkMode.colors.success['3']};`);
|
|
1352
|
+
}
|
|
1353
|
+
if (darkMode.colors.warning) {
|
|
1354
|
+
lines.push(` --color-warning-1: ${darkMode.colors.warning['1']};`);
|
|
1355
|
+
lines.push(` --color-warning-2: ${darkMode.colors.warning['2']};`);
|
|
1356
|
+
lines.push(` --color-warning-3: ${darkMode.colors.warning['3']};`);
|
|
1357
|
+
}
|
|
1358
|
+
if (darkMode.colors.danger) {
|
|
1359
|
+
lines.push(` --color-danger-1: ${darkMode.colors.danger['1']};`);
|
|
1360
|
+
lines.push(` --color-danger-2: ${darkMode.colors.danger['2']};`);
|
|
1361
|
+
lines.push(` --color-danger-3: ${darkMode.colors.danger['3']};`);
|
|
1362
|
+
}
|
|
1363
|
+
if (darkMode.colors.info) {
|
|
1364
|
+
lines.push(` --color-info-1: ${darkMode.colors.info['1']};`);
|
|
1365
|
+
lines.push(` --color-info-2: ${darkMode.colors.info['2']};`);
|
|
1366
|
+
lines.push(` --color-info-3: ${darkMode.colors.info['3']};`);
|
|
1367
|
+
}
|
|
1368
|
+
if (darkMode.colors.gray) {
|
|
1369
|
+
lines.push('');
|
|
1370
|
+
lines.push(' /* Gray Scale */');
|
|
1371
|
+
lines.push(` --color-gray-1: ${darkMode.colors.gray['1']};`);
|
|
1372
|
+
lines.push(` --color-gray-2: ${darkMode.colors.gray['2']};`);
|
|
1373
|
+
lines.push(` --color-gray-3: ${darkMode.colors.gray['3']};`);
|
|
1374
|
+
lines.push(` --color-gray-soft: ${darkMode.colors.gray.soft};`);
|
|
1375
|
+
}
|
|
1376
|
+
if (darkMode.colors.bg) {
|
|
1377
|
+
lines.push('');
|
|
1378
|
+
lines.push(' /* Background Colors */');
|
|
1379
|
+
lines.push(` --bg-default: ${darkMode.colors.bg.default};`);
|
|
1380
|
+
lines.push(` --bg-alt: ${darkMode.colors.bg.alt};`);
|
|
1381
|
+
lines.push(` --bg-elevated: ${darkMode.colors.bg.elevated};`);
|
|
1382
|
+
lines.push(` --bg-soft: ${darkMode.colors.bg.soft};`);
|
|
1383
|
+
}
|
|
1384
|
+
if (darkMode.colors.text) {
|
|
1385
|
+
lines.push('');
|
|
1386
|
+
lines.push(' /* Text Colors */');
|
|
1387
|
+
lines.push(` --text-1: ${darkMode.colors.text['1']};`);
|
|
1388
|
+
lines.push(` --text-2: ${darkMode.colors.text['2']};`);
|
|
1389
|
+
lines.push(` --text-3: ${darkMode.colors.text['3']};`);
|
|
1390
|
+
}
|
|
1391
|
+
if (darkMode.colors.border) {
|
|
1392
|
+
lines.push('');
|
|
1393
|
+
lines.push(' /* Border Colors */');
|
|
1394
|
+
lines.push(` --border-default: ${darkMode.colors.border.default};`);
|
|
1395
|
+
lines.push(` --border-divider: ${darkMode.colors.border.divider};`);
|
|
1396
|
+
lines.push(` --border-gutter: ${darkMode.colors.border.gutter};`);
|
|
1397
|
+
}
|
|
1398
|
+
lines.push(' }');
|
|
1399
|
+
lines.push('}');
|
|
1400
|
+
}
|
|
1401
|
+
return lines.join('\n');
|
|
1402
|
+
}
|
|
1403
|
+
/**
|
|
1404
|
+
* Generate CSS variables for design system
|
|
1405
|
+
*/
|
|
1406
|
+
function generateDesignSystemVariables(designSystemName) {
|
|
1407
|
+
try {
|
|
1408
|
+
// Get the design system (default to vitepress if invalid)
|
|
1409
|
+
const validNames = ['vitepress', 'docusaurus', 'rspress'];
|
|
1410
|
+
const name = validNames.includes(designSystemName) ? designSystemName : 'vitepress';
|
|
1411
|
+
const designSystem = getDesignSystem(name);
|
|
1412
|
+
const darkModeOverride = getDarkModeOverride(name);
|
|
1413
|
+
return designSystemToCSS(designSystem, darkModeOverride);
|
|
1414
|
+
}
|
|
1415
|
+
catch (error) {
|
|
1416
|
+
console.error(`Failed to load design system "${designSystemName}":`, error);
|
|
1417
|
+
// Fallback to vitepress if there's an error
|
|
1418
|
+
const designSystem = getDesignSystem('vitepress');
|
|
1419
|
+
const darkModeOverride = getDarkModeOverride('vitepress');
|
|
1420
|
+
return designSystemToCSS(designSystem, darkModeOverride);
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
/**
|
|
1424
|
+
* Escape HTML
|
|
1425
|
+
*/
|
|
1426
|
+
function escapeHtml(str) {
|
|
1427
|
+
return str
|
|
1428
|
+
.replace(/&/g, '&')
|
|
1429
|
+
.replace(/</g, '<')
|
|
1430
|
+
.replace(/>/g, '>')
|
|
1431
|
+
.replace(/"/g, '"')
|
|
1432
|
+
.replace(/'/g, ''');
|
|
1433
|
+
}
|
|
1434
|
+
/**
|
|
1435
|
+
* Escape JavaScript strings
|
|
1436
|
+
*/
|
|
1437
|
+
function escapeJsString(str) {
|
|
1438
|
+
return str
|
|
1439
|
+
.replace(/\\/g, '\\\\')
|
|
1440
|
+
.replace(/'/g, "\\'")
|
|
1441
|
+
.replace(/"/g, '\\"')
|
|
1442
|
+
.replace(/\n/g, '\\n')
|
|
1443
|
+
.replace(/\r/g, '\\r');
|
|
1444
|
+
}
|
|
1445
|
+
/**
|
|
1446
|
+
* Run @marko/run build
|
|
1447
|
+
*/
|
|
1448
|
+
async function runMarkoRunBuild(outDir, debug, root) {
|
|
1449
|
+
return new Promise((resolve) => {
|
|
1450
|
+
const args = ['build'];
|
|
1451
|
+
if (outDir) {
|
|
1452
|
+
args.push('--output', outDir);
|
|
1453
|
+
}
|
|
1454
|
+
if (debug) {
|
|
1455
|
+
args.push('--debug');
|
|
1456
|
+
}
|
|
1457
|
+
const buildProcess = spawn('npx', ['marko-run', ...args], {
|
|
1458
|
+
stdio: 'inherit',
|
|
1459
|
+
cwd: root,
|
|
1460
|
+
});
|
|
1461
|
+
buildProcess.on('close', (code) => {
|
|
1462
|
+
if (code === 0) {
|
|
1463
|
+
// Determine output directory
|
|
1464
|
+
const outputDir = outDir || 'dist';
|
|
1465
|
+
resolve({
|
|
1466
|
+
success: true,
|
|
1467
|
+
outDir: path.join(root, outputDir),
|
|
1468
|
+
errors: [],
|
|
1469
|
+
});
|
|
1470
|
+
}
|
|
1471
|
+
else {
|
|
1472
|
+
resolve({
|
|
1473
|
+
success: false,
|
|
1474
|
+
outDir: '',
|
|
1475
|
+
errors: [`Build process exited with code ${code}`],
|
|
1476
|
+
});
|
|
1477
|
+
}
|
|
1478
|
+
});
|
|
1479
|
+
buildProcess.on('error', (error) => {
|
|
1480
|
+
resolve({
|
|
1481
|
+
success: false,
|
|
1482
|
+
outDir: '',
|
|
1483
|
+
errors: [`Failed to start build process: ${error.message}`],
|
|
1484
|
+
});
|
|
1485
|
+
});
|
|
1486
|
+
});
|
|
1487
|
+
}
|
|
1488
|
+
/**
|
|
1489
|
+
* Generate catch-all routes using dynamic routes
|
|
1490
|
+
* Supports generic modules (not just hardcoded docs/blog/pages)
|
|
1491
|
+
*/
|
|
1492
|
+
export async function generateCatchAllRoutes(manifest, routesDir, config, modules, debug) {
|
|
1493
|
+
console.log(' Using catch-all dynamic routes...');
|
|
1494
|
+
console.log(' Content will be rendered at request time');
|
|
1495
|
+
// Generate catch-all routes for each content directory from config
|
|
1496
|
+
const contentDirs = config.content || {};
|
|
1497
|
+
for (const [moduleId, dirConfig] of Object.entries(contentDirs)) {
|
|
1498
|
+
if (!dirConfig)
|
|
1499
|
+
continue;
|
|
1500
|
+
// Skip non-directory entries
|
|
1501
|
+
if (typeof dirConfig === 'object' && dirConfig !== null && !('dir' in dirConfig)) {
|
|
1502
|
+
continue;
|
|
1503
|
+
}
|
|
1504
|
+
if (moduleId === 'pages') {
|
|
1505
|
+
// Generate catch-all route for pages (root-level, no module prefix)
|
|
1506
|
+
const pagesDir = path.join(routesDir, '$$slug');
|
|
1507
|
+
await fs.mkdir(pagesDir, { recursive: true });
|
|
1508
|
+
// Handler - no manifest needed, content rendered at request time
|
|
1509
|
+
const pagesHandler = await loadTemplate('catch-all-handler.js.template', {
|
|
1510
|
+
CONTENT_TYPE: 'pages',
|
|
1511
|
+
CONFIG_PATH: '../_config.js',
|
|
1512
|
+
VITE_PLUGIN_PATH: 'markopress/build',
|
|
1513
|
+
});
|
|
1514
|
+
await fs.writeFile(path.join(pagesDir, '+handler.js'), pagesHandler);
|
|
1515
|
+
// Page
|
|
1516
|
+
const pagesPage = await loadTemplate('catch-all-page.marko.template', {
|
|
1517
|
+
CONTENT_TYPE_CLASS: 'page',
|
|
1518
|
+
});
|
|
1519
|
+
await fs.writeFile(path.join(pagesDir, '+page.marko'), pagesPage);
|
|
1520
|
+
if (debug)
|
|
1521
|
+
console.log(` Generated pages catch-all route`);
|
|
1522
|
+
}
|
|
1523
|
+
else {
|
|
1524
|
+
// Generate catch-all route for non-pages modules (blog, guides, etc.)
|
|
1525
|
+
const moduleDir = path.join(routesDir, moduleId, '$$slug');
|
|
1526
|
+
await fs.mkdir(moduleDir, { recursive: true });
|
|
1527
|
+
// Handler - no manifest needed
|
|
1528
|
+
const handlerTemplate = await loadTemplate('catch-all-handler.js.template', {
|
|
1529
|
+
CONTENT_TYPE: moduleId,
|
|
1530
|
+
CONFIG_PATH: '../../_config.js',
|
|
1531
|
+
VITE_PLUGIN_PATH: 'markopress/build',
|
|
1532
|
+
});
|
|
1533
|
+
await fs.writeFile(path.join(moduleDir, '+handler.js'), handlerTemplate);
|
|
1534
|
+
// Page
|
|
1535
|
+
const pageTemplate = await loadTemplate('catch-all-page.marko.template', {
|
|
1536
|
+
CONTENT_TYPE_CLASS: moduleId,
|
|
1537
|
+
});
|
|
1538
|
+
await fs.writeFile(path.join(moduleDir, '+page.marko'), pageTemplate);
|
|
1539
|
+
if (debug)
|
|
1540
|
+
console.log(` Generated ${moduleId} catch-all route`);
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
// Step 3: Generate config file for handlers
|
|
1544
|
+
await generateConfigFile(routesDir, config, debug);
|
|
1545
|
+
// Step 4: Generate vite.config.js for markdown content virtual module support
|
|
1546
|
+
await generateViteConfig(config.root, debug);
|
|
1547
|
+
// Step 5: Generate root layout that wraps all pages
|
|
1548
|
+
await generateRootLayout(routesDir, config, debug);
|
|
1549
|
+
}
|
|
1550
|
+
// Re-export markdown plugin utilities for use in generated handlers
|
|
1551
|
+
export { loadMarkdownModule, registerMarkdownContent };
|
|
1552
|
+
export { markdownContentPlugin } from './vite-markdown-plugin.js';
|
|
1553
|
+
//# sourceMappingURL=index.js.map
|