markopress 0.0.7 → 0.0.9

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 (135) hide show
  1. package/dist/build/index.d.ts +1 -0
  2. package/dist/build/index.js +1 -1
  3. package/dist/cli/index.js +1 -1
  4. package/dist/config/loader.js +1 -1
  5. package/dist/config/types.d.ts +15 -3
  6. package/dist/config/validation.d.ts +47 -15
  7. package/dist/config/validation.js +1 -1
  8. package/dist/dev/index.js +1 -1
  9. package/dist/markdown/tag-validator.js +1 -1
  10. package/dist/preview/index.js +1 -1
  11. package/package.json +1 -1
  12. package/src/theme/default/layouts/default.marko +4 -2
  13. package/src/theme/default/styles.css +22 -0
  14. package/templates/catch-all-handler.js.template +30 -13
  15. package/templates/layout.marko.template +1 -1
  16. package/dist/build/index.d.ts.map +0 -1
  17. package/dist/build/index.js.map +0 -1
  18. package/dist/build/security.test.d.ts +0 -6
  19. package/dist/build/security.test.d.ts.map +0 -1
  20. package/dist/build/security.test.js +0 -1
  21. package/dist/build/security.test.js.map +0 -1
  22. package/dist/build/types.d.ts.map +0 -1
  23. package/dist/build/types.js.map +0 -1
  24. package/dist/build/vite-config.test.d.ts +0 -2
  25. package/dist/build/vite-config.test.d.ts.map +0 -1
  26. package/dist/build/vite-config.test.js +0 -1
  27. package/dist/build/vite-config.test.js.map +0 -1
  28. package/dist/build/vite-markdown-plugin.d.ts.map +0 -1
  29. package/dist/build/vite-markdown-plugin.js.map +0 -1
  30. package/dist/build/vite-markdown-plugin.test.d.ts +0 -2
  31. package/dist/build/vite-markdown-plugin.test.d.ts.map +0 -1
  32. package/dist/build/vite-markdown-plugin.test.js +0 -1
  33. package/dist/build/vite-markdown-plugin.test.js.map +0 -1
  34. package/dist/cli/index.d.ts.map +0 -1
  35. package/dist/cli/index.js.map +0 -1
  36. package/dist/config/app-root.d.ts.map +0 -1
  37. package/dist/config/app-root.js.map +0 -1
  38. package/dist/config/app-root.test.d.ts +0 -2
  39. package/dist/config/app-root.test.d.ts.map +0 -1
  40. package/dist/config/app-root.test.js +0 -1
  41. package/dist/config/app-root.test.js.map +0 -1
  42. package/dist/config/index.d.ts.map +0 -1
  43. package/dist/config/index.js.map +0 -1
  44. package/dist/config/loader.d.ts.map +0 -1
  45. package/dist/config/loader.js.map +0 -1
  46. package/dist/config/loader.test.d.ts +0 -2
  47. package/dist/config/loader.test.d.ts.map +0 -1
  48. package/dist/config/loader.test.js +0 -1
  49. package/dist/config/loader.test.js.map +0 -1
  50. package/dist/config/types.d.ts.map +0 -1
  51. package/dist/config/types.js.map +0 -1
  52. package/dist/config/validation.d.ts.map +0 -1
  53. package/dist/config/validation.js.map +0 -1
  54. package/dist/content/index.d.ts.map +0 -1
  55. package/dist/content/index.js.map +0 -1
  56. package/dist/content/registry.d.ts.map +0 -1
  57. package/dist/content/registry.js.map +0 -1
  58. package/dist/content/types.d.ts.map +0 -1
  59. package/dist/content/types.js.map +0 -1
  60. package/dist/dev/index.d.ts.map +0 -1
  61. package/dist/dev/index.js.map +0 -1
  62. package/dist/index.d.ts.map +0 -1
  63. package/dist/index.js.map +0 -1
  64. package/dist/markdown/code.d.ts.map +0 -1
  65. package/dist/markdown/code.js.map +0 -1
  66. package/dist/markdown/containers.d.ts.map +0 -1
  67. package/dist/markdown/containers.js.map +0 -1
  68. package/dist/markdown/includes.d.ts.map +0 -1
  69. package/dist/markdown/includes.js.map +0 -1
  70. package/dist/markdown/index.d.ts.map +0 -1
  71. package/dist/markdown/index.js.map +0 -1
  72. package/dist/markdown/loader.d.ts.map +0 -1
  73. package/dist/markdown/loader.js.map +0 -1
  74. package/dist/markdown/preserve-tags.d.ts.map +0 -1
  75. package/dist/markdown/preserve-tags.js.map +0 -1
  76. package/dist/markdown/renderer.d.ts.map +0 -1
  77. package/dist/markdown/renderer.js.map +0 -1
  78. package/dist/markdown/tag-validator.d.ts.map +0 -1
  79. package/dist/markdown/tag-validator.js.map +0 -1
  80. package/dist/markdown/types.d.ts.map +0 -1
  81. package/dist/markdown/types.js.map +0 -1
  82. package/dist/plugin/compat.d.ts.map +0 -1
  83. package/dist/plugin/compat.js.map +0 -1
  84. package/dist/plugin/context.d.ts.map +0 -1
  85. package/dist/plugin/context.js.map +0 -1
  86. package/dist/plugin/index.d.ts.map +0 -1
  87. package/dist/plugin/index.js.map +0 -1
  88. package/dist/plugin/manager.d.ts.map +0 -1
  89. package/dist/plugin/manager.js.map +0 -1
  90. package/dist/plugin/types.d.ts.map +0 -1
  91. package/dist/plugin/types.js.map +0 -1
  92. package/dist/plugins/blog-index/index.d.ts.map +0 -1
  93. package/dist/plugins/blog-index/index.js.map +0 -1
  94. package/dist/plugins/sidenav/index.d.ts.map +0 -1
  95. package/dist/plugins/sidenav/index.js.map +0 -1
  96. package/dist/plugins/toc/index.d.ts.map +0 -1
  97. package/dist/plugins/toc/index.js.map +0 -1
  98. package/dist/preview/index.d.ts.map +0 -1
  99. package/dist/preview/index.js.map +0 -1
  100. package/dist/theme/default/build/generate-all.d.ts +0 -9
  101. package/dist/theme/default/build/generate-all.d.ts.map +0 -1
  102. package/dist/theme/default/build/generate-all.js +0 -1
  103. package/dist/theme/default/build/generate-all.js.map +0 -1
  104. package/dist/theme/default/build/generate-css.d.ts +0 -19
  105. package/dist/theme/default/build/generate-css.d.ts.map +0 -1
  106. package/dist/theme/default/build/generate-css.js +0 -1
  107. package/dist/theme/default/build/generate-css.js.map +0 -1
  108. package/dist/theme/default/build/index.d.ts +0 -5
  109. package/dist/theme/default/build/index.d.ts.map +0 -1
  110. package/dist/theme/default/build/index.js +0 -1
  111. package/dist/theme/default/build/index.js.map +0 -1
  112. package/dist/theme/default/design-systems/default.d.ts.map +0 -1
  113. package/dist/theme/default/design-systems/default.js.map +0 -1
  114. package/dist/theme/default/design-systems/docusaurus.d.ts.map +0 -1
  115. package/dist/theme/default/design-systems/docusaurus.js.map +0 -1
  116. package/dist/theme/default/design-systems/index.d.ts.map +0 -1
  117. package/dist/theme/default/design-systems/index.js.map +0 -1
  118. package/dist/theme/default/design-systems/rspress.d.ts.map +0 -1
  119. package/dist/theme/default/design-systems/rspress.js.map +0 -1
  120. package/dist/theme/default/design-systems/types.d.ts.map +0 -1
  121. package/dist/theme/default/design-systems/types.js.map +0 -1
  122. package/dist/theme/default/design-systems/vitepress.d.ts.map +0 -1
  123. package/dist/theme/default/design-systems/vitepress.js.map +0 -1
  124. package/dist/theme/default/index.d.ts.map +0 -1
  125. package/dist/theme/default/index.js.map +0 -1
  126. package/dist/theme/default/theme.d.ts.map +0 -1
  127. package/dist/theme/default/theme.js.map +0 -1
  128. package/dist/theme/index.d.ts.map +0 -1
  129. package/dist/theme/index.js.map +0 -1
  130. package/dist/theme/loader.d.ts.map +0 -1
  131. package/dist/theme/loader.js.map +0 -1
  132. package/dist/theme/types.d.ts.map +0 -1
  133. package/dist/theme/types.js.map +0 -1
  134. package/src/theme/default/styles/main.css +0 -249
  135. package/src/theme/default/styles-base.css +0 -757
@@ -6,6 +6,7 @@ import type { ContentManifest } from '../content/types.js';
6
6
  import type { ResolvedConfig } from '../config/types.js';
7
7
  import { loadMarkdownModule, registerMarkdownContent } from './vite-markdown-plugin.js';
8
8
  export type ContentModule = any;
9
+ export declare function filePathToUrl(filePath: string, contentDir: string): string;
9
10
  export interface BuildOptions {
10
11
  useCatchAllRoutes?: boolean;
11
12
  outDir?: string;
@@ -1 +1 @@
1
- import{promises as e}from"node:fs";import o from"node:path";import{spawn as t}from"node:child_process";import{fileURLToPath as n}from"node:url";import s from"gray-matter";import{loadConfig as a}from"../config/loader.js";import{getDesignSystem as i,getDarkModeOverride as r}from"../theme/default/design-systems/index.js";import{globalTagValidator as c,formatValidationError as l}from"../markdown/index.js";import{PluginManager as d}from"../plugin/manager.js";import{loadMarkdownModule as u,registerMarkdownContent as m,escapeMarkoText as g}from"./vite-markdown-plugin.js";import{renderMarkdown as f}from"../markdown/renderer.js";import{buildSearchIndex as p}from"../search/index.js";const h=o.dirname(n(import.meta.url)),w=o.resolve(h,"..",".."),k=o.join(w,"src","theme","default"),y=new Set(["@markopress/theme-default","theme-default","default"]);function j(e){return y.has(e)}export async function build(n={}){const{outDir:i,debug:r=!1,useCatchAllRoutes:u,root:m}=n,h=m||process.cwd(),w=[],k=new Map,y=new Map,j=e=>({start:()=>{y.set(e,performance.now())},end:()=>{const o=y.get(e)||0,t=performance.now()-o;k.set(e,t)}});try{console.log("🚀 Building MarkoPress site...\n");const n=j("Config loading");n.start();const m=await a(h,{mode:"production",command:"build"});let y;if(n.end(),m.plugins&&m.plugins.length>0){console.log("🔌 Loading plugins...");const e=j("Plugin loading");e.start(),y=new d(m),await y.loadPlugins(m.plugins),e.end(),console.log("")}if(y){console.log("📦 Loading plugin content...");const e=j("Plugin loadContent hooks");e.start(),await y.execLoadContentHooks(),e.end(),console.log(" Plugin content loaded\n")}const $={},T=[];for(const[t,n]of Object.entries(m.content)){const a="string"==typeof n?{dir:n}:n;if(!a?.dir)continue;const i=new Map,r={id:t,dir:a.dir,config:a,enhance(e,o){i.set(e,o)},getEnhancement:e=>i.get(e),files:[],_enhancements:i},c=o.resolve(h,a.dir);try{const n=await e.readdir(c,{withFileTypes:!0});for(const a of n)if(a.isFile()&&a.name.endsWith(".md")){const n=o.join(c,a.name),i=await e.readFile(n,"utf-8");let l={};try{l=s(i).data}catch{}r.files.push({id:a.name.replace(".md",""),slug:a.name.replace(".md",""),filePath:n,urlPath:"index"===a.name.replace(".md","")?"/"+t:`/${t}/${a.name.replace(".md","")}`,processed:{frontmatter:l}})}}catch{}T.push(r)}if(y&&T.length>0){console.log("🔌 Enhancing modules with plugin metadata...");const t=j("Module enhancement");t.start();for(const e of T)console.log(` Module: ${e.id} (${e.files.length} files)`),e.files.length>0&&console.log(" First file has processed: "+!!e.files[0].processed);await y.execEnhanceModulesHooks(T),t.end(),console.log(` Enhanced ${T.length} module(s)\n`);const n=o.join(h,"src",".generated");await e.mkdir(n,{recursive:!0});const s=(m.site?.base||"/").replace(/\/$/,""),a={};for(const e of T){const o={},t=e._enhancements.entries();for(const[e,n]of t)o[e]=n;Object.keys(o).length>0&&(s&&b(o,s),a[e.id]=o)}const i=o.join(n,"module-enhancements.js"),r=`// Auto-generated by MarkoPress - Do not edit\nexport default ${JSON.stringify(a,null,2)};\n`;await e.writeFile(i,r,"utf-8"),console.log(" Wrote module enhancements to src/.generated/module-enhancements.js\n")}if(!1!==m.search?.enabled){console.log("🔍 Building search index...");const t=j("Search index");t.start();const n=[];for(const o of T)for(const t of o.files)try{const o=await e.readFile(t.filePath,"utf-8"),s=await f(o,m.markdown);n.push({url:t.urlPath,html:s.html,title:s.frontmatter?.title||t.id,frontmatter:s.frontmatter})}catch(e){console.warn(` Warning: Could not index ${t.filePath}:`,e)}try{const t=await p(n,m.search),s=o.join(h,"public","search-index.json");await e.mkdir(o.dirname(s),{recursive:!0}),await e.writeFile(s,t),console.log(` Search index built (${n.length} pages)\n`)}catch(e){console.warn(" Warning: Failed to build search index:",e)}t.end()}if(m.markdown.markoTags?.enabled){const e=o.join(h,m.markdown.markoTags.tagsDir||"src/.markopress/tags");console.log("🔍 Scanning tags directory...");const t=j("Tag validation setup");t.start(),await c.loadAvailableTags(e),t.end(),console.log(` Found ${c.getAvailableTagsCount()} tags\n`)}else c.reset();const v=o.join(h,"src","routes");await e.mkdir(v,{recursive:!0});let C={};if(y){const e=j("Extend routes hooks");e.start(),C=await y.execExtendRoutesHooks(C),e.end(),console.log("🔌 Extended routes manifest:",Object.keys(C).length)}console.log("📝 Generating routes from content...");const P=j("Route generation");P.start();const F=u??m.build.useCatchAllRoutes;console.log("📄 Pre-rendering markdown to .marko files...");const x=j("Pre-render markdown");x.start();const E=o.join(h,"src",".generated","markdown");await e.mkdir(E,{recursive:!0});const S={};let A=0;for(const t of T){const n=o.join(E,t.id);await e.mkdir(n,{recursive:!0});for(const s of t.files)try{const a=await e.readFile(s.filePath,"utf-8"),i=(m.site?.base||"/").replace(/\/$/,""),r=await f(a,{base:i});let c=r.html.replace(/<span class="line"><\/span>(\s*<\/code>)/g,"$1");const l=`<div class="markdown-content">\n${g(c)}\n</div>`,d=o.join(n,s.slug+".marko");await e.writeFile(d,l),S[`${t.id}/${s.slug}`]={frontmatter:r.frontmatter,headers:r.headers||[]},A++}catch(e){console.warn(` Warning: Failed to pre-render ${t.id}/${s.slug}:`,e)}}const _=o.join(h,"src",".generated","content-metadata.js"),D=`// Auto-generated by MarkoPress - Do not edit\nexport default ${JSON.stringify(S,null,2)};\n`;await e.writeFile(_,D),x.end(),console.log(` Pre-rendered ${A} markdown files\n`),F?(await generateCatchAllRoutes($,v,m,T,r,!0),console.log(" Using catch-all dynamic routes")):(await generateRoutes($,v,m,T,r),console.log(" Using static routes")),P.end(),console.log(" Routes generated\n");const O=[];for(const[e,o]of Object.entries(C))(o.handler||o.component)&&(O.push({path:e,...o}),console.log(" Found plugin route: "+e));if(console.log(`🔌 Total manifest routes: ${Object.keys(C).length}, Plugin routes: ${O.length}`),y){const t=[...y.getPluginRoutes(),...O];if(t.length>0){console.log(`🔌 Generating ${t.length} plugin routes...`);const n=j("Plugin route generation");n.start(),await async function(t,n,s,a){for(const s of t){const t=s.path.slice(1),i=o.join(n,t,"+page");if(await e.mkdir(o.dirname(i),{recursive:!0}),s.handler){const t=o.join(o.dirname(i),"+handler.js");await e.writeFile(t,s.handler)}if(s.component){const o=i+".marko";await e.writeFile(o,s.component)}a&&console.log(" Generated plugin route: "+s.path)}}(t,v,0,r),n.end(),console.log(" Plugin routes generated\n")}}console.log("⚙️ Generating Vite config...");const M=j("Vite config generation");if(M.start(),await generateViteConfig(h,r),M.end(),console.log(" Vite config generated\n"),y){console.log("🔌 Processing plugin allContentLoaded hooks...");const e=j("AllContentLoaded hooks");e.start(),await y.execAllContentLoadedHooks(C),e.end(),console.log(" All content processed\n")}if(m.markdown.markoTags?.enabled){console.log("🔍 Validating Marko tags...");const e=j("Tag validation");e.start();const o=c.validate();if(e.end(),!o.success){const e=l(o.missingTags);return console.error(`\n${e}\n`),w.push(e),{success:!1,outDir:"",pages:0,errors:w}}console.log(" All tags validated ✓\n")}console.log("🎨 Copying theme CSS...");const N=j("Theme CSS copy");N.start(),await copyThemeCSS(h,m,r),N.end(),console.log(" Theme CSS copied\n"),console.log("🎨 Extracting styles from Marko components...");const W=j("Marko component styles extraction");W.start(),await extractStylesFromMarkoTags(h,m,r),W.end(),console.log(" Component styles extracted\n");const G=[];for(const e of T)for(const o of e.files)"pages"===e.id?G.push("index"===o.id?"/":"/"+o.id):G.push(o.urlPath);for(const e of Object.keys(C))G.includes(e)||G.push(e);const L=o.join(h,"src",".generated","static-urls.json");await e.mkdir(o.dirname(L),{recursive:!0}),await e.writeFile(L,JSON.stringify(G,null,2)),r&&console.log(` Generated static URL manifest: ${G.length} URLs`),console.log("🔨 Building with @marko/run...");const R=j("@marko/run build");R.start();const I=i||m.build.outDir,B=await async function(e,n,s){return new Promise(a=>{const i=["build"];e&&i.push("--output",e),n&&i.push("--debug");const r=t("npx",["marko-run",...i],{stdio:"inherit",cwd:s});r.on("close",t=>{if(0===t){const t=e||"dist";a({success:!0,outDir:o.join(s,t),errors:[]})}else a({success:!1,outDir:"",errors:["Build process exited with code "+t]})}),r.on("error",e=>{a({success:!1,outDir:"",errors:["Failed to start build process: "+e.message]})})})}(I,r,h);if(R.end(),!B.success)return w.push(...B.errors),{success:!1,outDir:"",pages:0,errors:w};const H=j("Collect build assets");H.start();const U=await async function(o){const t=[];try{const n=await e.readdir(o,{recursive:!0});for(const e of n)"string"==typeof e&&(e.endsWith(".js")||e.endsWith(".css")||e.endsWith(".json"))&&t.push(e)}catch(e){console.warn("Warning: Could not collect build assets:",e)}return t}(B.outDir);if(H.end(),y){console.log("🔌 Processing plugin postBuild hooks...");const e=j("Post-build hooks");e.start(),await y.execPostBuildHooks(B.outDir,C,U),e.end(),console.log(" Post-build hooks completed\n")}console.log("📦 Copying Marko tags directory...");const V=j("Copy tags directory");V.start(),await copyTagsDirectory(h,B.outDir,m,r),V.end(),console.log(" Tags directory copied\n"),console.log("\n✅ Build completed successfully!"),console.log(" Output: "+B.outDir),console.log(" Pages: Generated dynamically at request time"),console.log("\n⏱️ Build timing:");const Y=Array.from(k.entries()).sort((e,o)=>o[1]-e[1]);for(const[e,o]of Y){const t=(o/1e3).toFixed(2);console.log(` ${"█".repeat(Math.min(Math.floor(o/100),20))} ${e}: ${t}s`)}return{success:!0,outDir:B.outDir,pages:0,errors:w}}catch(e){const o=e instanceof Error?e.message:e+"";return w.push(o),console.error("\n❌ Build failed:",o),{success:!1,outDir:"",pages:0,errors:w}}}function b(e,o){Array.isArray(e.sidebar)&&(e.sidebar=e.sidebar.map(e=>({...e,items:Array.isArray(e.items)?e.items.map(e=>({...e,link:e.link&&!e.link.startsWith(o)?o+e.link:e.link})):e.items}))),Array.isArray(e.blogPosts)&&(e.blogPosts=e.blogPosts.map(e=>({...e,link:e.link&&!e.link.startsWith(o)?o+e.link:e.link})))}export async function generateRoutes(e,o,t,n,s){await cleanupGeneratedRoutes(o,e,s);const a=[];let i=0,r=0,c=0;for(const[o,t]of Object.entries(e)){if(!Array.isArray(t))continue;a.push(o);const e=t;if("pages"===o)for(const o of e)await T(0,0,0,0,s),i++;else if("blog"===o)for(const o of e)await C(0,0,0,0,s),c++;else for(const o of e)await v(0,0,0,0,s),r++}await P(o,t,s),await generateViteConfig(t.root,s),await F(o,t,s),s&&(console.log(` Generated ${i} page routes`),console.log(` Generated ${r} doc routes`),console.log(` Generated ${c} blog routes`))}export async function cleanupGeneratedRoutes(t,n,s){const a=[],i=["+layout.marko","+middleware.js","components/**/*","api/**/*","lib/**/*"],r=Object.keys(n).filter(e=>"pages"!==e).map(e=>e+"/");try{const n=await e.readdir(t,{recursive:!0,withFileTypes:!0});for(const c of n){if(!c.isFile())continue;const n=o.join(c.path||c.parentPath||t,c.name),l=o.relative(t,n);if(r.some(e=>l.startsWith(e)))if(i.some(e=>e.includes("**")?RegExp(e.replace(/\*\*/g,".*").replace(/\*/g,"[^/]*")).test(l):c.name===e))s&&console.log(" Preserving: "+l);else try{await e.unlink(n),s&&console.log(" Deleted: "+l)}catch(e){if("ENOENT"!==e.code){const o=e instanceof Error?e.message:e+"";a.push(`Failed to delete ${l}: ${o}`)}}}for(const e of r){const n=o.join(t,e);try{await $(n)}catch{}}a.length>0&&(console.warn("⚠️ Cleanup warnings:"),a.forEach(e=>console.warn(" "+e)))}catch(e){if("ENOENT"!==e.code)throw e}}async function $(t){try{const n=await e.readdir(t,{withFileTypes:!0});for(const e of n)if(e.isDirectory()){const n=o.join(t,e.name);await $(n)}0===(await e.readdir(t)).length&&await e.rmdir(t)}catch{}}async function T(e,o,t,n,s){s&&console.log(" Warning: Static routes deprecated, use catch-all routes")}async function v(e,o,t,n,s){s&&console.log(" Warning: Static routes deprecated, use catch-all routes")}async function C(e,o,t,n,s){s&&console.log(" Warning: Static routes deprecated, use catch-all routes")}async function P(t,n,s){const a=o.join(t,"_config.js"),i={root:n.root,site:{title:n.site?.title||"MarkoPress",description:n.site?.description||"",lang:n.site?.lang||"en-US",head:n.site?.head||[],base:n.site?.base||"/"},content:n.content,theme:{name:n.theme?.name||"@markopress/theme-default",options:n.theme?.options||{}},markdown:n.markdown||{},build:n.build||{}},r=(n.site?.base||"/").replace(/\/$/,"");r&&i.theme.options?.navbar&&(i.theme.options.navbar=i.theme.options.navbar.map(e=>({...e,link:e.link&&!e.link.startsWith(r)?r+e.link:e.link})));const c=`// Auto-generated by MarkoPress - Do not edit\nexport const config = ${JSON.stringify(i,null,2)};\n`;await e.writeFile(a,c),s&&console.log(" Generated: "+a)}async function F(t,n,s){const a=o.join(t,"+layout.marko"),i=(n.theme,n.site?.title||"MarkoPress"),r=n.theme?.options?.style||"default",c=await x("layout.marko.template",{SITE_TITLE:i,THEME_STYLE:r,BASE_PATH:n.site?.base?.replace(/\/$/,"")||""});await e.writeFile(a,c),s&&console.log(" Generated: "+a)}async function x(t,n){const s=o.dirname(new URL(import.meta.url).pathname),a=o.join(s,"..",".."),i=o.join(a,"templates",t);let r=await e.readFile(i,"utf-8");for(const[e,o]of Object.entries(n))r=r.replace(RegExp(`\\{\\{${e}\\}\\}`,"g"),o);return r}export function validateThemeName(e){if(!/^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/.test(e))throw Error(`Invalid theme name: "${e}". Must be a valid npm package name (e.g., "my-theme" or "@org/my-theme")`);if(e.includes(".."))throw Error(`Theme name cannot contain traversal sequences (..): "${e}"`);if(e.includes("\\"))throw Error(`Theme name cannot contain backslashes: "${e}"`);if((e.match(/\//g)||[]).length>1)throw Error(`Theme name can only contain one forward slash (for scoped packages): "${e}"`);if(o.isAbsolute(e))throw Error(`Theme name cannot be an absolute path: "${e}"`)}export async function generateViteConfig(t,n){const s=o.join(t,"vite.config.js");try{if((await e.readFile(s,"utf-8")).includes("markdownContentPlugin"))return void(n&&console.log(" Vite config already has markdownContentPlugin"))}catch{}await e.writeFile(s,"import { defineConfig } from 'vite';\nimport marko from '@marko/run/vite';\nimport { markdownContentPlugin } from 'markopress/build';\n\nexport default defineConfig({\n plugins: [\n marko(),\n markdownContentPlugin(),\n ],\n resolve: {\n // Preserve symlinks for pnpm workspace compatibility\n // This allows Marko to properly discover tags from symlinked packages\n preserveSymlinks: true,\n },\n build: {\n outDir: 'dist',\n },\n});\n"),n&&console.log(" Created vite.config.js with markdownContentPlugin")}export async function copyThemeCSS(t,n,s){const a=o.join(t,"public","_markopress","theme");await e.mkdir(a,{recursive:!0});const i=n.theme?.name||"@markopress/theme-default";try{validateThemeName(i)}catch(e){const o=e instanceof Error?e.message:e+"";throw Error("Security: "+o)}const r=n.theme?.options?.style||"default",c=`theme-${r}.css`,l=[...j(i)?[o.join(k,"public",c)]:[],o.join(t,"..","node_modules",i,"public",c),o.join(t,"node_modules",i,"public",c)];let d=null,u=null;for(const o of l)try{await e.access(o),d=await e.readFile(o,"utf-8"),u=o;break}catch{}if(!d){console.warn(` Warning: Could not find ${c}, using minimal fallback`);const t=`/* Minimal fallback CSS for style: ${r} */\nbody { font-family: system-ui, sans-serif; margin: 0; padding: 0; }`,n=o.join(a,c);return void await e.writeFile(n,t)}const m=o.join(a,c);await e.writeFile(m,d),s&&(console.log(` Copied ${c} from: ${u}`),console.log(" Output: "+m));const g="styles.css",f=[...j(i)?[o.join(k,g)]:[],o.join(t,"..","node_modules",i,"src",g),o.join(t,"node_modules",i,"src",g)];for(const t of f)try{await e.access(t);const n=await e.readFile(t,"utf-8"),i=o.join(a,g);await e.writeFile(i,n),s&&(console.log(` Copied ${g} from: ${t}`),console.log(" Output: "+i));break}catch{}}export async function extractStylesFromMarkoTags(t,n,s){const a=n.markdown?.markoTags?.tagsDir||"src/.markopress/tags",i=o.join(t,a);try{await e.access(i)}catch{return void(s&&console.log(" No tags directory found at: "+i))}const r=[];if(await async function t(n){const s=await e.readdir(n,{withFileTypes:!0});for(const e of s){const s=o.join(n,e.name);e.isDirectory()?await t(s):e.isFile()&&e.name.endsWith(".marko")&&r.push(s)}}(i),0===r.length)return void(s&&console.log(" No .marko files found in: "+i));const c=[];c.push("/* Custom markdown tag styles"),c.push(" * Loaded globally because request-time virtual markdown modules"),c.push(" * do not emit tag-local CSS assets reliably. */"),c.push("");for(const t of r){const n=o.relative(i,t),s=""===o.dirname(n)?o.basename(n,".marko"):o.join(o.dirname(n),o.basename(n,".marko"));try{const o=await e.readFile(t,"utf-8"),n=/<style\b[^>]*>([\s\S]*?)<\/style>/gi,a=Array.from(o.matchAll(n));if(a.length>0){c.push(`/* ${s}.marko */`);for(const e of a){const o=e[1]||"";if(o){const e=o.split("\n");let t=0;for(;t<e.length&&""===e[t].trim();)t++;let n=e.length-1;for(;n>=t&&""===e[n].trim();)n--;for(let o=t;o<=n;o++){const t=e[o];if(""===t.trim()){c.push("");continue}const n=t.match(/^(\s*)/),s=n?n[1].length:0,a=" ".repeat(Math.floor(s/2)),i=t.trim().replace(/:global\(([^)]+)\)/g,"$1");c.push(a+i)}}}c.push("")}}catch(e){console.warn(` Warning: Could not read file ${t}:`,e)}}const l=o.join(t,"public");await e.mkdir(l,{recursive:!0});const d=o.join(l,"markopress-components.css"),u=c.join("\n");await e.writeFile(d,u),s&&(console.log(` Extracted styles from ${r.length} Marko component(s)`),console.log(" Output: "+d))}async function E(t){const n=await e.readdir(t,{withFileTypes:!0}),s=await Promise.all(n.map(e=>{const n=o.resolve(t,e.name);return e.isDirectory()?E(n):n}));return Array.prototype.concat(...s).filter(e=>e.endsWith(".marko"))}export async function copyThemeComponents(t,n,s){const a=n.theme?.name||"@markopress/theme-default",i=o.join(t,"src"),r=o.join(i,"tags");await e.mkdir(r,{recursive:!0});const c=[...j(a)?[o.join(k,"tags")]:[],o.join(t,"..","node_modules",a,"dist","tags"),o.join(t,"node_modules",a,"dist","tags"),o.join(t,"..","node_modules",a,"src","components"),o.join(t,"node_modules",a,"src","components")];let l=null;for(const o of c)try{await e.access(o),l=o;break}catch{}if(!l)return void(s&&console.warn(" Warning: Could not find theme components, skipping"));const d=await E(l);let u=0;for(const t of d){const n=o.relative(l,t),a=o.join(r,n);let i=!1;try{await e.access(a),i=!0}catch{}i?s&&console.log(" Skipped component (user override exists): "+n):(await e.mkdir(o.dirname(a),{recursive:!0}),await e.copyFile(t,a),u++)}s&&(console.log(` Copied ${u} theme components from: ${l}`),console.log(" Output: "+r))}export async function copyTagsDirectory(t,n,s,a){const i=s.markdown?.markoTags?.tagsDir||"src/.markopress/tags",r=o.join(t,i),c=o.join(n,"tags");try{await e.access(r)}catch{return void(a&&console.log(" No tags directory found at: "+r))}await e.mkdir(c,{recursive:!0});const l=await e.readdir(r,{withFileTypes:!0});let d=0;for(const t of l){const n=o.join(r,t.name),s=o.join(c,t.name);if(t.isDirectory()){await e.mkdir(s,{recursive:!0});const t=await e.readdir(n,{withFileTypes:!0});for(const a of t){const t=o.join(n,a.name),i=o.join(s,a.name);a.isDirectory()||(await e.copyFile(t,i),d++)}}else t.isFile()&&(await e.copyFile(n,s),d++)}a&&(console.log(` Copied ${d} tag files from: ${r}`),console.log(" Output: "+c))}export async function generateCatchAllRoutes(t,n,s,a,i,r=!0){console.log(" Using catch-all dynamic routes..."),console.log(" Mode: "+(r?"build (pre-compiled)":"dev (request-time rendering)"));const c=s.content||{};for(const[t,s]of Object.entries(c))if(s&&("object"!=typeof s||null===s||"dir"in s))if("pages"===t){const t=o.join(n,"$$slug");await e.mkdir(t,{recursive:!0});const s=await x("catch-all-handler.js.template",{CONTENT_TYPE:"pages",CONFIG_PATH:"../_config.js",VITE_PLUGIN_PATH:"markopress/build",IS_BUILD:r?"true":"false"});await e.writeFile(o.join(t,"+handler.js"),s);const a=await x("catch-all-page.marko.template",{CONTENT_TYPE_CLASS:"page"});await e.writeFile(o.join(t,"+page.marko"),a),i&&console.log(" Generated pages catch-all route")}else{const s=o.join(n,t,"$$slug");await e.mkdir(s,{recursive:!0});const a=await x("catch-all-handler.js.template",{CONTENT_TYPE:t,CONFIG_PATH:"../../_config.js",VITE_PLUGIN_PATH:"markopress/build",IS_BUILD:r?"true":"false"});await e.writeFile(o.join(s,"+handler.js"),a);const c=await x("catch-all-page.marko.template",{CONTENT_TYPE_CLASS:t});await e.writeFile(o.join(s,"+page.marko"),c),i&&console.log(` Generated ${t} catch-all route`)}await P(n,s,i),await generateViteConfig(s.root,i),await F(n,s,i)}export{u as loadMarkdownModule,m as registerMarkdownContent};export{markdownContentPlugin}from"./vite-markdown-plugin.js";
1
+ import{promises as e}from"node:fs";import o from"node:path";import{spawn as t}from"node:child_process";import{fileURLToPath as n}from"node:url";import s from"gray-matter";import{loadConfig as a}from"../config/loader.js";import{getDesignSystem as r,getDarkModeOverride as i}from"../theme/default/design-systems/index.js";import{globalTagValidator as c,formatValidationError as l}from"../markdown/index.js";import{PluginManager as d}from"../plugin/manager.js";import{loadMarkdownModule as u,registerMarkdownContent as m,escapeMarkoText as g}from"./vite-markdown-plugin.js";import{renderMarkdown as f}from"../markdown/renderer.js";import{buildSearchIndex as p}from"../search/index.js";const h=o.dirname(n(import.meta.url)),w=o.resolve(h,"..",".."),k=o.join(w,"src","theme","default"),y=new Set(["@markopress/theme-default","theme-default","default"]);function j(e){return y.has(e)}export function filePathToUrl(e,t){const n=o.relative(t,e).replace(/\.md$/,"").split(o.sep).join("/");return"index"===n?"/":n.endsWith("/index")?"/"+n.replace("/index",""):"/"+n}export async function build(n={}){const{outDir:r,debug:i=!1,useCatchAllRoutes:u,root:m}=n,h=m||process.cwd(),w=[],k=new Map,y=new Map,j=o.join(h,".markopress");let T=h;try{(await e.stat(j)).isDirectory()&&(T=j)}catch{}const v=e=>({start:()=>{y.set(e,performance.now())},end:()=>{const o=y.get(e)||0,t=performance.now()-o;k.set(e,t)}});try{console.log("🚀 Building MarkoPress site...\n");const n=v("Config loading");n.start();const m=await a(h,{mode:"production",command:"build"});let y;if(n.end(),m.plugins&&m.plugins.length>0){console.log("🔌 Loading plugins...");const e=v("Plugin loading");e.start(),y=new d(m),await y.loadPlugins(m.plugins),e.end(),console.log("")}if(y){console.log("📦 Loading plugin content...");const e=v("Plugin loadContent hooks");e.start(),await y.execLoadContentHooks(),e.end(),console.log(" Plugin content loaded\n")}const j={},$=[],P=o.resolve(h,m.contentDir);try{const t=await e.readdir(P,{withFileTypes:!0,recursive:!0}),n=new Map;for(const a of t){if(!a.isFile()||!a.name.endsWith(".md"))continue;const t=o.join(a.path||a.parentPath||P,a.name),r=o.relative(P,t),i=filePathToUrl(t,P),c=r.split(o.sep),l=1===c.length?"root":c[0],d=await e.readFile(t,"utf-8");let u={};try{u=s(d).data}catch{}n.has(l)||n.set(l,[]),n.get(l).push({id:a.name.replace(".md",""),slug:a.name.replace(".md",""),filePath:t,urlPath:i,directory:l,processed:{frontmatter:u}})}for(const[e,t]of n){const n=m.content[e]||{},s=new Map;$.push({id:e,dir:o.join(P,"root"===e?"":e),config:n,features:n,files:t,enhance(e,o){s.set(e,o)},getEnhancement:e=>s.get(e),_enhancements:s})}}catch(e){console.warn("Warning: Could not scan content directory: "+e)}if(y&&$.length>0){console.log("🔌 Enhancing modules with plugin metadata...");const t=v("Module enhancement");t.start();for(const e of $)console.log(` Module: ${e.id} (${e.files.length} files)`),e.files.length>0&&console.log(" First file has processed: "+!!e.files[0].processed);await y.execEnhanceModulesHooks($),t.end(),console.log(` Enhanced ${$.length} module(s)\n`);const n=o.join(T,"src",".generated");await e.mkdir(n,{recursive:!0});const s=(m.site?.base||"/").replace(/\/$/,""),a={};for(const e of $){const o={},t=e._enhancements.entries();for(const[e,n]of t)o[e]=n;Object.keys(o).length>0&&(s&&b(o,s),a[e.id]=o)}const r=o.join(n,"module-enhancements.js"),i=`// Auto-generated by MarkoPress - Do not edit\nexport default ${JSON.stringify(a,null,2)};\n`;await e.writeFile(r,i,"utf-8"),console.log(" Wrote module enhancements to src/.generated/module-enhancements.js\n")}if(!1!==m.search?.enabled){console.log("🔍 Building search index...");const t=v("Search index");t.start();const n=[];for(const o of $)for(const t of o.files)try{const o=await e.readFile(t.filePath,"utf-8"),s=await f(o,m.markdown);n.push({url:t.urlPath,html:s.html,title:s.frontmatter?.title||t.id,frontmatter:s.frontmatter})}catch(e){console.warn(` Warning: Could not index ${t.filePath}:`,e)}try{const t=await p(n,m.search),s=o.join(T,"public","search-index.json");await e.mkdir(o.dirname(s),{recursive:!0}),await e.writeFile(s,t),console.log(` Search index built (${n.length} pages)\n`)}catch(e){console.warn(" Warning: Failed to build search index:",e)}t.end()}if(m.markdown.markoTags?.enabled){const e=o.join(T,m.markdown.markoTags.tagsDir||"src/tags");console.log("🔍 Scanning tags directory...");const t=v("Tag validation setup");t.start(),await c.loadAvailableTags(e),t.end(),console.log(` Found ${c.getAvailableTagsCount()} tags\n`)}else c.reset();const C=o.join(T,"src","routes");await e.mkdir(C,{recursive:!0});let F={};if(y){const e=v("Extend routes hooks");e.start(),F=await y.execExtendRoutesHooks(F),e.end(),console.log("🔌 Extended routes manifest:",Object.keys(F).length)}console.log("📝 Generating routes from content...");const S=v("Route generation");S.start();const x=u??m.build.useCatchAllRoutes;console.log("📄 Pre-rendering markdown to .marko files...");const E=v("Pre-render markdown");E.start();const _=o.join(T,"src",".generated","markdown");await e.mkdir(_,{recursive:!0});const D={};let A=0;for(const t of $){const n=o.join(_,t.id);await e.mkdir(n,{recursive:!0});for(const s of t.files)try{const a=await e.readFile(s.filePath,"utf-8"),r=(m.site?.base||"/").replace(/\/$/,""),i=await f(a,{base:r});let c=i.html.replace(/<span class="line"><\/span>(\s*<\/code>)/g,"$1");const l=`<div class="markdown-content">\n${g(c)}\n</div>`,d=o.join(n,s.slug+".marko");await e.writeFile(d,l),D[`${t.id}/${s.slug}`]={frontmatter:i.frontmatter,headers:i.headers||[]},A++}catch(e){console.warn(` Warning: Failed to pre-render ${t.id}/${s.slug}:`,e)}}const N=o.join(T,"src",".generated","content-metadata.js"),M=`// Auto-generated by MarkoPress - Do not edit\nexport default ${JSON.stringify(D,null,2)};\n`;await e.writeFile(N,M),E.end(),console.log(` Pre-rendered ${A} markdown files\n`),x?(await generateCatchAllRoutes(j,C,m,$,i,!0),console.log(" Using catch-all dynamic routes")):(await generateRoutes(j,C,m,$,i),console.log(" Using static routes")),S.end(),console.log(" Routes generated\n");const O=[];for(const[e,o]of Object.entries(F))(o.handler||o.component)&&(O.push({path:e,...o}),console.log(" Found plugin route: "+e));if(console.log(`🔌 Total manifest routes: ${Object.keys(F).length}, Plugin routes: ${O.length}`),y){const t=[...y.getPluginRoutes(),...O];if(t.length>0){console.log(`🔌 Generating ${t.length} plugin routes...`);const n=v("Plugin route generation");n.start(),await async function(t,n,s,a){for(const s of t){const t=s.path.slice(1),r=o.join(n,t,"+page");if(await e.mkdir(o.dirname(r),{recursive:!0}),s.handler){const t=o.join(o.dirname(r),"+handler.js");await e.writeFile(t,s.handler)}if(s.component){const o=r+".marko";await e.writeFile(o,s.component)}a&&console.log(" Generated plugin route: "+s.path)}}(t,C,0,i),n.end(),console.log(" Plugin routes generated\n")}}console.log("⚙️ Generating Vite config...");const I=v("Vite config generation");if(I.start(),await generateViteConfig(T,i),I.end(),console.log(" Vite config generated\n"),y){console.log("🔌 Processing plugin allContentLoaded hooks...");const e=v("AllContentLoaded hooks");e.start(),await y.execAllContentLoadedHooks(F),e.end(),console.log(" All content processed\n")}if(m.markdown.markoTags?.enabled){console.log("🔍 Validating Marko tags...");const e=v("Tag validation");e.start();const o=c.validate();if(e.end(),!o.success){const e=l(o.missingTags);return console.error(`\n${e}\n`),w.push(e),{success:!1,outDir:"",pages:0,errors:w}}console.log(" All tags validated ✓\n")}console.log("🎨 Copying theme CSS...");const L=v("Theme CSS copy");L.start(),await copyThemeCSS(T,m,i),L.end(),console.log(" Theme CSS copied\n"),console.log("🎨 Extracting styles from Marko components...");const W=v("Marko component styles extraction");W.start(),await extractStylesFromMarkoTags(T,m,i),W.end(),console.log(" Component styles extracted\n");const R=[];for(const e of $)for(const o of e.files)R.push(o.urlPath);for(const e of Object.keys(F))R.includes(e)||R.push(e);const G=o.join(T,"src",".generated","static-urls.json");await e.mkdir(o.dirname(G),{recursive:!0}),await e.writeFile(G,JSON.stringify(R,null,2)),i&&console.log(` Generated static URL manifest: ${R.length} URLs`),console.log("🔨 Building with @marko/run...");const U=v("@marko/run build");U.start();const B=r||m.build.outDir,V=await async function(e,n,s){return new Promise(a=>{const r=["build"];e&&r.push("--output",e),n&&r.push("--debug");const i=t("npx",["marko-run",...r],{stdio:"inherit",cwd:s});i.on("close",t=>{if(0===t){const t=e||"dist";a({success:!0,outDir:o.join(s,t),errors:[]})}else a({success:!1,outDir:"",errors:["Build process exited with code "+t]})}),i.on("error",e=>{a({success:!1,outDir:"",errors:["Failed to start build process: "+e.message]})})})}(B,i,T);if(U.end(),!V.success)return w.push(...V.errors),{success:!1,outDir:"",pages:0,errors:w};const H=v("Collect build assets");H.start();const Y=await async function(o){const t=[];try{const n=await e.readdir(o,{recursive:!0});for(const e of n)"string"==typeof e&&(e.endsWith(".js")||e.endsWith(".css")||e.endsWith(".json"))&&t.push(e)}catch(e){console.warn("Warning: Could not collect build assets:",e)}return t}(V.outDir);if(H.end(),y){console.log("🔌 Processing plugin postBuild hooks...");const e=v("Post-build hooks");e.start(),await y.execPostBuildHooks(V.outDir,F,Y),e.end(),console.log(" Post-build hooks completed\n")}console.log("📦 Copying Marko tags directory...");const q=v("Copy tags directory");q.start(),await copyTagsDirectory(h,V.outDir,m,i),q.end(),console.log(" Tags directory copied\n"),console.log("\n✅ Build completed successfully!"),console.log(" Output: "+V.outDir),console.log(" Pages: Generated dynamically at request time"),console.log("\n⏱️ Build timing:");const z=Array.from(k.entries()).sort((e,o)=>o[1]-e[1]);for(const[e,o]of z){const t=(o/1e3).toFixed(2);console.log(` ${"█".repeat(Math.min(Math.floor(o/100),20))} ${e}: ${t}s`)}return{success:!0,outDir:V.outDir,pages:0,errors:w}}catch(e){const o=e instanceof Error?e.message:e+"";return w.push(o),console.error("\n❌ Build failed:",o),{success:!1,outDir:"",pages:0,errors:w}}}function b(e,o){Array.isArray(e.sidebar)&&(e.sidebar=e.sidebar.map(e=>({...e,items:Array.isArray(e.items)?e.items.map(e=>({...e,link:e.link&&!e.link.startsWith(o)?o+e.link:e.link})):e.items}))),Array.isArray(e.blogPosts)&&(e.blogPosts=e.blogPosts.map(e=>({...e,link:e.link&&!e.link.startsWith(o)?o+e.link:e.link})))}export async function generateRoutes(e,t,n,s,a){await cleanupGeneratedRoutes(t,e,a);const r=[];let i=0,c=0,l=0;for(const[o,t]of Object.entries(e)){if(!Array.isArray(t))continue;r.push(o);const e=t;if("pages"===o)for(const o of e)await v(0,0,0,0,a),i++;else if("blog"===o)for(const o of e)await P(0,0,0,0,a),l++;else for(const o of e)await $(0,0,0,0,a),c++}await C(t,n,a);const d=o.resolve(t,"..","..");await generateViteConfig(d,a),await F(t,n,a),a&&(console.log(` Generated ${i} page routes`),console.log(` Generated ${c} doc routes`),console.log(` Generated ${l} blog routes`))}export async function cleanupGeneratedRoutes(t,n,s){const a=[],r=["+layout.marko","+middleware.js","components/**/*","api/**/*","lib/**/*"],i=Object.keys(n).filter(e=>"pages"!==e).map(e=>e+"/");try{const n=await e.readdir(t,{recursive:!0,withFileTypes:!0});for(const c of n){if(!c.isFile())continue;const n=o.join(c.path||c.parentPath||t,c.name),l=o.relative(t,n);if(i.some(e=>l.startsWith(e)))if(r.some(e=>e.includes("**")?RegExp(e.replace(/\*\*/g,".*").replace(/\*/g,"[^/]*")).test(l):c.name===e))s&&console.log(" Preserving: "+l);else try{await e.unlink(n),s&&console.log(" Deleted: "+l)}catch(e){if("ENOENT"!==e.code){const o=e instanceof Error?e.message:e+"";a.push(`Failed to delete ${l}: ${o}`)}}}for(const e of i){const n=o.join(t,e);try{await T(n)}catch{}}a.length>0&&(console.warn("⚠️ Cleanup warnings:"),a.forEach(e=>console.warn(" "+e)))}catch(e){if("ENOENT"!==e.code)throw e}}async function T(t){try{const n=await e.readdir(t,{withFileTypes:!0});for(const e of n)if(e.isDirectory()){const n=o.join(t,e.name);await T(n)}0===(await e.readdir(t)).length&&await e.rmdir(t)}catch{}}async function v(e,o,t,n,s){s&&console.log(" Warning: Static routes deprecated, use catch-all routes")}async function $(e,o,t,n,s){s&&console.log(" Warning: Static routes deprecated, use catch-all routes")}async function P(e,o,t,n,s){s&&console.log(" Warning: Static routes deprecated, use catch-all routes")}async function C(t,n,s){const a=o.join(t,"_config.js"),r={root:n.root,contentDir:n.contentDir,site:{title:n.site?.title||"MarkoPress",description:n.site?.description||"",lang:n.site?.lang||"en-US",head:n.site?.head||[],base:n.site?.base||"/"},content:n.content,theme:{name:n.theme?.name||"@markopress/theme-default",options:n.theme?.options||{}},markdown:n.markdown||{},build:n.build||{}},i=(n.site?.base||"/").replace(/\/$/,"");i&&r.theme.options?.navbar&&(r.theme.options.navbar=r.theme.options.navbar.map(e=>({...e,link:e.link&&!e.link.startsWith(i)?i+e.link:e.link})));const c=`// Auto-generated by MarkoPress - Do not edit\nexport const config = ${JSON.stringify(r,null,2)};\n`;await e.writeFile(a,c),s&&console.log(" Generated: "+a)}async function F(t,n,s){const a=o.join(t,"+layout.marko"),r=(n.theme,n.site?.title||"MarkoPress"),i=n.theme?.options?.style||"default",c=n.site?.base?.replace(/\/$/,"")||"",l=n.markdown?.markoTags?.enabled?`<link rel="stylesheet" href="${c}/markopress-components.css">`:"",d=await S("layout.marko.template",{SITE_TITLE:r,THEME_STYLE:i,BASE_PATH:c,COMPONENT_STYLES_LINK:l});await e.writeFile(a,d),s&&console.log(" Generated: "+a)}async function S(t,n){const s=o.dirname(new URL(import.meta.url).pathname),a=o.join(s,"..",".."),r=o.join(a,"templates",t);let i=await e.readFile(r,"utf-8");i="true"===n.IS_BUILD?i.replace(/\/\/ \{\{IF_DEV_START\}\}[\s\S]*?\/\/ \{\{IF_DEV_END\}\}/g,""):i.replace(/\/\/ \{\{IF_BUILD_START\}\}[\s\S]*?\/\/ \{\{IF_BUILD_END\}\}/g,""),i=i.replace(/\/\/ \{\{IF_(?:BUILD|DEV)_(?:START|END)\}\}\n?/g,"");for(const[e,o]of Object.entries(n))i=i.replace(RegExp(`\\{\\{${e}\\}\\}`,"g"),o);return i}export function validateThemeName(e){if(!/^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/.test(e))throw Error(`Invalid theme name: "${e}". Must be a valid npm package name (e.g., "my-theme" or "@org/my-theme")`);if(e.includes(".."))throw Error(`Theme name cannot contain traversal sequences (..): "${e}"`);if(e.includes("\\"))throw Error(`Theme name cannot contain backslashes: "${e}"`);if((e.match(/\//g)||[]).length>1)throw Error(`Theme name can only contain one forward slash (for scoped packages): "${e}"`);if(o.isAbsolute(e))throw Error(`Theme name cannot be an absolute path: "${e}"`)}export async function generateViteConfig(t,n){const s=o.join(t,"vite.config.js");try{if((await e.readFile(s,"utf-8")).includes("markdownContentPlugin"))return void(n&&console.log(" Vite config already has markdownContentPlugin"))}catch{}await e.writeFile(s,"import { defineConfig } from 'vite';\nimport marko from '@marko/run/vite';\nimport { markdownContentPlugin } from 'markopress/build';\n\nexport default defineConfig({\n plugins: [\n marko(),\n markdownContentPlugin(),\n ],\n resolve: {\n // Preserve symlinks for pnpm workspace compatibility\n // This allows Marko to properly discover tags from symlinked packages\n preserveSymlinks: true,\n },\n build: {\n outDir: 'dist',\n },\n});\n"),n&&console.log(" Created vite.config.js with markdownContentPlugin")}export async function copyThemeCSS(t,n,s){const a=o.join(t,"public","_markopress","theme");await e.mkdir(a,{recursive:!0});const r=n.theme?.name||"@markopress/theme-default";try{validateThemeName(r)}catch(e){const o=e instanceof Error?e.message:e+"";throw Error("Security: "+o)}const i=n.theme?.options?.style||"default",c=`theme-${i}.css`,l=[...j(r)?[o.join(k,"public",c)]:[],o.join(t,"..","node_modules",r,"public",c),o.join(t,"node_modules",r,"public",c)];let d=null,u=null;for(const o of l)try{await e.access(o),d=await e.readFile(o,"utf-8"),u=o;break}catch{}if(!d){console.warn(` Warning: Could not find ${c}, using minimal fallback`);const t=`/* Minimal fallback CSS for style: ${i} */\nbody { font-family: system-ui, sans-serif; margin: 0; padding: 0; }`,n=o.join(a,c);return void await e.writeFile(n,t)}const m=o.join(a,c);await e.writeFile(m,d),s&&(console.log(` Copied ${c} from: ${u}`),console.log(" Output: "+m));const g="styles.css",f=[...j(r)?[o.join(k,g)]:[],o.join(t,"..","node_modules",r,"src",g),o.join(t,"node_modules",r,"src",g)];for(const t of f)try{await e.access(t);const n=await e.readFile(t,"utf-8"),r=o.join(a,g);await e.writeFile(r,n),s&&(console.log(` Copied ${g} from: ${t}`),console.log(" Output: "+r));break}catch{}}export async function extractStylesFromMarkoTags(t,n,s){const a=n.markdown?.markoTags?.tagsDir||"src/tags",r=o.join(t,a),i=o.join(t,"public");await e.mkdir(i,{recursive:!0});const c=o.join(i,"markopress-components.css");if(!n.markdown?.markoTags?.enabled){s&&console.log(" Marko tags not enabled, skipping style extraction");try{await e.unlink(c)}catch{}return}try{await e.access(r)}catch{return void(s&&console.log(" No tags directory found at: "+r))}const l=[];if(await async function t(n){const s=await e.readdir(n,{withFileTypes:!0});for(const e of s){const s=o.join(n,e.name);e.isDirectory()?await t(s):e.isFile()&&e.name.endsWith(".marko")&&l.push(s)}}(r),0===l.length)return void(s&&console.log(" No .marko files found in: "+r));const d=[];d.push("/* Custom markdown tag styles"),d.push(" * Loaded globally because request-time virtual markdown modules"),d.push(" * do not emit tag-local CSS assets reliably. */"),d.push("");for(const t of l){const n=o.relative(r,t),s=""===o.dirname(n)?o.basename(n,".marko"):o.join(o.dirname(n),o.basename(n,".marko"));try{const o=await e.readFile(t,"utf-8"),n=/<style\b[^>]*>([\s\S]*?)<\/style>/gi,a=Array.from(o.matchAll(n));if(a.length>0){d.push(`/* ${s}.marko */`);for(const e of a){const o=e[1]||"";if(o){const e=o.split("\n");let t=0;for(;t<e.length&&""===e[t].trim();)t++;let n=e.length-1;for(;n>=t&&""===e[n].trim();)n--;for(let o=t;o<=n;o++){const t=e[o];if(""===t.trim()){d.push("");continue}const n=t.match(/^(\s*)/),s=n?n[1].length:0,a=" ".repeat(Math.floor(s/2)),r=t.trim().replace(/:global\(([^)]+)\)/g,"$1");d.push(a+r)}}}d.push("")}}catch(e){console.warn(` Warning: Could not read file ${t}:`,e)}}const u=d.join("\n");await e.writeFile(c,u),s&&(console.log(` Extracted styles from ${l.length} Marko component(s)`),console.log(" Output: "+c))}async function x(t){const n=await e.readdir(t,{withFileTypes:!0}),s=await Promise.all(n.map(e=>{const n=o.resolve(t,e.name);return e.isDirectory()?x(n):n}));return Array.prototype.concat(...s).filter(e=>e.endsWith(".marko"))}export async function copyThemeComponents(t,n,s){const a=n.theme?.name||"@markopress/theme-default",r=o.join(t,"src"),i=o.join(r,"tags");await e.mkdir(i,{recursive:!0});const c=[...j(a)?[o.join(k,"tags")]:[],o.join(t,"..","node_modules",a,"dist","tags"),o.join(t,"node_modules",a,"dist","tags"),o.join(t,"..","node_modules",a,"src","components"),o.join(t,"node_modules",a,"src","components")];let l=null;for(const o of c)try{await e.access(o),l=o;break}catch{}if(!l)return void(s&&console.warn(" Warning: Could not find theme components, skipping"));const d=await x(l);let u=0;for(const t of d){const n=o.relative(l,t),a=o.join(i,n);let r=!1;try{await e.access(a),r=!0}catch{}r?s&&console.log(" Skipped component (user override exists): "+n):(await e.mkdir(o.dirname(a),{recursive:!0}),await e.copyFile(t,a),u++)}s&&(console.log(` Copied ${u} theme components from: ${l}`),console.log(" Output: "+i))}export async function copyTagsDirectory(t,n,s,a){const r=s.markdown?.markoTags?.tagsDir||"src/tags",i=o.join(t,r),c=o.join(n,"tags");try{await e.access(i)}catch{return void(a&&console.log(" No tags directory found at: "+i))}await e.mkdir(c,{recursive:!0});const l=await e.readdir(i,{withFileTypes:!0});let d=0;for(const t of l){const n=o.join(i,t.name),s=o.join(c,t.name);if(t.isDirectory()){await e.mkdir(s,{recursive:!0});const t=await e.readdir(n,{withFileTypes:!0});for(const a of t){const t=o.join(n,a.name),r=o.join(s,a.name);a.isDirectory()||(await e.copyFile(t,r),d++)}}else t.isFile()&&(await e.copyFile(n,s),d++)}a&&(console.log(` Copied ${d} tag files from: ${i}`),console.log(" Output: "+c))}export async function generateCatchAllRoutes(t,n,s,a,r,i=!0){console.log(" Using catch-all dynamic routes..."),console.log(" Mode: "+(i?"build (pre-compiled)":"dev (request-time rendering)"));const c=Object.keys(s.content||{}),l=new Set(a.map(e=>e.id)),d=[...new Set([...c,...l])];if(a.some(e=>"root"===e.id)){const t=o.join(n,"$$slug");await e.mkdir(t,{recursive:!0});const s=await S("catch-all-handler.js.template",{CONTENT_TYPE:"root",CONFIG_PATH:"../_config.js",VITE_PLUGIN_PATH:"markopress/build",IS_BUILD:i?"true":"false"});await e.writeFile(o.join(t,"+handler.js"),s);const a=await S("catch-all-page.marko.template",{CONTENT_TYPE_CLASS:"page"});await e.writeFile(o.join(t,"+page.marko"),a),r&&console.log(" Generated root pages catch-all route")}for(const t of d){if("root"===t)continue;const s=o.join(n,t,"$$slug");await e.mkdir(s,{recursive:!0});const a=await S("catch-all-handler.js.template",{CONTENT_TYPE:t,CONFIG_PATH:"../../_config.js",VITE_PLUGIN_PATH:"markopress/build",IS_BUILD:i?"true":"false"});await e.writeFile(o.join(s,"+handler.js"),a);const c=await S("catch-all-page.marko.template",{CONTENT_TYPE_CLASS:t});await e.writeFile(o.join(s,"+page.marko"),c),r&&console.log(` Generated ${t} catch-all route`)}await C(n,s,r);const u=o.resolve(n,"..","..");await generateViteConfig(u,r),await F(n,s,r)}export{u as loadMarkdownModule,m as registerMarkdownContent};export{markdownContentPlugin}from"./vite-markdown-plugin.js";
package/dist/cli/index.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import{Command as o}from"commander";import{resolveAppRoot as t}from"../config/app-root.js";import{startDevServer as e}from"../dev/index.js";import{build as r}from"../build/index.js";import{preview as s}from"../preview/index.js";const i=t(),n=new o;n.name("markopress").description("A general-purpose static site generator using Marko.js v6").version("0.1.0"),n.command("dev").description("Start development server").option("-p, --port <port>","Port to run server on","3000").option("--host <host>","Host to run server on","localhost").option("--open","Open browser automatically",!1).option("--catch-all","Use catch-all dynamic routes").action(async o=>{await e({port:parseInt(o.port),host:o.host,open:o.open,useCatchAllRoutes:o.catchAll,root:i})}),n.command("build").description("Build for production").option("--debug","Debug mode",!1).option("-o, --output <dir>","Output directory","dist").option("--catch-all","Use catch-all dynamic routes").action(async o=>{const t=await r({debug:o.debug,outDir:o.output,useCatchAllRoutes:o.catchAll,root:i});t.success||(console.error("\n❌ Build failed!"),t.errors.forEach(o=>console.error(" "+o)),process.exit(1))}),n.command("preview").description("Preview production build").option("-p, --port <port>","Port to run server on","4173").option("--host <host>","Host to run server on","localhost").action(async o=>{await s({port:parseInt(o.port),host:o.host,root:i})}),n.command("init [site-dir]").description("Create a new MarkoPress site").option("-t, --template <template>","Template to use","default").action(async(o,t)=>{console.log(`Initializing MarkoPress site in ${o||"."}...`),console.log("Template: "+t.template)}),n.parse();
2
+ import o from"node:path";import{Command as t}from"commander";import{resolveAppRoot as e}from"../config/app-root.js";import{startDevServer as r}from"../dev/index.js";import{build as s}from"../build/index.js";import{preview as i}from"../preview/index.js";const n=e(),a=n.endsWith(".markopress")||n.endsWith(".markopress/")?o.dirname(n):n,p=new t;p.name("markopress").description("A general-purpose static site generator using Marko.js v6").version("0.1.0"),p.command("dev").description("Start development server").option("-p, --port <port>","Port to run server on","3000").option("--host <host>","Host to run server on","localhost").option("--open","Open browser automatically",!1).option("--catch-all","Use catch-all dynamic routes").action(async o=>{await r({port:parseInt(o.port),host:o.host,open:o.open,useCatchAllRoutes:o.catchAll,root:a})}),p.command("build").description("Build for production").option("--debug","Debug mode",!1).option("-o, --output <dir>","Output directory","dist").option("--catch-all","Use catch-all dynamic routes").action(async o=>{const t=await s({debug:o.debug,outDir:o.output,useCatchAllRoutes:o.catchAll,root:a});t.success||(console.error("\n❌ Build failed!"),t.errors.forEach(o=>console.error(" "+o)),process.exit(1))}),p.command("preview").description("Preview production build").option("-p, --port <port>","Port to run server on","4173").option("--host <host>","Host to run server on","localhost").action(async o=>{await i({port:parseInt(o.port),host:o.host,root:a})}),p.command("init [site-dir]").description("Create a new MarkoPress site").option("-t, --template <template>","Template to use","default").action(async(o,t)=>{console.log(`Initializing MarkoPress site in ${o||"."}...`),console.log("Template: "+t.template)}),p.parse();
@@ -1 +1 @@
1
- import o from"node:path";import e from"node:fs/promises";import{pathToFileURL as t}from"node:url";import{ZodError as n}from"zod";import{validateConfigSafe as r}from"./validation.js";const i={site:{title:"MarkoPress Site",description:"",base:"/",lang:"en-US",head:[]},content:{pages:"../content/pages",blog:"../content/blog"},theme:{name:"@markopress/theme-default",options:{}},markdown:{lineNumbers:!1,theme:{light:"github-light",dark:"github-dark"}},build:{useCatchAllRoutes:!1,outDir:"dist",assetsDir:"assets"},search:{enabled:!0},plugins:[]};function s(o,e){const t={...o};for(const o in e){const n=e[o],r=t[o];void 0!==n&&(a(r)&&a(n)?t[o]=s(r,n):t[o]=n)}return t}function a(o){return null!==o&&"object"==typeof o&&!Array.isArray(o)}function c(o){const e=r(o);return e.success?{valid:!0,errors:[],data:e.data}:{valid:!1,errors:e.errors.map(o=>`${o.path}: ${o.message}`)}}export async function loadConfigFromFile(n,r){const i=["config.ts","config.js","config.mjs",".markopress/config.ts",".markopress/config.js",".markopress/config.mjs"];for(const s of i){const i=o.resolve(n,s);try{await e.access(i);const o=`${t(i).href}?t=${Date.now()}`,n=(await import(o)).default;if(!n)throw Error(`Config file ${s} must have a default export`);let a;a="function"==typeof n?await n(r):n;const f=c(a);if(!f.valid)throw Error(`Invalid configuration in ${s}:\n${f.errors.map(o=>" - "+o).join("\n")}`);return console.log("✓ Loaded config from "+s),{file:i,config:f.data}}catch(o){if(o&&"object"==typeof o&&"code"in o&&"ENOENT"!==o.code)throw Error(`Failed to load config from ${s}: ${o instanceof Error?o.message:o+""}`)}}return null}export function resolveConfig(o,e){const t=o.content?{...o.content}:i.content,n=o.build?{...o.build}:i.build,r=s(i.site,o.site||{}),a=s(i.theme,o.theme||{}),c=s(i.markdown,o.markdown||{}),f=s(i.search,o.search||{}),l=o.plugins||i.plugins,d=a.options?.style||"default",m=["link",{rel:"stylesheet",href:`${(r.base||"/").replace(/\/$/,"")}/_markopress/theme/theme-${d}.css`}];return r.head=[...r.head||[],m],{root:e,site:r,content:t,theme:a,markdown:c,build:n,search:f,plugins:l}}export async function loadConfig(o=process.cwd(),e={mode:"development",command:"dev"}){const t=await loadConfigFromFile(o,e);return resolveConfig(t?t.config:{site:i.site},o)}export function defineConfig(o){return o}export function defineConfigWithCallback(o){return o}
1
+ import o from"node:path";import e from"node:fs/promises";import{pathToFileURL as t}from"node:url";import{ZodError as n}from"zod";import{validateConfigSafe as r}from"./validation.js";const i={site:{title:"MarkoPress Site",description:"",base:"/",lang:"en-US",head:[]},contentDir:"content",content:{docs:{sidebar:!0},blog:{rss:!0,list:!0}},theme:{name:"@markopress/theme-default",options:{}},markdown:{lineNumbers:!1,theme:{light:"github-light",dark:"github-dark"}},build:{useCatchAllRoutes:!0,outDir:"dist",assetsDir:"assets"},search:{enabled:!0},plugins:[]};function s(o,e){const t={...o};for(const o in e){const n=e[o],r=t[o];void 0!==n&&(a(r)&&a(n)?t[o]=s(r,n):t[o]=n)}return t}function a(o){return null!==o&&"object"==typeof o&&!Array.isArray(o)}function c(o){const e=r(o);return e.success?{valid:!0,errors:[],data:e.data}:{valid:!1,errors:e.errors.map(o=>`${o.path}: ${o.message}`)}}export async function loadConfigFromFile(n,r){const i=["config.ts","config.js","config.mjs",".markopress/config.ts",".markopress/config.js",".markopress/config.mjs"];for(const s of i){const i=o.resolve(n,s);try{await e.access(i);const o=`${t(i).href}?t=${Date.now()}`,n=(await import(o)).default;if(!n)throw Error(`Config file ${s} must have a default export`);let a;a="function"==typeof n?await n(r):n;const f=c(a);if(!f.valid)throw Error(`Invalid configuration in ${s}:\n${f.errors.map(o=>" - "+o).join("\n")}`);return console.log("✓ Loaded config from "+s),{file:i,config:f.data}}catch(o){if(o&&"object"==typeof o&&"code"in o&&"ENOENT"!==o.code)throw Error(`Failed to load config from ${s}: ${o instanceof Error?o.message:o+""}`)}}return null}export function resolveConfig(o,e){const t=o.content?{...o.content}:i.content,n=o.build?{...o.build}:i.build,r=s(i.site,o.site||{}),a=s(i.theme,o.theme||{}),c=s(i.markdown,o.markdown||{}),f=s(i.search,o.search||{}),l=o.plugins||i.plugins,d=a.options?.style||"default",m=["link",{rel:"stylesheet",href:`${(r.base||"/").replace(/\/$/,"")}/_markopress/theme/theme-${d}.css`}];return r.head=[...r.head||[],m],{root:e,site:r,contentDir:o.contentDir??i.contentDir,content:t,theme:a,markdown:c,build:n,search:f,plugins:l}}export async function loadConfig(o=process.cwd(),e={mode:"development",command:"dev"}){const t=await loadConfigFromFile(o,e);return resolveConfig(t?t.config:{site:i.site},o)}export function defineConfig(o){return o}export function defineConfigWithCallback(o){return o}
@@ -35,6 +35,16 @@ export interface ModuleOptions {
35
35
  /** Module type override */
36
36
  type?: 'page' | 'doc' | 'blog' | 'custom';
37
37
  }
38
+ export interface ContentFeatureOptions {
39
+ sidebar?: boolean;
40
+ rss?: boolean;
41
+ list?: boolean;
42
+ toc?: boolean;
43
+ }
44
+ export type ContentDirectoryConfig = ContentFeatureOptions;
45
+ export interface NewContentConfig {
46
+ [directory: string]: ContentDirectoryConfig;
47
+ }
38
48
  /**
39
49
  * Content module configuration
40
50
  *
@@ -139,7 +149,8 @@ export interface SearchConfig {
139
149
  }
140
150
  export interface MarkoPressConfig {
141
151
  site: SiteConfig;
142
- content?: ContentConfig;
152
+ contentDir?: string;
153
+ content?: NewContentConfig;
143
154
  theme?: ThemeConfig;
144
155
  markdown?: MarkdownConfig;
145
156
  build?: BuildConfig;
@@ -152,9 +163,10 @@ export interface PluginConfig {
152
163
  }
153
164
  export interface UserConfig extends MarkoPressConfig {
154
165
  }
155
- export interface ResolvedConfig extends Required<MarkoPressConfig> {
166
+ export interface ResolvedConfig extends Required<Omit<MarkoPressConfig, 'contentDir'>> {
156
167
  root: string;
157
- content: ContentConfig;
168
+ contentDir: string;
169
+ content: NewContentConfig;
158
170
  build: BuildConfig;
159
171
  }
160
172
  export type ConfigFn = (env: ConfigEnv) => UserConfig | Promise<UserConfig>;
@@ -14,11 +14,14 @@ export declare const MarkoPressConfigSchema: z.ZodObject<{
14
14
  lang: z.ZodOptional<z.ZodString>;
15
15
  head: z.ZodOptional<z.ZodArray<z.ZodTuple<[z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>], null>>>;
16
16
  }, z.core.$strip>;
17
- content: z.ZodOptional<z.ZodObject<{
18
- pages: z.ZodOptional<z.ZodString>;
19
- docs: z.ZodOptional<z.ZodString>;
20
- blog: z.ZodOptional<z.ZodString>;
21
- }, z.core.$loose>>;
17
+ contentDir: z.ZodOptional<z.ZodString>;
18
+ content: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
19
+ dir: z.ZodOptional<z.ZodString>;
20
+ sidebar: z.ZodOptional<z.ZodBoolean>;
21
+ toc: z.ZodOptional<z.ZodBoolean>;
22
+ rss: z.ZodOptional<z.ZodBoolean>;
23
+ list: z.ZodOptional<z.ZodBoolean>;
24
+ }, z.core.$loose>]>>>;
22
25
  theme: z.ZodOptional<z.ZodObject<{
23
26
  name: z.ZodOptional<z.ZodString>;
24
27
  designSystem: z.ZodOptional<z.ZodEnum<{
@@ -45,6 +48,10 @@ export declare const MarkoPressConfigSchema: z.ZodObject<{
45
48
  light: z.ZodOptional<z.ZodString>;
46
49
  dark: z.ZodOptional<z.ZodString>;
47
50
  }, z.core.$strip>>;
51
+ markoTags: z.ZodOptional<z.ZodObject<{
52
+ enabled: z.ZodOptional<z.ZodBoolean>;
53
+ tagsDir: z.ZodOptional<z.ZodString>;
54
+ }, z.core.$strip>>;
48
55
  }, z.core.$strip>>;
49
56
  build: z.ZodOptional<z.ZodObject<{
50
57
  useCatchAllRoutes: z.ZodOptional<z.ZodBoolean>;
@@ -54,6 +61,9 @@ export declare const MarkoPressConfigSchema: z.ZodObject<{
54
61
  minify: z.ZodOptional<z.ZodBoolean>;
55
62
  clean: z.ZodOptional<z.ZodBoolean>;
56
63
  }, z.core.$loose>>;
64
+ search: z.ZodOptional<z.ZodObject<{
65
+ enabled: z.ZodOptional<z.ZodBoolean>;
66
+ }, z.core.$loose>>;
57
67
  plugins: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodTuple<[z.ZodString, z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>], null>, z.ZodObject<{
58
68
  name: z.ZodString;
59
69
  options: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
@@ -73,12 +83,15 @@ export declare function validateConfig(config: unknown): {
73
83
  lang?: string;
74
84
  head?: [string?, Record<string, string>?, ...unknown[]][];
75
85
  };
76
- content?: {
86
+ contentDir?: string;
87
+ content?: Record<string, string | {
77
88
  [x: string]: unknown;
78
- pages?: string;
79
- docs?: string;
80
- blog?: string;
81
- };
89
+ dir?: string;
90
+ sidebar?: boolean;
91
+ toc?: boolean;
92
+ rss?: boolean;
93
+ list?: boolean;
94
+ }>;
82
95
  theme?: {
83
96
  name?: string;
84
97
  designSystem?: "vitepress" | "docusaurus" | "rspress";
@@ -102,6 +115,10 @@ export declare function validateConfig(config: unknown): {
102
115
  light?: string;
103
116
  dark?: string;
104
117
  };
118
+ markoTags?: {
119
+ enabled?: boolean;
120
+ tagsDir?: string;
121
+ };
105
122
  };
106
123
  build?: {
107
124
  [x: string]: unknown;
@@ -112,6 +129,10 @@ export declare function validateConfig(config: unknown): {
112
129
  minify?: boolean;
113
130
  clean?: boolean;
114
131
  };
132
+ search?: {
133
+ [x: string]: unknown;
134
+ enabled?: boolean;
135
+ };
115
136
  plugins?: (string | [string?, Record<string, unknown>?, ...unknown[]] | {
116
137
  name: string;
117
138
  options?: Record<string, unknown>;
@@ -139,12 +160,15 @@ export declare function validateConfigSafe(config: unknown): {
139
160
  lang?: string;
140
161
  head?: [string?, Record<string, string>?, ...unknown[]][];
141
162
  };
142
- content?: {
163
+ contentDir?: string;
164
+ content?: Record<string, string | {
143
165
  [x: string]: unknown;
144
- pages?: string;
145
- docs?: string;
146
- blog?: string;
147
- };
166
+ dir?: string;
167
+ sidebar?: boolean;
168
+ toc?: boolean;
169
+ rss?: boolean;
170
+ list?: boolean;
171
+ }>;
148
172
  theme?: {
149
173
  name?: string;
150
174
  designSystem?: "vitepress" | "docusaurus" | "rspress";
@@ -168,6 +192,10 @@ export declare function validateConfigSafe(config: unknown): {
168
192
  light?: string;
169
193
  dark?: string;
170
194
  };
195
+ markoTags?: {
196
+ enabled?: boolean;
197
+ tagsDir?: string;
198
+ };
171
199
  };
172
200
  build?: {
173
201
  [x: string]: unknown;
@@ -178,6 +206,10 @@ export declare function validateConfigSafe(config: unknown): {
178
206
  minify?: boolean;
179
207
  clean?: boolean;
180
208
  };
209
+ search?: {
210
+ [x: string]: unknown;
211
+ enabled?: boolean;
212
+ };
181
213
  plugins?: (string | [string?, Record<string, unknown>?, ...unknown[]] | {
182
214
  name: string;
183
215
  options?: Record<string, unknown>;
@@ -1 +1 @@
1
- import{z as e}from"zod";const t=e.object({title:e.string().min(1,{message:"Site title is required"}).max(100,{message:"Site title too long"}),description:e.string().max(500,{message:"Description too long"}).optional(),base:e.string().startsWith("/",{message:"Base must start with /"}).optional(),lang:e.string().regex(/^[a-z]{2}(-[A-Z]{2})?$/,{message:"Invalid language code"}).optional(),head:e.array(e.tuple([e.string(),e.record(e.string(),e.string())])).optional()}),o=e.object({pages:e.string().optional(),docs:e.string().optional(),blog:e.string().optional()}).passthrough(),n=e.object({text:e.string().min(1,{message:"Nav item text is required"}),link:e.string().min(1,{message:"Nav item link is required"})}),i=e.object({text:e.string().min(1,{message:"Sidebar item text is required"}),link:e.string().min(1,{message:"Sidebar item link is required"})}),a=e.record(e.string(),e.union([e.array(i),e.object({autoGenerate:e.boolean()})])),s=e.object({navbar:e.array(n).optional(),sidebar:a.optional()}).passthrough(),r=e.object({name:e.string().regex(/^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/,{message:"Invalid theme name"}).refine(e=>!e.includes(".."),{message:"Theme name cannot contain path traversal"}).optional(),designSystem:e.enum(["vitepress","docusaurus","rspress"]).optional(),options:s.optional()}),l=e.object({lineNumbers:e.boolean().optional(),theme:e.object({light:e.string().optional(),dark:e.string().optional()}).optional()}),g=e.object({useCatchAllRoutes:e.boolean().optional(),outDir:e.string().optional(),assetsDir:e.string().optional(),sourcemap:e.boolean().optional(),minify:e.boolean().optional(),clean:e.boolean().optional()}).passthrough(),p=e.union([e.string(),e.tuple([e.string(),e.record(e.string(),e.unknown()).optional()]),e.object({name:e.string().min(1,{message:"Plugin name is required"}),options:e.record(e.string(),e.unknown()).optional()})]);export const MarkoPressConfigSchema=e.object({site:t,content:o.optional(),theme:r.optional(),markdown:l.optional(),build:g.optional(),plugins:e.array(p).optional()});export function validateConfig(e){return MarkoPressConfigSchema.parse(e)}export function validateConfigSafe(e){const t=MarkoPressConfigSchema.safeParse(e);return t.success?{success:!0,data:t.data}:{success:!1,errors:t.error.issues.map(e=>({path:e.path.join("."),message:e.message}))}}
1
+ import{z as o}from"zod";const e=o.object({title:o.string().min(1,{message:"Site title is required"}).max(100,{message:"Site title too long"}),description:o.string().max(500,{message:"Description too long"}).optional(),base:o.string().startsWith("/",{message:"Base must start with /"}).optional(),lang:o.string().regex(/^[a-z]{2}(-[A-Z]{2})?$/,{message:"Invalid language code"}).optional(),head:o.array(o.tuple([o.string(),o.record(o.string(),o.string())])).optional()}),t=o.union([o.string(),o.object({dir:o.string().optional(),sidebar:o.boolean().optional(),toc:o.boolean().optional(),rss:o.boolean().optional(),list:o.boolean().optional()}).passthrough()]),n=o.record(o.string(),t),a=o.object({text:o.string().min(1,{message:"Nav item text is required"}),link:o.string().min(1,{message:"Nav item link is required"})}),i=o.object({text:o.string().min(1,{message:"Sidebar item text is required"}),link:o.string().min(1,{message:"Sidebar item link is required"})}),s=o.record(o.string(),o.union([o.array(i),o.object({autoGenerate:o.boolean()})])),r=o.object({navbar:o.array(a).optional(),sidebar:s.optional()}).passthrough(),l=o.object({name:o.string().regex(/^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/,{message:"Invalid theme name"}).refine(o=>!o.includes(".."),{message:"Theme name cannot contain path traversal"}).optional(),designSystem:o.enum(["vitepress","docusaurus","rspress"]).optional(),options:r.optional()}),g=o.object({lineNumbers:o.boolean().optional(),theme:o.object({light:o.string().optional(),dark:o.string().optional()}).optional(),markoTags:o.object({enabled:o.boolean().optional(),tagsDir:o.string().optional()}).optional()}),p=o.object({useCatchAllRoutes:o.boolean().optional(),outDir:o.string().optional(),assetsDir:o.string().optional(),sourcemap:o.boolean().optional(),minify:o.boolean().optional(),clean:o.boolean().optional()}).passthrough(),m=o.union([o.string(),o.tuple([o.string(),o.record(o.string(),o.unknown()).optional()]),o.object({name:o.string().min(1,{message:"Plugin name is required"}),options:o.record(o.string(),o.unknown()).optional()})]);export const MarkoPressConfigSchema=o.object({site:e,contentDir:o.string().optional(),content:n.optional(),theme:l.optional(),markdown:g.optional(),build:p.optional(),search:o.object({enabled:o.boolean().optional()}).passthrough().optional(),plugins:o.array(m).optional()});export function validateConfig(o){return MarkoPressConfigSchema.parse(o)}export function validateConfigSafe(o){const e=MarkoPressConfigSchema.safeParse(o);return e.success?{success:!0,data:e.data}:{success:!1,errors:e.error.issues.map(o=>({path:o.path.join("."),message:o.message}))}}
package/dist/dev/index.js CHANGED
@@ -1 +1 @@
1
- import{promises as o}from"node:fs";import e from"node:path";import{spawn as t}from"node:child_process";import r from"gray-matter";import{loadConfig as n}from"../config/index.js";import{PluginManager as i}from"../plugin/manager.js";import{generateRoutes as s,copyThemeCSS as a,generateCatchAllRoutes as c}from"../build/index.js";import{buildSearchIndex as l}from"../search/index.js";import{renderMarkdown as d}from"../markdown/renderer.js";export async function startDevServer(r={}){console.log("🚀 Starting MarkoPress dev server...\n");const m=r.root||process.cwd(),p=await n(m,{mode:"development",command:"dev"});let g;console.log("✓ Config loaded from "+p.root),p.plugins&&p.plugins.length>0&&(g=new i(p),await g.loadPlugins(p.plugins),await g.execLoadContentHooks());const u=e.join(p.root,"src",".generated","markdown");await o.rm(u,{recursive:!0,force:!0});const f={},h=[];console.log("📝 Generating routes from content...");const w=e.join(p.root,"src","routes"),x=r.useCatchAllRoutes??p.build.useCatchAllRoutes;await o.mkdir(w,{recursive:!0});let v={};if(g&&(v=await g.execExtendRoutesHooks(v)),x?(await c(f,w,p,h,!1,!1),console.log(" Using catch-all dynamic routes")):(await s(f,w,p,h,!1),console.log(" Using static routes")),console.log(" Routes generated\n"),g&&await g.execAllContentLoadedHooks(v),console.log("🎨 Copying theme CSS..."),await a(p.root,p,!1),console.log(" Theme CSS copied\n"),!1!==p.search?.enabled){console.log("🔍 Building search index...");const t=[];for(const[r,n]of Object.entries(p.content)){const i="string"==typeof n?{dir:n}:n;if(!i?.dir)continue;const s=e.resolve(m,i.dir);try{const n=await o.readdir(s,{withFileTypes:!0});for(const i of n)if(i.isFile()&&i.name.endsWith(".md")){const n=e.join(s,i.name),a=await o.readFile(n,"utf-8"),c=await d(a,p.markdown),l=i.name.replace(".md",""),m="index"===l?"/"+r:`/${r}/${l}`;t.push({url:m,html:c.html,title:c.frontmatter?.title||l,frontmatter:c.frontmatter})}}catch{}}try{const r=await l(t,p.search),n=e.join(m,"public","search-index.json");await o.mkdir(e.dirname(n),{recursive:!0}),await o.writeFile(n,r),console.log(` Search index built (${t.length} pages)\n`)}catch(o){console.warn(" Warning: Failed to build search index:",o)}}console.log("🔨 Starting @marko/run dev server...\n");const k=r.port||3e3,S=["dev"];k&&S.push("--port",k+"");const j=t("npx",["marko-run",...S],{stdio:"inherit",cwd:p.root});j.on("error",o=>{console.error("Failed to start dev server:",o),process.exit(1)}),j.on("exit",o=>{0!==o&&(console.error("Dev server exited with code "+o),process.exit(o||1))}),process.on("SIGINT",()=>{j.kill("SIGINT"),process.exit(0)}),process.on("SIGTERM",()=>{j.kill("SIGTERM"),process.exit(0)})}
1
+ import{promises as e}from"node:fs";import o from"node:path";import{spawn as t}from"node:child_process";import r from"gray-matter";import{loadConfig as n}from"../config/index.js";import{PluginManager as s}from"../plugin/manager.js";import{generateRoutes as i,copyThemeCSS as a,generateCatchAllRoutes as c,filePathToUrl as l,extractStylesFromMarkoTags as d}from"../build/index.js";import{buildSearchIndex as m}from"../search/index.js";import{renderMarkdown as p}from"../markdown/renderer.js";export async function startDevServer(r={}){console.log("🚀 Starting MarkoPress dev server...\n");const g=r.root||process.cwd(),u=await n(g,{mode:"development",command:"dev"});console.log("✓ Config loaded from "+u.root);const h=o.join(g,".markopress"),w=await e.stat(h).then(e=>e.isDirectory()).catch(()=>!1)?h:g;let f;u.plugins&&u.plugins.length>0&&(f=new s(u),await f.loadPlugins(u.plugins),await f.execLoadContentHooks());const v=o.join(w,"src",".generated","markdown");await e.rm(v,{recursive:!0,force:!0});const x={},k=[],y=o.resolve(g,u.contentDir);try{const t=await e.readdir(y,{withFileTypes:!0,recursive:!0}),r=new Map;for(const e of t){if(!e.isFile()||!e.name.endsWith(".md"))continue;const t=o.join(e.path||e.parentPath||y,e.name),n=o.relative(y,t),s=l(t,y),i=n.split(o.sep),a=1===i.length?"root":i[0];r.has(a)||r.set(a,[]),r.get(a).push({id:e.name.replace(".md",""),slug:e.name.replace(".md",""),filePath:t,urlPath:s,directory:a})}for(const[e,t]of r)k.push({id:e,dir:o.join(y,"root"===e?"":e),files:t})}catch(e){console.warn("Warning: Could not scan content directory: "+e)}console.log("📝 Generating routes from content...");const j=o.join(w,"src","routes"),S=r.useCatchAllRoutes??u.build.useCatchAllRoutes;await e.mkdir(j,{recursive:!0});let C={};if(f&&(C=await f.execExtendRoutesHooks(C)),S?(await c(x,j,u,k,!1,!1),console.log(" Using catch-all dynamic routes")):(await i(x,j,u,k,!1),console.log(" Using static routes")),console.log(" Routes generated\n"),f&&await f.execAllContentLoadedHooks(C),console.log("🎨 Copying theme CSS..."),await a(w,u,!1),console.log(" Theme CSS copied\n"),u.markdown?.markoTags?.enabled&&(console.log("🎨 Extracting custom tag styles..."),await d(w,u,!1),console.log(" Custom tag styles extracted\n")),!1!==u.search?.enabled){console.log("🔍 Building search index...");const t=[],r=o.resolve(g,u.contentDir);try{const n=await e.readdir(r,{withFileTypes:!0,recursive:!0});for(const s of n){if(!s.isFile()||!s.name.endsWith(".md"))continue;const n=o.join(s.path||s.parentPath||r,s.name),i=l(n,r),a=await e.readFile(n,"utf-8"),c=await p(a,u.markdown),d=s.name.replace(".md","");t.push({url:i,html:c.html,title:c.frontmatter?.title||d,frontmatter:c.frontmatter})}}catch{}try{const r=await m(t,u.search),n=o.join(w,"public","search-index.json");await e.mkdir(o.dirname(n),{recursive:!0}),await e.writeFile(n,r),console.log(` Search index built (${t.length} pages)\n`)}catch(e){console.warn(" Warning: Failed to build search index:",e)}}console.log("🔨 Starting @marko/run dev server...\n");const F=r.port||3e3,T=["dev"];F&&T.push("--port",F+"");const b=t("npx",["marko-run",...T],{stdio:"inherit",cwd:w});b.on("error",e=>{console.error("Failed to start dev server:",e),process.exit(1)}),b.on("exit",e=>{0!==e&&(console.error("Dev server exited with code "+e),process.exit(e||1))}),process.on("SIGINT",()=>{b.kill("SIGINT"),process.exit(0)}),process.on("SIGTERM",()=>{b.kill("SIGTERM"),process.exit(0)})}
@@ -1 +1 @@
1
- import{promises as a}from"node:fs";import e from"node:path";export class TagValidator{detectedTags=new Map;availableTags=new Set;addDetectedTag(a,e,t){const s=a.toLowerCase();this.detectedTags.has(s)||this.detectedTags.set(s,[]),this.detectedTags.get(s).push({tagName:s,fileName:e,lineNumber:t})}async loadAvailableTags(e){try{await a.access(e)}catch{return void console.warn("Warning: tags directory not found at "+e)}const t=await a.readdir(e);for(const a of t)if(a.endsWith(".marko")){const e=a.replace(".marko","");this.availableTags.add(e)}}getAvailableTagsCount(){return this.availableTags.size}getAvailableTags(){return Array.from(this.availableTags).sort()}getDetectedTags(){return this.detectedTags}validate(){const a=[];for(const[e,t]of this.detectedTags)this.availableTags.has(e)||a.push(...t);return{success:0===a.length,missingTags:a}}reset(){this.detectedTags.clear(),this.availableTags.clear()}}export function formatValidationError(a){const e=new Map;for(const t of a)e.has(t.tagName)||e.set(t.tagName,[]),e.get(t.tagName).push(t);let t="\n❌ Marko tags not found:\n";for(const[a,s]of e){t+=`\n <${a}> used in:\n`;for(const a of s)t+=` ${a.fileName}:${a.lineNumber}\n`}return t+="\nCreate missing files in tags/ directory or remove tags from markdown.\n",t}export const globalTagValidator=new TagValidator;
1
+ import{promises as e}from"node:fs";import t from"node:path";const a=new Set(["a","abbr","address","area","article","aside","audio","b","base","bdi","bdo","blockquote","body","br","button","canvas","caption","cite","code","col","colgroup","data","datalist","dd","del","details","dfn","dialog","div","dl","dt","em","embed","fieldset","figcaption","figure","footer","form","h1","h2","h3","h4","h5","h6","head","header","hgroup","hr","html","i","iframe","img","input","ins","kbd","label","legend","li","link","main","map","mark","math","menu","meta","meter","nav","noscript","object","ol","optgroup","option","output","p","param","picture","pre","progress","q","rp","rt","ruby","s","samp","script","search","section","select","slot","small","source","span","strong","style","sub","summary","sup","svg","table","tbody","td","template","textarea","tfoot","th","thead","time","title","tr","track","u","ul","var","video","wbr","circle","clippath","defs","desc","ellipse","feblend","fecolormatrix","fecomponenttransfer","fecomposite","feconvolvematrix","fediffuselighting","fedisplacementmap","fedropshadow","feflood","fefunca","fefuncb","fefuncg","fefuncr","fegaussianblur","feimage","femerge","femergenode","femorphology","feoffset","fepointlight","fespecularlighting","fespotlight","fetile","feturbulence","filter","foreignobject","g","image","line","lineargradient","marker","mask","metadata","mpath","path","pattern","polygon","polyline","radialgradient","rect","set","stop","switch","symbol","text","textpath","tspan","use","view","annotation","semantics","urlset","url","loc","lastmod","changefreq","priority","rss","channel","item","guid","pubdate"]),o=new Set(["if","else","else-if","for","while","let","const","return","await","try","catch","id","lifecycle","effect","html-comment","void","theme-head-top","theme-head-bottom","theme-body-top","theme-body-bottom","theme-doc-top","theme-doc-bottom","theme-navbar-start","theme-navbar-center","theme-navbar-end","theme-sidebar-top","theme-sidebar-bottom","theme-page-top","theme-page-bottom"]);export class TagValidator{detectedTags=new Map;availableTags=new Set;addDetectedTag(e,t,a){const o=e.toLowerCase();this.detectedTags.has(o)||this.detectedTags.set(o,[]),this.detectedTags.get(o).push({tagName:o,fileName:t,lineNumber:a})}async loadAvailableTags(t){try{await e.access(t)}catch{return void console.warn("Warning: tags directory not found at "+t)}const a=await e.readdir(t);for(const e of a)if(e.endsWith(".marko")){const t=e.replace(".marko","");this.availableTags.add(t)}}getAvailableTagsCount(){return this.availableTags.size}getAvailableTags(){return Array.from(this.availableTags).sort()}getDetectedTags(){return this.detectedTags}validate(){const e=[];for(const[t,s]of this.detectedTags)a.has(t)||o.has(t)||this.availableTags.has(t)||e.push(...s);return{success:0===e.length,missingTags:e}}reset(){this.detectedTags.clear(),this.availableTags.clear()}}export function formatValidationError(e){const t=new Map;for(const a of e)t.has(a.tagName)||t.set(a.tagName,[]),t.get(a.tagName).push(a);let a="\n❌ Marko tags not found:\n";for(const[e,o]of t){a+=`\n <${e}> used in:\n`;for(const e of o)a+=` ${e.fileName}:${e.lineNumber}\n`}return a+="\nCreate missing files in tags/ directory or remove tags from markdown.\n",a}export const globalTagValidator=new TagValidator;
@@ -1 +1 @@
1
- import{spawn as o}from"node:child_process";export async function preview(r={}){const{port:e=4173,host:s="localhost",root:t}=r;console.log("🚀 Starting MarkoPress preview server...\n"),console.log(` Server: http://${s}:${e}`),console.log(" Press Ctrl+C to stop\n");const n=t||process.cwd();return o("npx",["marko-run","preview","--port",e+""],{stdio:"inherit",cwd:n}).on("error",o=>{console.error("Failed to start preview server:",o.message),process.exit(1)}),new Promise(()=>{})}
1
+ import o from"node:path";import{existsSync as r,statSync as e}from"node:fs";import{spawn as s}from"node:child_process";export async function preview(t={}){const{port:n=4173,host:i="localhost",root:p}=t;console.log("🚀 Starting MarkoPress preview server...\n"),console.log(` Server: http://${i}:${n}`),console.log(" Press Ctrl+C to stop\n");const c=p||process.cwd(),l=o.join(c,".markopress"),a=r(l)&&e(l).isDirectory()?l:c;return s("npx",["marko-run","preview","--port",n+""],{stdio:"inherit",cwd:a}).on("error",o=>{console.error("Failed to start preview server:",o.message),process.exit(1)}),new Promise(()=>{})}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "markopress",
3
- "version": "0.0.7",
3
+ "version": "0.0.9",
4
4
  "description": "A fast, modern static site generator built on Marko.js v6 - drop-in alternative to VitePress and Docusaurus with full content compatibility",
5
5
  "keywords": [
6
6
  "static-site-generator",
@@ -78,8 +78,10 @@
78
78
  <include('../styles.css')/>
79
79
  </style>
80
80
 
81
- <!-- Component Styles -->
82
- <link rel="stylesheet" href=(basePath + '/markopress-components.css')>
81
+ <!-- Component Styles (only when marko tags are enabled) -->
82
+ <if=$global.hasComponentStyles>
83
+ <link rel="stylesheet" href=(basePath + '/markopress-components.css')>
84
+ </if>
83
85
 
84
86
  <theme-head-bottom/>
85
87
  </head>
@@ -143,6 +143,20 @@
143
143
  padding: 0;
144
144
  }
145
145
 
146
+ /* Element-level reset to override user-agent styles (specificity 0,0,1 > 0,0,0) */
147
+ h1, h2, h3, h4, h5, h6,
148
+ p, ol, ul, dl, dd,
149
+ blockquote, pre, figure,
150
+ fieldset, legend,
151
+ hr {
152
+ margin: 0;
153
+ margin-block: 0;
154
+ margin-inline: 0;
155
+ padding: 0;
156
+ padding-block: 0;
157
+ padding-inline: 0;
158
+ }
159
+
146
160
  html {
147
161
  font-size: 16px;
148
162
  scroll-behavior: smooth;
@@ -773,9 +787,17 @@ blockquote {
773
787
  }
774
788
 
775
789
  /* Header anchors */
790
+ h1, h2, h3, h4, h5, h6 {
791
+ position: relative;
792
+ }
793
+
776
794
  a.header-anchor {
795
+ position: absolute;
796
+ left: -0.8em;
777
797
  color: var(--color-primary-3);
778
798
  opacity: 0;
799
+ text-decoration: none;
800
+ font-weight: var(--font-medium);
779
801
  transition: opacity var(--transition-fast);
780
802
  }
781
803
 
@@ -9,6 +9,7 @@ let moduleEnhancements = {};
9
9
  let allMetadata = {};
10
10
  let contentModules = {};
11
11
 
12
+ // {{IF_BUILD_START}}
12
13
  if (IS_BUILD) {
13
14
  // Build mode: load pre-compiled content via import.meta.glob (Vite resolves at build time)
14
15
  contentModules = import.meta.glob('/src/.generated/markdown/{{CONTENT_TYPE}}/*.marko');
@@ -17,16 +18,6 @@ if (IS_BUILD) {
17
18
  } catch {
18
19
  allMetadata = {};
19
20
  }
20
- } else {
21
- // Dev mode: load module enhancements for sidebar, etc.
22
- try {
23
- const { promises: fs } = await import('node:fs');
24
- const { default: path } = await import('node:path');
25
- const enhancementsPath = path.join(config.root, 'src/.generated/module-enhancements.json');
26
- moduleEnhancements = await fs.readFile(enhancementsPath, 'utf-8').then(JSON.parse).catch(() => ({}));
27
- } catch {
28
- // Enhancements file not found, use empty object
29
- }
30
21
  }
31
22
 
32
23
  // Also load module enhancements in build mode
@@ -37,6 +28,21 @@ if (IS_BUILD) {
37
28
  moduleEnhancements = {};
38
29
  }
39
30
  }
31
+ // {{IF_BUILD_END}}
32
+
33
+ // {{IF_DEV_START}}
34
+ if (!IS_BUILD) {
35
+ // Dev mode: load module enhancements for sidebar, etc.
36
+ try {
37
+ const { promises: fs } = await import('node:fs');
38
+ const { default: path } = await import('node:path');
39
+ const enhancementsPath = path.join(config.root, 'src/.generated/module-enhancements.json');
40
+ moduleEnhancements = await fs.readFile(enhancementsPath, 'utf-8').then(JSON.parse).catch(() => ({}));
41
+ } catch {
42
+ // Enhancements file not found, use empty object
43
+ }
44
+ }
45
+ // {{IF_DEV_END}}
40
46
 
41
47
  export async function GET(context, next) {
42
48
  const slug = context.params.slug || 'index';
@@ -50,6 +56,7 @@ export async function GET(context, next) {
50
56
  let frontmatter = {};
51
57
  let headers = [];
52
58
 
59
+ // {{IF_BUILD_START}}
53
60
  if (IS_BUILD) {
54
61
  // === BUILD MODE ===
55
62
  // Look up pre-compiled .marko module from glob map
@@ -68,7 +75,11 @@ export async function GET(context, next) {
68
75
  const meta = allMetadata[metaKey] || {};
69
76
  frontmatter = meta.frontmatter || {};
70
77
  headers = meta.headers || [];
71
- } else {
78
+ }
79
+ // {{IF_BUILD_END}}
80
+
81
+ // {{IF_DEV_START}}
82
+ if (!IS_BUILD) {
72
83
  // === DEV MODE ===
73
84
  // Render markdown at request time using virtual modules
74
85
  const { renderMarkdown } = await import('markopress/markdown');
@@ -76,8 +87,12 @@ export async function GET(context, next) {
76
87
  const { promises: fs } = await import('node:fs');
77
88
  const { default: path } = await import('node:path');
78
89
 
79
- const contentConfig = config.content[contentType];
80
- const contentDir = typeof contentConfig === 'string' ? contentConfig : contentConfig?.dir;
90
+ // For root-level content, use contentDir directly; for modules, use module-specific dir
91
+ const contentDir = contentType === 'root'
92
+ ? config.contentDir
93
+ : (typeof config.content[contentType] === 'string'
94
+ ? config.content[contentType]
95
+ : config.content[contentType]?.dir);
81
96
 
82
97
  if (!contentDir) {
83
98
  return next();
@@ -104,6 +119,7 @@ export async function GET(context, next) {
104
119
  return new Response('Not Found', { status: 404, statusText: 'Not Found' });
105
120
  }
106
121
  }
122
+ // {{IF_DEV_END}}
107
123
 
108
124
  // === SHARED: Set context for template ===
109
125
  // Prefix navbar links with base path
@@ -111,6 +127,7 @@ export async function GET(context, next) {
111
127
  context.lang = config.site.lang || 'en-US';
112
128
  context.siteHead = config.site.head || [];
113
129
  context.footer = config.theme.options.footer || null;
130
+ context.hasComponentStyles = !!(config.markdown?.markoTags?.enabled);
114
131
 
115
132
  context.title = frontmatter.title || slug;
116
133
  context.description = frontmatter.description || '';
@@ -11,7 +11,7 @@
11
11
  <meta name="theme-color" content="#3c8772">
12
12
  <link rel="stylesheet" href="{{BASE_PATH}}/_markopress/theme/theme-{{THEME_STYLE}}.css">
13
13
  <link rel="stylesheet" href="{{BASE_PATH}}/_markopress/theme/styles.css">
14
- <link rel="stylesheet" href="{{BASE_PATH}}/markopress-components.css">
14
+ {{COMPONENT_STYLES_LINK}}
15
15
  <theme-head-bottom/>
16
16
  </head>
17
17
  <body>
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/build/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAQH,OAAO,KAAK,EAAE,eAAe,EAAe,MAAM,qBAAqB,CAAC;AACxE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAKzD,OAAO,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAiBxF,MAAM,MAAM,aAAa,GAAG,GAAG,CAAC;AAEhC,MAAM,WAAW,YAAY;IAC3B,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;GAEG;AACH,wBAAsB,KAAK,CAAC,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,WAAW,CAAC,CAka5E;AAyDD;;;GAGG;AACH,wBAAsB,cAAc,CAClC,QAAQ,EAAE,eAAe,EACzB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,aAAa,EAAE,EACxB,KAAK,EAAE,OAAO,GACb,OAAO,CAAC,IAAI,CAAC,CAsDf;AAED;;;;;;;;;GASG;AACH,wBAAsB,sBAAsB,CAC1C,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,eAAe,EACzB,KAAK,EAAE,OAAO,GACb,OAAO,CAAC,IAAI,CAAC,CA4Ff;AAiLD;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAsCpD;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,OAAO,GACb,OAAO,CAAC,IAAI,CAAC,CAuCf;AAED;;GAEG;AACH,wBAAsB,YAAY,CAChC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,cAAc,EACtB,KAAK,EAAE,OAAO,GACb,OAAO,CAAC,IAAI,CAAC,CAuFf;AAED;;;;;;;GAOG;AACH,wBAAsB,0BAA0B,CAC9C,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,cAAc,EACtB,KAAK,EAAE,OAAO,GACb,OAAO,CAAC,IAAI,CAAC,CA4Hf;AAkBD,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,cAAc,EACtB,KAAK,EAAE,OAAO,GACb,OAAO,CAAC,IAAI,CAAC,CA6Ef;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,cAAc,EACtB,KAAK,EAAE,OAAO,GACb,OAAO,CAAC,IAAI,CAAC,CAkDf;AAyYD;;;GAGG;AACH,wBAAsB,sBAAsB,CAC1C,QAAQ,EAAE,eAAe,EACzB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,aAAa,EAAE,EACxB,KAAK,EAAE,OAAO,GACb,OAAO,CAAC,IAAI,CAAC,CAkEf;AAGD,OAAO,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,CAAC;AACvD,OAAO,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC"}