markopress 0.0.19 → 0.0.21

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.
@@ -1 +1 @@
1
- import{promises as e}from"node:fs";import t from"node:path";import{spawn as o}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 g,escapeMarkoText as m}from"./vite-markdown-plugin.js";import{renderMarkdown as f}from"../markdown/renderer.js";import{buildSearchIndex as p}from"../search/index.js";const h=t.dirname(n(import.meta.url)),w=t.resolve(h,"..",".."),y=t.join(w,"src","theme","default"),k=new Set(["@markopress/theme-default","theme-default","default"]);function j(e){return k.has(e)}export function filePathToUrl(e,o){const n=t.relative(o,e).replace(/\.md$/,"").split(t.sep).join("/");return"index"===n?"/":n.endsWith("/index")?"/"+n.replace("/index",""):"/"+n}export async function syncPublicAssets(o,n,s){if(o===n)return;const a=t.join(o,"public"),i=t.join(n,"public"),r=t.join(n,"src",".generated","public-assets-manifest.json"),c=await async function(t){try{const o=await e.readFile(t,"utf-8"),n=JSON.parse(o);return Array.isArray(n)?n.filter(e=>"string"==typeof e):[]}catch{return[]}}(r);let l=[];try{if(!(await e.stat(a)).isDirectory())return await _(i,c,l),void await E(r,l)}catch{return await _(i,c,l),void await E(r,l)}await e.mkdir(i,{recursive:!0}),l=await D(a,i),await _(i,c,l),await E(r,l),s&&(console.log(" Synced public assets from: "+a),console.log(" Output: "+i))}export async function build(n={}){const{outDir:i,debug:r=!1,useCatchAllRoutes:u,root:g}=n,h=g||process.cwd(),w=[],y=new Map,k=new Map,j=t.join(h,".markopress");let T=h;try{(await e.stat(j)).isDirectory()&&(T=j)}catch{}const v=e=>({start:()=>{k.set(e,performance.now())},end:()=>{const t=k.get(e)||0,o=performance.now()-t;y.set(e,o)}});try{console.log("šŸš€ Building MarkoPress site...\n");const n=v("Config loading");n.start();let g,k=await a(h,{mode:"production",command:"build"});if(n.end(),k.plugins&&k.plugins.length>0){console.log("šŸ”Œ Loading plugins...");const e=v("Plugin loading");e.start(),g=new d(k),await g.loadPlugins(k.plugins),k=g.getConfig(),e.end(),console.log("")}if(g){console.log("šŸ“¦ Loading plugin content...");const e=v("Plugin loadContent hooks");e.start(),await g.execLoadContentHooks(),e.end(),console.log(" Plugin content loaded\n")}const j={},P=[],$=t.resolve(h,k.contentDir);try{const o=await e.readdir($,{withFileTypes:!0,recursive:!0}),n=new Map;for(const a of o){if(!a.isFile()||!a.name.endsWith(".md"))continue;const o=t.join(a.path||a.parentPath||$,a.name),i=t.relative($,o),r=filePathToUrl(o,$),c=i.split(t.sep),l=1===c.length?"root":c[0],d=await e.readFile(o,"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:o,urlPath:r,directory:l,processed:{frontmatter:u}})}for(const[e,o]of n){const n=k.content[e]||{},s=new Map;P.push({id:e,dir:t.join($,"root"===e?"":e),config:n,features:n,files:o,enhance(e,t){s.set(e,t)},getEnhancement:e=>s.get(e),_enhancements:s})}}catch(e){console.warn("Warning: Could not scan content directory: "+e)}const F={};for(const e of P){const t=e.files.map(t=>({id:t.urlPath,moduleId:e.id,metadata:{...t.processed.frontmatter},urlPath:t.urlPath}));t.length>0&&(F[e.id]=t)}if(g&&g.getPluginData().set("contentRegistry",F),r&&Object.keys(F).length>0){const e=Object.keys(F),t=Object.values(F).reduce((e,t)=>e+t.length,0);console.log(` Registry: ${t} items across ${e.length} modules (${e.join(", ")})`)}if(g&&P.length>0){console.log("šŸ”Œ Enhancing modules with plugin metadata...");const o=v("Module enhancement");o.start();for(const e of P)console.log(` Module: ${e.id} (${e.files.length} files)`),e.files.length>0&&console.log(" First file has processed: "+!!e.files[0].processed);await g.execEnhanceModulesHooks(P),o.end(),console.log(` Enhanced ${P.length} module(s)\n`);const n=t.join(T,"src",".generated");await e.mkdir(n,{recursive:!0});const s=(k.site?.base||"/").replace(/\/$/,""),a={};for(const e of P){const t={},o=e._enhancements.entries();for(const[e,n]of o)t[e]=n;Object.keys(t).length>0&&(s&&b(t,s),a[e.id]=t)}const i=t.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");const c=t.join(n,"module-enhancements.json");await e.writeFile(c,JSON.stringify(a,null,2),"utf-8"),console.log(" Wrote module enhancements to src/.generated/module-enhancements.{js,json}\n")}console.log("šŸ“¦ Syncing public assets...");const C=v("Public assets sync");if(C.start(),await syncPublicAssets(h,T,r),C.end(),console.log(" Public assets synced\n"),!1!==k.search?.enabled){console.log("šŸ” Building search index...");const o=v("Search index");o.start();const n=[],s=(k.site?.base||"/").replace(/\/$/,"");for(const t of P)for(const o of t.files)try{const t=await e.readFile(o.filePath,"utf-8"),a=await f(t,{...k.markdown,base:s});n.push({url:o.urlPath,html:a.html,title:a.frontmatter?.title||o.id,frontmatter:a.frontmatter})}catch(e){console.warn(` Warning: Could not index ${o.filePath}:`,e)}try{const o=await p(n,k.search),s=t.join(T,"public","search-index.json");await e.mkdir(t.dirname(s),{recursive:!0}),await e.writeFile(s,o),console.log(` Search index built (${n.length} pages)\n`)}catch(e){console.warn(" Warning: Failed to build search index:",e)}o.end()}if(k.markdown.markoTags?.enabled){const e=t.join(T,k.markdown.markoTags.tagsDir||"src/tags");console.log("šŸ” Scanning tags directory...");const o=v("Tag validation setup");o.start(),await c.loadAvailableTags(e),o.end(),console.log(` Found ${c.getAvailableTagsCount()} tags\n`)}else c.reset();const S=t.join(T,"src","routes");await e.mkdir(S,{recursive:!0});let x={};if(g){const e=v("Extend routes hooks");e.start(),x=await g.execExtendRoutesHooks(x),e.end(),console.log("šŸ”Œ Extended routes manifest:",Object.keys(x).length)}console.log("šŸ“ Generating routes from content...");const D=v("Route generation");D.start();const E=u??k.build.useCatchAllRoutes;console.log("šŸ“„ Pre-rendering markdown to .marko files...");const _=v("Pre-render markdown");_.start();const A=t.join(T,"src",".generated","markdown");await e.mkdir(A,{recursive:!0});const O={};let N=0;for(const o of P){const n=t.join(A,o.id);await e.mkdir(n,{recursive:!0});for(const s of o.files)try{const a=await e.readFile(s.filePath,"utf-8"),i=(k.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${m(c)}\n</div>`,d=t.join(n,s.slug+".marko");await e.writeFile(d,l),O[`${o.id}/${s.slug}`]={frontmatter:r.frontmatter,headers:r.headers||[],headTop:s.headTop||[],headBottom:s.headBottom||[]},N++}catch(e){console.warn(` Warning: Failed to pre-render ${o.id}/${s.slug}:`,e)}}const M=t.join(T,"src",".generated","content-metadata.js"),I=`// Auto-generated by MarkoPress - Do not edit\nexport default ${JSON.stringify(O,null,2)};\n`;await e.writeFile(M,I),_.end(),console.log(` Pre-rendered ${N} markdown files\n`),E?(await generateCatchAllRoutes(j,S,k,P,r,!0),console.log(" Using catch-all dynamic routes")):(await generateRoutes(j,S,k,P,r),console.log(" Using static routes")),D.end(),console.log(" Routes generated\n");const W=[];for(const[e,t]of Object.entries(x))(t.handler||t.component)&&(W.push({path:e,...t}),console.log(" Found plugin route: "+e));if(console.log(`šŸ”Œ Total manifest routes: ${Object.keys(x).length}, Plugin routes: ${W.length}`),g){const o=[...g.getPluginRoutes(),...W];if(o.length>0){console.log(`šŸ”Œ Generating ${o.length} plugin routes...`);const n=v("Plugin route generation");n.start(),await async function(o,n,s,a){for(const s of o){const o=s.path.slice(1),i=t.join(n,o,"+page");if(await e.mkdir(t.dirname(i),{recursive:!0}),s.handler){const o=t.join(t.dirname(i),"+handler.js");await e.writeFile(o,s.handler)}if(s.component){const t=i+".marko";await e.writeFile(t,s.component)}a&&console.log(" Generated plugin route: "+s.path)}}(o,S,0,r),n.end(),console.log(" Plugin routes generated\n")}}console.log("āš™ļø Generating Vite config...");const L=v("Vite config generation");if(L.start(),await generateViteConfig(T,r),L.end(),console.log(" Vite config generated\n"),g){console.log("šŸ”Œ Processing plugin allContentLoaded hooks...");const o=v("AllContentLoaded hooks");o.start(),await g.execAllContentLoadedHooks(x),o.end(),console.log(" All content processed\n");const n=g.getPluginData();if(n.has("contentRegistry")){const o=t.join(T,"src",".generated");await e.mkdir(o,{recursive:!0});const s=n.get("contentRegistry"),a=t.join(o,"content-registry.js"),i=`// Auto-generated by MarkoPress content-registry plugin - Do not edit\nexport default ${JSON.stringify(s,null,2)};\n`;await e.writeFile(a,i,"utf-8");const r=t.join(o,"content-registry.json");await e.writeFile(r,JSON.stringify(s,null,2),"utf-8"),console.log(" Wrote content registry to src/.generated/content-registry.{js,json}\n")}}if(k.markdown.markoTags?.enabled){console.log("šŸ” Validating Marko tags...");const e=v("Tag validation");e.start();const t=c.validate();if(e.end(),!t.success){const e=l(t.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 R=v("Theme CSS copy");R.start(),await copyThemeCSS(T,k,r),R.end(),console.log(" Theme CSS copied\n"),console.log("šŸŽØ Extracting styles from Marko components...");const G=v("Marko component styles extraction");G.start(),await extractStylesFromMarkoTags(T,k,r),G.end(),console.log(" Component styles extracted\n");const B=[];for(const e of P)for(const t of e.files)B.push(t.urlPath);for(const e of Object.keys(x))B.includes(e)||B.push(e);const U=t.join(T,"src",".generated","static-urls.json");await e.mkdir(t.dirname(U),{recursive:!0}),await e.writeFile(U,JSON.stringify(B,null,2)),r&&console.log(` Generated static URL manifest: ${B.length} URLs`),console.log("šŸ”Ø Building with @marko/run...");const V=v("@marko/run build");V.start();const H=i||k.build.outDir,J=await async function(e,n,s){return new Promise(a=>{const i=["build"];e&&i.push("--output",e),n&&i.push("--debug");const r=o("npx",["marko-run",...i],{stdio:"inherit",cwd:s});r.on("close",o=>{if(0===o){const o=e||"dist";a({success:!0,outDir:t.join(s,o),errors:[]})}else a({success:!1,outDir:"",errors:["Build process exited with code "+o]})}),r.on("error",e=>{a({success:!1,outDir:"",errors:["Failed to start build process: "+e.message]})})})}(H,r,T);if(V.end(),!J.success)return w.push(...J.errors),{success:!1,outDir:"",pages:0,errors:w};const Y=v("Collect build assets");Y.start();const q=await async function(t){const o=[];try{const n=await e.readdir(t,{recursive:!0});for(const e of n)"string"==typeof e&&(e.endsWith(".js")||e.endsWith(".css")||e.endsWith(".json"))&&o.push(e)}catch(e){console.warn("Warning: Could not collect build assets:",e)}return o}(J.outDir);if(Y.end(),g){console.log("šŸ”Œ Processing plugin postBuild hooks...");const e=v("Post-build hooks");e.start(),await g.execPostBuildHooks(k,J.outDir,x,q),e.end(),console.log(" Post-build hooks completed\n")}console.log("šŸ“¦ Copying Marko tags directory...");const z=v("Copy tags directory");z.start(),await copyTagsDirectory(h,J.outDir,k,r),z.end(),console.log(" Tags directory copied\n"),console.log("\nāœ… Build completed successfully!"),console.log(" Output: "+J.outDir),console.log(" Pages: Generated dynamically at request time"),console.log("\nā±ļø Build timing:");const K=Array.from(y.entries()).sort((e,t)=>t[1]-e[1]);for(const[e,t]of K){const o=(t/1e3).toFixed(2);console.log(` ${"ā–ˆ".repeat(Math.min(Math.floor(t/100),20))} ${e}: ${o}s`)}return{success:!0,outDir:J.outDir,pages:0,errors:w}}catch(e){const t=e instanceof Error?e.message:e+"";return w.push(t),console.error("\nāŒ Build failed:",t),{success:!1,outDir:"",pages:0,errors:w}}}function b(e,t){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(t)?t+e.link:e.link})):e.items}))),Array.isArray(e.blogPosts)&&(e.blogPosts=e.blogPosts.map(e=>({...e,link:e.link&&!e.link.startsWith(t)?t+e.link:e.link})))}export async function generateRoutes(e,o,n,s,a){await cleanupGeneratedRoutes(o,e,a);const i=[];let r=0,c=0,l=0;for(const[t,o]of Object.entries(e)){if(!Array.isArray(o))continue;i.push(t);const e=o;if("pages"===t)for(const t of e)await v(0,0,0,0,a),r++;else if("blog"===t)for(const t of e)await $(0,0,0,0,a),l++;else for(const t of e)await P(0,0,0,0,a),c++}await F(o,n,a);const d=t.resolve(o,"..","..");await generateViteConfig(d,a),await C(o,n,a),a&&(console.log(` Generated ${r} page routes`),console.log(` Generated ${c} doc routes`),console.log(` Generated ${l} blog routes`))}export async function cleanupGeneratedRoutes(o,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(o,{recursive:!0,withFileTypes:!0});for(const c of n){if(!c.isFile())continue;const n=t.join(c.path||c.parentPath||o,c.name),l=t.relative(o,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 t=e instanceof Error?e.message:e+"";a.push(`Failed to delete ${l}: ${t}`)}}}for(const e of r){const n=t.join(o,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(o){try{const n=await e.readdir(o,{withFileTypes:!0});for(const e of n)if(e.isDirectory()){const n=t.join(o,e.name);await T(n)}0===(await e.readdir(o)).length&&await e.rmdir(o)}catch{}}async function v(e,t,o,n,s){s&&console.log(" Warning: Static routes deprecated, use catch-all routes")}async function P(e,t,o,n,s){s&&console.log(" Warning: Static routes deprecated, use catch-all routes")}async function $(e,t,o,n,s){s&&console.log(" Warning: Static routes deprecated, use catch-all routes")}async function F(o,n,s){const a=t.join(o,"_config.js"),i={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||{},_headInject:n._headInject||void 0},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 C(o,n,s){const a=t.join(o,"+layout.marko"),i=(n.theme,n.site?.title||"MarkoPress"),r=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:i,THEME_STYLE:r,BASE_PATH:c,COMPONENT_STYLES_LINK:l});await e.writeFile(a,d),s&&console.log(" Generated: "+a)}async function S(o,n){const s=t.dirname(new URL(import.meta.url).pathname),a=t.join(s,"..",".."),i=t.join(a,"templates",o);let r=await e.readFile(i,"utf-8");r="true"===n.IS_BUILD?r.replace(/\/\/ \{\{IF_DEV_START\}\}[\s\S]*?\/\/ \{\{IF_DEV_END\}\}/g,""):r.replace(/\/\/ \{\{IF_BUILD_START\}\}[\s\S]*?\/\/ \{\{IF_BUILD_END\}\}/g,""),r=r.replace(/\/\/ \{\{IF_(?:BUILD|DEV)_(?:START|END)\}\}\n?/g,"");for(const[e,t]of Object.entries(n))r=r.replace(RegExp(`\\{\\{${e}\\}\\}`,"g"),t);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(t.isAbsolute(e))throw Error(`Theme name cannot be an absolute path: "${e}"`)}export async function generateViteConfig(o,n){const s=t.join(o,"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(o,n,s){const a=t.join(o,"public","_markopress","theme");await e.mkdir(a,{recursive:!0});const i=n.theme?.name||"@markopress/theme-default";try{validateThemeName(i)}catch(e){const t=e instanceof Error?e.message:e+"";throw Error("Security: "+t)}const r=n.theme?.options?.style||"default",c=`theme-${r}.css`,l=[...j(i)?[t.join(y,"public",c)]:[],t.join(o,"..","node_modules",i,"public",c),t.join(o,"node_modules",i,"public",c)];let d=null,u=null;for(const t of l)try{await e.access(t),d=await e.readFile(t,"utf-8"),u=t;break}catch{}if(!d){console.warn(` Warning: Could not find ${c}, using minimal fallback`);const o=`/* Minimal fallback CSS for style: ${r} */\nbody { font-family: system-ui, sans-serif; margin: 0; padding: 0; }`,n=t.join(a,c);return void await e.writeFile(n,o)}const g=t.join(a,c);await e.writeFile(g,d),s&&(console.log(` Copied ${c} from: ${u}`),console.log(" Output: "+g));const m="styles.css",f=[...j(i)?[t.join(y,m)]:[],t.join(o,"..","node_modules",i,"src",m),t.join(o,"node_modules",i,"src",m)];for(const o of f)try{await e.access(o);const n=await e.readFile(o,"utf-8"),i=t.join(a,m);await e.writeFile(i,n),s&&(console.log(` Copied ${m} from: ${o}`),console.log(" Output: "+i));break}catch{}}export async function extractStylesFromMarkoTags(o,n,s){const a=n.markdown?.markoTags?.tagsDir||"src/tags",i=t.join(o,a),r=t.join(o,"public");await e.mkdir(r,{recursive:!0});const c=t.join(r,"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(i)}catch{return void(s&&console.log(" No tags directory found at: "+i))}const l=[];if(await async function o(n){const s=await e.readdir(n,{withFileTypes:!0});for(const e of s){const s=t.join(n,e.name);e.isDirectory()?await o(s):e.isFile()&&e.name.endsWith(".marko")&&l.push(s)}}(i),0===l.length)return void(s&&console.log(" No .marko files found in: "+i));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 o of l){const n=t.relative(i,o),s=""===t.dirname(n)?t.basename(n,".marko"):t.join(t.dirname(n),t.basename(n,".marko"));try{const t=await e.readFile(o,"utf-8"),n=/<style\b[^>]*>([\s\S]*?)<\/style>/gi,a=Array.from(t.matchAll(n));if(a.length>0){d.push(`/* ${s}.marko */`);for(const e of a){const t=e[1]||"";if(t){const e=t.split("\n");let o=0;for(;o<e.length&&""===e[o].trim();)o++;let n=e.length-1;for(;n>=o&&""===e[n].trim();)n--;for(let t=o;t<=n;t++){const o=e[t];if(""===o.trim()){d.push("");continue}const n=o.match(/^(\s*)/),s=n?n[1].length:0,a=" ".repeat(Math.floor(s/2)),i=o.trim().replace(/:global\(([^)]+)\)/g,"$1");d.push(a+i)}}}d.push("")}}catch(e){console.warn(` Warning: Could not read file ${o}:`,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(o){const n=await e.readdir(o,{withFileTypes:!0}),s=await Promise.all(n.map(e=>{const n=t.resolve(o,e.name);return e.isDirectory()?x(n):n}));return Array.prototype.concat(...s).filter(e=>e.endsWith(".marko"))}export async function copyThemeComponents(o,n,s){const a=n.theme?.name||"@markopress/theme-default",i=t.join(o,"src"),r=t.join(i,"tags");await e.mkdir(r,{recursive:!0});const c=[...j(a)?[t.join(y,"tags")]:[],t.join(o,"..","node_modules",a,"dist","tags"),t.join(o,"node_modules",a,"dist","tags"),t.join(o,"..","node_modules",a,"src","components"),t.join(o,"node_modules",a,"src","components")];let l=null;for(const t of c)try{await e.access(t),l=t;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 o of d){const n=t.relative(l,o),a=t.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(t.dirname(a),{recursive:!0}),await e.copyFile(o,a),u++)}s&&(console.log(` Copied ${u} theme components from: ${l}`),console.log(" Output: "+r))}export async function copyTagsDirectory(o,n,s,a){const i=s.markdown?.markoTags?.tagsDir||"src/tags",r=t.join(o,i),c=t.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 o of l){const n=t.join(r,o.name),s=t.join(c,o.name);if(o.isDirectory()){await e.mkdir(s,{recursive:!0});const o=await e.readdir(n,{withFileTypes:!0});for(const a of o){const o=t.join(n,a.name),i=t.join(s,a.name);a.isDirectory()||(await e.copyFile(o,i),d++)}}else o.isFile()&&(await e.copyFile(n,s),d++)}a&&(console.log(` Copied ${d} tag files from: ${r}`),console.log(" Output: "+c))}async function D(o,n,s=o){const a=await e.readdir(o,{withFileTypes:!0}),i=[];for(const r of a){const a=t.join(o,r.name),c=t.join(n,r.name);r.isDirectory()?(await e.mkdir(c,{recursive:!0}),i.push(...await D(a,c,s))):r.isFile()&&(await e.copyFile(a,c),i.push(t.relative(s,a)))}return i}async function E(o,n){await e.mkdir(t.dirname(o),{recursive:!0}),await e.writeFile(o,JSON.stringify(n,null,2))}async function _(o,n,s){const a=new Set(s),i=n.filter(e=>!a.has(e)).sort((e,t)=>t.length-e.length);for(const n of i){const s=t.join(o,n);await e.rm(s,{force:!0}),await A(t.dirname(s),o)}}async function A(o,n){let s=o;for(;s.startsWith(n)&&s!==n;){let o;try{o=await e.readdir(s)}catch{break}if(o.length>0)break;await e.rmdir(s),s=t.dirname(s)}}export async function generateCatchAllRoutes(o,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=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 o=t.join(n,"$$slug");await e.mkdir(o,{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:r?"true":"false"});await e.writeFile(t.join(o,"+handler.js"),s);const a=await S("catch-all-page.marko.template",{CONTENT_TYPE_CLASS:"page"});await e.writeFile(t.join(o,"+page.marko"),a),i&&console.log(" Generated root pages catch-all route")}for(const o of d){if("root"===o)continue;const s=t.join(n,o,"$$slug");await e.mkdir(s,{recursive:!0});const a=await S("catch-all-handler.js.template",{CONTENT_TYPE:o,CONFIG_PATH:"../../_config.js",VITE_PLUGIN_PATH:"markopress/build",IS_BUILD:r?"true":"false"});await e.writeFile(t.join(s,"+handler.js"),a);const c=await S("catch-all-page.marko.template",{CONTENT_TYPE_CLASS:o});await e.writeFile(t.join(s,"+page.marko"),c),i&&console.log(` Generated ${o} catch-all route`)}await F(n,s,i);const u=t.resolve(n,"..","..");await generateViteConfig(u,i),await C(n,s,i)}export{u as loadMarkdownModule,g as registerMarkdownContent};export{markdownContentPlugin}from"./vite-markdown-plugin.js";
1
+ import{promises as e}from"node:fs";import t from"node:path";import{spawn as o}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 g,escapeMarkoText as m}from"./vite-markdown-plugin.js";import{renderMarkdown as f}from"../markdown/renderer.js";import{preloadContentLanguages as p}from"../markdown/loader.js";import{buildSearchIndex as h}from"../search/index.js";const w=t.dirname(n(import.meta.url)),y=t.resolve(w,"..",".."),k=t.join(y,"src","theme","default"),j=new Set(["@markopress/theme-default","theme-default","default"]);function b(e){return j.has(e)}export function filePathToUrl(e,o){const n=t.relative(o,e).replace(/\.md$/,"").split(t.sep).join("/");return"index"===n?"/":n.endsWith("/index")?"/"+n.replace("/index",""):"/"+n}export async function syncPublicAssets(o,n,s){if(o===n)return;const a=t.join(o,"public"),i=t.join(n,"public"),r=t.join(n,"src",".generated","public-assets-manifest.json"),c=await async function(t){try{const o=await e.readFile(t,"utf-8"),n=JSON.parse(o);return Array.isArray(n)?n.filter(e=>"string"==typeof e):[]}catch{return[]}}(r);let l=[];try{if(!(await e.stat(a)).isDirectory())return await A(i,c,l),void await _(r,l)}catch{return await A(i,c,l),void await _(r,l)}await e.mkdir(i,{recursive:!0}),l=await E(a,i),await A(i,c,l),await _(r,l),s&&(console.log(" Synced public assets from: "+a),console.log(" Output: "+i))}export async function build(n={}){const{outDir:i,debug:r=!1,useCatchAllRoutes:u,root:g}=n,w=g||process.cwd(),y=[],k=new Map,j=new Map,b=t.join(w,".markopress");let v=w;try{(await e.stat(b)).isDirectory()&&(v=b)}catch{}const P=e=>({start:()=>{j.set(e,performance.now())},end:()=>{const t=j.get(e)||0,o=performance.now()-t;k.set(e,o)}});try{console.log("šŸš€ Building MarkoPress site...\n");const n=P("Config loading");n.start();let g,j=await a(w,{mode:"production",command:"build"});if(n.end(),j.plugins&&j.plugins.length>0){console.log("šŸ”Œ Loading plugins...");const e=P("Plugin loading");e.start(),g=new d(j),await g.loadPlugins(j.plugins),j=g.getConfig(),e.end(),console.log("")}if(g){console.log("šŸ“¦ Loading plugin content...");const e=P("Plugin loadContent hooks");e.start(),await g.execLoadContentHooks(),e.end(),console.log(" Plugin content loaded\n")}const b={},$=[],F=t.resolve(w,j.contentDir);try{const o=await e.readdir(F,{withFileTypes:!0,recursive:!0}),n=new Map;for(const a of o){if(!a.isFile()||!a.name.endsWith(".md"))continue;const o=t.join(a.path||a.parentPath||F,a.name),i=t.relative(F,o),r=filePathToUrl(o,F),c=i.split(t.sep),l=1===c.length?"root":c[0],d=await e.readFile(o,"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:o,urlPath:r,directory:l,processed:{frontmatter:u}})}for(const[e,o]of n){const n=j.content[e]||{},s=new Map;$.push({id:e,dir:t.join(F,"root"===e?"":e),config:n,features:n,files:o,enhance(e,t){s.set(e,t)},getEnhancement:e=>s.get(e),_enhancements:s})}}catch(e){console.warn("Warning: Could not scan content directory: "+e)}const C={};for(const e of $){const t=e.files.map(t=>({id:t.urlPath,moduleId:e.id,metadata:{...t.processed.frontmatter},urlPath:t.urlPath}));t.length>0&&(C[e.id]=t)}if(g&&g.getPluginData().set("contentRegistry",C),r&&Object.keys(C).length>0){const e=Object.keys(C),t=Object.values(C).reduce((e,t)=>e+t.length,0);console.log(` Registry: ${t} items across ${e.length} modules (${e.join(", ")})`)}if(g&&$.length>0){console.log("šŸ”Œ Enhancing modules with plugin metadata...");const o=P("Module enhancement");o.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 g.execEnhanceModulesHooks($),o.end(),console.log(` Enhanced ${$.length} module(s)\n`);const n=t.join(v,"src",".generated");await e.mkdir(n,{recursive:!0});const s=(j.site?.base||"/").replace(/\/$/,""),a={};for(const e of $){const t={},o=e._enhancements.entries();for(const[e,n]of o)t[e]=n;Object.keys(t).length>0&&(s&&T(t,s),a[e.id]=t)}const i=t.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");const c=t.join(n,"module-enhancements.json");await e.writeFile(c,JSON.stringify(a,null,2),"utf-8"),console.log(" Wrote module enhancements to src/.generated/module-enhancements.{js,json}\n")}console.log("šŸ“¦ Syncing public assets...");const S=P("Public assets sync");if(S.start(),await syncPublicAssets(w,v,r),S.end(),console.log(" Public assets synced\n"),await p($,j.markdown?.languages,r),!1!==j.search?.enabled){console.log("šŸ” Building search index...");const o=P("Search index");o.start();const n=[],s=(j.site?.base||"/").replace(/\/$/,"");for(const t of $)for(const o of t.files)try{const t=await e.readFile(o.filePath,"utf-8"),a=await f(t,{...j.markdown,base:s});n.push({url:o.urlPath,html:a.html,title:a.frontmatter?.title||o.id,frontmatter:a.frontmatter})}catch(e){console.warn(` Warning: Could not index ${o.filePath}:`,e)}try{const o=await h(n,j.search),s=t.join(v,"public","search-index.json");await e.mkdir(t.dirname(s),{recursive:!0}),await e.writeFile(s,o),console.log(` Search index built (${n.length} pages)\n`)}catch(e){console.warn(" Warning: Failed to build search index:",e)}o.end()}if(j.markdown.markoTags?.enabled){const e=t.join(v,j.markdown.markoTags.tagsDir||"src/tags");console.log("šŸ” Scanning tags directory...");const o=P("Tag validation setup");o.start(),await c.loadAvailableTags(e),o.end(),console.log(` Found ${c.getAvailableTagsCount()} tags\n`)}else c.reset();const x=t.join(v,"src","routes");await e.mkdir(x,{recursive:!0});let D={};if(g){const e=P("Extend routes hooks");e.start(),D=await g.execExtendRoutesHooks(D),e.end(),console.log("šŸ”Œ Extended routes manifest:",Object.keys(D).length)}console.log("šŸ“ Generating routes from content...");const E=P("Route generation");E.start();const _=u??j.build.useCatchAllRoutes;console.log("šŸ“„ Pre-rendering markdown to .marko files...");const A=P("Pre-render markdown");A.start();const O=t.join(v,"src",".generated","markdown");await e.mkdir(O,{recursive:!0});const N={};let M=0;for(const o of $){const n=t.join(O,o.id);await e.mkdir(n,{recursive:!0});for(const s of o.files)try{const a=await e.readFile(s.filePath,"utf-8"),i=(j.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${m(c)}\n</div>`,d=t.join(n,s.slug+".marko");await e.writeFile(d,l),N[`${o.id}/${s.slug}`]={frontmatter:r.frontmatter,headers:r.headers||[],headTop:s.headTop||[],headBottom:s.headBottom||[]},M++}catch(e){console.warn(` Warning: Failed to pre-render ${o.id}/${s.slug}:`,e)}}const I=t.join(v,"src",".generated","content-metadata.js"),W=`// Auto-generated by MarkoPress - Do not edit\nexport default ${JSON.stringify(N,null,2)};\n`;await e.writeFile(I,W),A.end(),console.log(` Pre-rendered ${M} markdown files\n`),_?(await generateCatchAllRoutes(b,x,j,$,r,!0),console.log(" Using catch-all dynamic routes")):(await generateRoutes(b,x,j,$,r),console.log(" Using static routes")),E.end(),console.log(" Routes generated\n");const L=[];for(const[e,t]of Object.entries(D))(t.handler||t.component)&&(L.push({path:e,...t}),console.log(" Found plugin route: "+e));if(console.log(`šŸ”Œ Total manifest routes: ${Object.keys(D).length}, Plugin routes: ${L.length}`),g){const o=[...g.getPluginRoutes(),...L];if(o.length>0){console.log(`šŸ”Œ Generating ${o.length} plugin routes...`);const n=P("Plugin route generation");n.start(),await async function(o,n,s,a){for(const s of o){const o=s.path.slice(1),i=t.join(n,o,"+page");if(await e.mkdir(t.dirname(i),{recursive:!0}),s.handler){const o=t.join(t.dirname(i),"+handler.js");await e.writeFile(o,s.handler)}if(s.component){const t=i+".marko";await e.writeFile(t,s.component)}a&&console.log(" Generated plugin route: "+s.path)}}(o,x,0,r),n.end(),console.log(" Plugin routes generated\n")}}console.log("āš™ļø Generating Vite config...");const R=P("Vite config generation");if(R.start(),await generateViteConfig(v,r),R.end(),console.log(" Vite config generated\n"),g){console.log("šŸ”Œ Processing plugin allContentLoaded hooks...");const o=P("AllContentLoaded hooks");o.start(),await g.execAllContentLoadedHooks(D),o.end(),console.log(" All content processed\n");const n=g.getPluginData();if(n.has("contentRegistry")){const o=t.join(v,"src",".generated");await e.mkdir(o,{recursive:!0});const s=n.get("contentRegistry"),a=t.join(o,"content-registry.js"),i=`// Auto-generated by MarkoPress content-registry plugin - Do not edit\nexport default ${JSON.stringify(s,null,2)};\n`;await e.writeFile(a,i,"utf-8");const r=t.join(o,"content-registry.json");await e.writeFile(r,JSON.stringify(s,null,2),"utf-8"),console.log(" Wrote content registry to src/.generated/content-registry.{js,json}\n")}}if(j.markdown.markoTags?.enabled){console.log("šŸ” Validating Marko tags...");const e=P("Tag validation");e.start();const t=c.validate();if(e.end(),!t.success){const e=l(t.missingTags);return console.error(`\n${e}\n`),y.push(e),{success:!1,outDir:"",pages:0,errors:y}}console.log(" All tags validated āœ“\n")}console.log("šŸŽØ Copying theme CSS...");const G=P("Theme CSS copy");G.start(),await copyThemeCSS(v,j,r),G.end(),console.log(" Theme CSS copied\n"),console.log("šŸŽØ Extracting styles from Marko components...");const B=P("Marko component styles extraction");B.start(),await extractStylesFromMarkoTags(v,j,r),B.end(),console.log(" Component styles extracted\n");const U=[];for(const e of $)for(const t of e.files)U.push(t.urlPath);for(const e of Object.keys(D))U.includes(e)||U.push(e);const V=t.join(v,"src",".generated","static-urls.json");await e.mkdir(t.dirname(V),{recursive:!0}),await e.writeFile(V,JSON.stringify(U,null,2)),r&&console.log(` Generated static URL manifest: ${U.length} URLs`),console.log("šŸ”Ø Building with @marko/run...");const H=P("@marko/run build");H.start();const J=i||j.build.outDir,Y=await async function(e,n,s){return new Promise(a=>{const i=["build"];e&&i.push("--output",e),n&&i.push("--debug");const r=o("npx",["marko-run",...i],{stdio:"inherit",cwd:s});r.on("close",o=>{if(0===o){const o=e||"dist";a({success:!0,outDir:t.join(s,o),errors:[]})}else a({success:!1,outDir:"",errors:["Build process exited with code "+o]})}),r.on("error",e=>{a({success:!1,outDir:"",errors:["Failed to start build process: "+e.message]})})})}(J,r,v);if(H.end(),!Y.success)return y.push(...Y.errors),{success:!1,outDir:"",pages:0,errors:y};const q=P("Collect build assets");q.start();const z=await async function(t){const o=[];try{const n=await e.readdir(t,{recursive:!0});for(const e of n)"string"==typeof e&&(e.endsWith(".js")||e.endsWith(".css")||e.endsWith(".json"))&&o.push(e)}catch(e){console.warn("Warning: Could not collect build assets:",e)}return o}(Y.outDir);if(q.end(),g){console.log("šŸ”Œ Processing plugin postBuild hooks...");const e=P("Post-build hooks");e.start(),await g.execPostBuildHooks(j,Y.outDir,D,z),e.end(),console.log(" Post-build hooks completed\n")}console.log("šŸ“¦ Copying Marko tags directory...");const K=P("Copy tags directory");K.start(),await copyTagsDirectory(w,Y.outDir,j,r),K.end(),console.log(" Tags directory copied\n"),console.log("\nāœ… Build completed successfully!"),console.log(" Output: "+Y.outDir),console.log(" Pages: Generated dynamically at request time"),console.log("\nā±ļø Build timing:");const Q=Array.from(k.entries()).sort((e,t)=>t[1]-e[1]);for(const[e,t]of Q){const o=(t/1e3).toFixed(2);console.log(` ${"ā–ˆ".repeat(Math.min(Math.floor(t/100),20))} ${e}: ${o}s`)}return{success:!0,outDir:Y.outDir,pages:0,errors:y}}catch(e){const t=e instanceof Error?e.message:e+"";return y.push(t),console.error("\nāŒ Build failed:",t),{success:!1,outDir:"",pages:0,errors:y}}}function T(e,t){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(t)?t+e.link:e.link})):e.items}))),Array.isArray(e.blogPosts)&&(e.blogPosts=e.blogPosts.map(e=>({...e,link:e.link&&!e.link.startsWith(t)?t+e.link:e.link})))}export async function generateRoutes(e,o,n,s,a){await cleanupGeneratedRoutes(o,e,a);const i=[];let r=0,c=0,l=0;for(const[t,o]of Object.entries(e)){if(!Array.isArray(o))continue;i.push(t);const e=o;if("pages"===t)for(const t of e)await P(0,0,0,0,a),r++;else if("blog"===t)for(const t of e)await F(0,0,0,0,a),l++;else for(const t of e)await $(0,0,0,0,a),c++}await C(o,n,a);const d=t.resolve(o,"..","..");await generateViteConfig(d,a),await S(o,n,a),a&&(console.log(` Generated ${r} page routes`),console.log(` Generated ${c} doc routes`),console.log(` Generated ${l} blog routes`))}export async function cleanupGeneratedRoutes(o,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(o,{recursive:!0,withFileTypes:!0});for(const c of n){if(!c.isFile())continue;const n=t.join(c.path||c.parentPath||o,c.name),l=t.relative(o,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 t=e instanceof Error?e.message:e+"";a.push(`Failed to delete ${l}: ${t}`)}}}for(const e of r){const n=t.join(o,e);try{await v(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 v(o){try{const n=await e.readdir(o,{withFileTypes:!0});for(const e of n)if(e.isDirectory()){const n=t.join(o,e.name);await v(n)}0===(await e.readdir(o)).length&&await e.rmdir(o)}catch{}}async function P(e,t,o,n,s){s&&console.log(" Warning: Static routes deprecated, use catch-all routes")}async function $(e,t,o,n,s){s&&console.log(" Warning: Static routes deprecated, use catch-all routes")}async function F(e,t,o,n,s){s&&console.log(" Warning: Static routes deprecated, use catch-all routes")}async function C(o,n,s){const a=t.join(o,"_config.js"),i={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||{},_headInject:n._headInject||void 0},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 S(o,n,s){const a=t.join(o,"+layout.marko"),i=(n.theme,n.site?.title||"MarkoPress"),r=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 x("layout.marko.template",{SITE_TITLE:i,THEME_STYLE:r,BASE_PATH:c,COMPONENT_STYLES_LINK:l});await e.writeFile(a,d),s&&console.log(" Generated: "+a)}async function x(o,n){const s=t.dirname(new URL(import.meta.url).pathname),a=t.join(s,"..",".."),i=t.join(a,"templates",o);let r=await e.readFile(i,"utf-8");r="true"===n.IS_BUILD?r.replace(/\/\/ \{\{IF_DEV_START\}\}[\s\S]*?\/\/ \{\{IF_DEV_END\}\}/g,""):r.replace(/\/\/ \{\{IF_BUILD_START\}\}[\s\S]*?\/\/ \{\{IF_BUILD_END\}\}/g,""),r=r.replace(/\/\/ \{\{IF_(?:BUILD|DEV)_(?:START|END)\}\}\n?/g,"");for(const[e,t]of Object.entries(n))r=r.replace(RegExp(`\\{\\{${e}\\}\\}`,"g"),t);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(t.isAbsolute(e))throw Error(`Theme name cannot be an absolute path: "${e}"`)}export async function generateViteConfig(o,n){const s=t.join(o,"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(o,n,s){const a=t.join(o,"public","_markopress","theme");await e.mkdir(a,{recursive:!0});const i=n.theme?.name||"@markopress/theme-default";try{validateThemeName(i)}catch(e){const t=e instanceof Error?e.message:e+"";throw Error("Security: "+t)}const r=n.theme?.options?.style||"default",c=`theme-${r}.css`,l=[...b(i)?[t.join(k,"public",c)]:[],t.join(o,"..","node_modules",i,"public",c),t.join(o,"node_modules",i,"public",c)];let d=null,u=null;for(const t of l)try{await e.access(t),d=await e.readFile(t,"utf-8"),u=t;break}catch{}if(!d){console.warn(` Warning: Could not find ${c}, using minimal fallback`);const o=`/* Minimal fallback CSS for style: ${r} */\nbody { font-family: system-ui, sans-serif; margin: 0; padding: 0; }`,n=t.join(a,c);return void await e.writeFile(n,o)}const g=t.join(a,c);await e.writeFile(g,d),s&&(console.log(` Copied ${c} from: ${u}`),console.log(" Output: "+g));const m="styles.css",f=[...b(i)?[t.join(k,m)]:[],t.join(o,"..","node_modules",i,"src",m),t.join(o,"node_modules",i,"src",m)];for(const o of f)try{await e.access(o);const n=await e.readFile(o,"utf-8"),i=t.join(a,m);await e.writeFile(i,n),s&&(console.log(` Copied ${m} from: ${o}`),console.log(" Output: "+i));break}catch{}}export async function extractStylesFromMarkoTags(o,n,s){const a=n.markdown?.markoTags?.tagsDir||"src/tags",i=t.join(o,a),r=t.join(o,"public");await e.mkdir(r,{recursive:!0});const c=t.join(r,"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(i)}catch{return void(s&&console.log(" No tags directory found at: "+i))}const l=[];if(await async function o(n){const s=await e.readdir(n,{withFileTypes:!0});for(const e of s){const s=t.join(n,e.name);e.isDirectory()?await o(s):e.isFile()&&e.name.endsWith(".marko")&&l.push(s)}}(i),0===l.length)return void(s&&console.log(" No .marko files found in: "+i));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 o of l){const n=t.relative(i,o),s=""===t.dirname(n)?t.basename(n,".marko"):t.join(t.dirname(n),t.basename(n,".marko"));try{const t=await e.readFile(o,"utf-8"),n=/<style\b[^>]*>([\s\S]*?)<\/style>/gi,a=Array.from(t.matchAll(n));if(a.length>0){d.push(`/* ${s}.marko */`);for(const e of a){const t=e[1]||"";if(t){const e=t.split("\n");let o=0;for(;o<e.length&&""===e[o].trim();)o++;let n=e.length-1;for(;n>=o&&""===e[n].trim();)n--;for(let t=o;t<=n;t++){const o=e[t];if(""===o.trim()){d.push("");continue}const n=o.match(/^(\s*)/),s=n?n[1].length:0,a=" ".repeat(Math.floor(s/2)),i=o.trim().replace(/:global\(([^)]+)\)/g,"$1");d.push(a+i)}}}d.push("")}}catch(e){console.warn(` Warning: Could not read file ${o}:`,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 D(o){const n=await e.readdir(o,{withFileTypes:!0}),s=await Promise.all(n.map(e=>{const n=t.resolve(o,e.name);return e.isDirectory()?D(n):n}));return Array.prototype.concat(...s).filter(e=>e.endsWith(".marko"))}export async function copyThemeComponents(o,n,s){const a=n.theme?.name||"@markopress/theme-default",i=t.join(o,"src"),r=t.join(i,"tags");await e.mkdir(r,{recursive:!0});const c=[...b(a)?[t.join(k,"tags")]:[],t.join(o,"..","node_modules",a,"dist","tags"),t.join(o,"node_modules",a,"dist","tags"),t.join(o,"..","node_modules",a,"src","components"),t.join(o,"node_modules",a,"src","components")];let l=null;for(const t of c)try{await e.access(t),l=t;break}catch{}if(!l)return void(s&&console.warn(" Warning: Could not find theme components, skipping"));const d=await D(l);let u=0;for(const o of d){const n=t.relative(l,o),a=t.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(t.dirname(a),{recursive:!0}),await e.copyFile(o,a),u++)}s&&(console.log(` Copied ${u} theme components from: ${l}`),console.log(" Output: "+r))}export async function copyTagsDirectory(o,n,s,a){const i=s.markdown?.markoTags?.tagsDir||"src/tags",r=t.join(o,i),c=t.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 o of l){const n=t.join(r,o.name),s=t.join(c,o.name);if(o.isDirectory()){await e.mkdir(s,{recursive:!0});const o=await e.readdir(n,{withFileTypes:!0});for(const a of o){const o=t.join(n,a.name),i=t.join(s,a.name);a.isDirectory()||(await e.copyFile(o,i),d++)}}else o.isFile()&&(await e.copyFile(n,s),d++)}a&&(console.log(` Copied ${d} tag files from: ${r}`),console.log(" Output: "+c))}async function E(o,n,s=o){const a=await e.readdir(o,{withFileTypes:!0}),i=[];for(const r of a){const a=t.join(o,r.name),c=t.join(n,r.name);r.isDirectory()?(await e.mkdir(c,{recursive:!0}),i.push(...await E(a,c,s))):r.isFile()&&(await e.copyFile(a,c),i.push(t.relative(s,a)))}return i}async function _(o,n){await e.mkdir(t.dirname(o),{recursive:!0}),await e.writeFile(o,JSON.stringify(n,null,2))}async function A(o,n,s){const a=new Set(s),i=n.filter(e=>!a.has(e)).sort((e,t)=>t.length-e.length);for(const n of i){const s=t.join(o,n);await e.rm(s,{force:!0}),await O(t.dirname(s),o)}}async function O(o,n){let s=o;for(;s.startsWith(n)&&s!==n;){let o;try{o=await e.readdir(s)}catch{break}if(o.length>0)break;await e.rmdir(s),s=t.dirname(s)}}export async function generateCatchAllRoutes(o,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=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 o=t.join(n,"$$slug");await e.mkdir(o,{recursive:!0});const s=await x("catch-all-handler.js.template",{CONTENT_TYPE:"root",CONFIG_PATH:"../_config.js",VITE_PLUGIN_PATH:"markopress/build",IS_BUILD:r?"true":"false"});await e.writeFile(t.join(o,"+handler.js"),s);const a=await x("catch-all-page.marko.template",{CONTENT_TYPE_CLASS:"page"});await e.writeFile(t.join(o,"+page.marko"),a),i&&console.log(" Generated root pages catch-all route")}for(const o of d){if("root"===o)continue;const s=t.join(n,o,"$$slug");await e.mkdir(s,{recursive:!0});const a=await x("catch-all-handler.js.template",{CONTENT_TYPE:o,CONFIG_PATH:"../../_config.js",VITE_PLUGIN_PATH:"markopress/build",IS_BUILD:r?"true":"false"});await e.writeFile(t.join(s,"+handler.js"),a);const c=await x("catch-all-page.marko.template",{CONTENT_TYPE_CLASS:o});await e.writeFile(t.join(s,"+page.marko"),c),i&&console.log(` Generated ${o} catch-all route`)}await C(n,s,i);const u=t.resolve(n,"..","..");await generateViteConfig(u,i),await S(n,s,i)}export{u as loadMarkdownModule,g as registerMarkdownContent};export{markdownContentPlugin}from"./vite-markdown-plugin.js";
@@ -122,6 +122,14 @@ export interface MarkdownConfig {
122
122
  light?: string;
123
123
  dark?: string;
124
124
  };
125
+ /**
126
+ * Additional languages to preload for syntax highlighting.
127
+ * By default, markopress auto-detects languages from code blocks in your content.
128
+ * Use this to pre-load languages that may be missed or to speed up builds.
129
+ *
130
+ * @example ['python', 'vue', 'rust']
131
+ */
132
+ languages?: string[];
125
133
  /**
126
134
  * Marko tags support
127
135
  */
@@ -55,6 +55,7 @@ export declare const MarkoPressConfigSchema: z.ZodObject<{
55
55
  light: z.ZodOptional<z.ZodString>;
56
56
  dark: z.ZodOptional<z.ZodString>;
57
57
  }, z.core.$strip>>;
58
+ languages: z.ZodOptional<z.ZodArray<z.ZodString>>;
58
59
  markoTags: z.ZodOptional<z.ZodObject<{
59
60
  enabled: z.ZodOptional<z.ZodBoolean>;
60
61
  tagsDir: z.ZodOptional<z.ZodString>;
@@ -139,6 +140,7 @@ export declare function validateConfig(config: unknown): {
139
140
  light?: string;
140
141
  dark?: string;
141
142
  };
143
+ languages?: string[];
142
144
  markoTags?: {
143
145
  enabled?: boolean;
144
146
  tagsDir?: string;
@@ -236,6 +238,7 @@ export declare function validateConfigSafe(config: unknown): {
236
238
  light?: string;
237
239
  dark?: string;
238
240
  };
241
+ languages?: string[];
239
242
  markoTags?: {
240
243
  enabled?: boolean;
241
244
  tagsDir?: string;
@@ -1 +1 @@
1
- import{z as o}from"zod";const t=o.object({type:o.enum(["meta","link","script","base"])}).passthrough(),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(t).optional()}),a=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(),a),i=o.object({text:o.string().min(1,{message:"Nav item text is required"}),link:o.string().min(1,{message:"Nav item link is required"})}),s=o.object({text:o.string().min(1,{message:"Sidebar item text is required"}),link:o.string().min(1,{message:"Sidebar item link is required"})}),r=o.record(o.string(),o.union([o.array(s),o.object({autoGenerate:o.boolean()})])),l=o.object({navbar:o.array(i).optional(),sidebar:r.optional()}).passthrough(),p=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:l.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()}),m=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(),c=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()})]),u=o.object({hostname:o.string().optional(),exclude:o.array(o.string()).optional(),transformItems:o.any().optional()}).passthrough(),b=o.object({userAgent:o.union([o.string(),o.array(o.string())]).optional(),allow:o.array(o.string()).optional(),disallow:o.array(o.string()).optional(),crawlDelay:o.number().nonnegative().optional(),sitemap:o.string().optional()}).passthrough(),d=o.object({sitemap:u.optional(),robots:b.optional()}).passthrough().optional();export const MarkoPressConfigSchema=o.object({site:e,contentDir:o.string().optional(),content:n.optional(),theme:p.optional(),markdown:g.optional(),build:m.optional(),search:o.object({enabled:o.boolean().optional()}).passthrough().optional(),seo:d,plugins:o.array(c).optional()});export function validateConfig(o){return MarkoPressConfigSchema.parse(o)}export function validateConfigSafe(o){const t=MarkoPressConfigSchema.safeParse(o);return t.success?{success:!0,data:t.data}:{success:!1,errors:t.error.issues.map(o=>({path:o.path.join("."),message:o.message}))}}
1
+ import{z as o}from"zod";const t=o.object({type:o.enum(["meta","link","script","base"])}).passthrough(),a=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(t).optional()}),e=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(),e),i=o.object({text:o.string().min(1,{message:"Nav item text is required"}),link:o.string().min(1,{message:"Nav item link is required"})}),s=o.object({text:o.string().min(1,{message:"Sidebar item text is required"}),link:o.string().min(1,{message:"Sidebar item link is required"})}),r=o.record(o.string(),o.union([o.array(s),o.object({autoGenerate:o.boolean()})])),l=o.object({navbar:o.array(i).optional(),sidebar:r.optional()}).passthrough(),p=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:l.optional()}),g=o.object({lineNumbers:o.boolean().optional(),theme:o.object({light:o.string().optional(),dark:o.string().optional()}).optional(),languages:o.array(o.string()).optional(),markoTags:o.object({enabled:o.boolean().optional(),tagsDir:o.string().optional()}).optional()}),m=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(),c=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()})]),u=o.object({hostname:o.string().optional(),exclude:o.array(o.string()).optional(),transformItems:o.any().optional()}).passthrough(),b=o.object({userAgent:o.union([o.string(),o.array(o.string())]).optional(),allow:o.array(o.string()).optional(),disallow:o.array(o.string()).optional(),crawlDelay:o.number().nonnegative().optional(),sitemap:o.string().optional()}).passthrough(),d=o.object({sitemap:u.optional(),robots:b.optional()}).passthrough().optional();export const MarkoPressConfigSchema=o.object({site:a,contentDir:o.string().optional(),content:n.optional(),theme:p.optional(),markdown:g.optional(),build:m.optional(),search:o.object({enabled:o.boolean().optional()}).passthrough().optional(),seo:d,plugins:o.array(c).optional()});export function validateConfig(o){return MarkoPressConfigSchema.parse(o)}export function validateConfigSafe(o){const t=MarkoPressConfigSchema.safeParse(o);return t.success?{success:!0,data:t.data}:{success:!1,errors:t.error.issues.map(o=>({path:o.path.join("."),message:o.message}))}}
package/dist/dev/index.js CHANGED
@@ -1 +1 @@
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,syncPublicAssets as m}from"../build/index.js";import{buildSearchIndex as p}from"../search/index.js";import{renderMarkdown as g}from"../markdown/renderer.js";export async function startDevServer(r={}){console.log("šŸš€ Starting MarkoPress dev server...\n");const u=r.root||process.cwd();let h=await n(u,{mode:"development",command:"dev"});console.log("āœ“ Config loaded from "+h.root);const w=o.join(u,".markopress"),f=await e.stat(w).then(e=>e.isDirectory()).catch(()=>!1)?w:u;let v;h.plugins&&h.plugins.length>0&&(v=new s(h),await v.loadPlugins(h.plugins),h=v.getConfig(),await v.execLoadContentHooks());const x=o.join(f,"src",".generated","markdown");await e.rm(x,{recursive:!0,force:!0});const k={},y=[],j=o.resolve(u,h.contentDir);try{const t=await e.readdir(j,{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||j,e.name),n=o.relative(j,t),s=l(t,j),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)y.push({id:e,dir:o.join(j,"root"===e?"":e),files:t})}catch(e){console.warn("Warning: Could not scan content directory: "+e)}console.log("šŸ“ Generating routes from content...");const S=o.join(f,"src","routes"),C=r.useCatchAllRoutes??h.build.useCatchAllRoutes;await e.mkdir(S,{recursive:!0});let b={};if(v&&(b=await v.execExtendRoutesHooks(b)),C?(await c(k,S,h,y,!1,!1),console.log(" Using catch-all dynamic routes")):(await i(k,S,h,y,!1),console.log(" Using static routes")),console.log(" Routes generated\n"),v&&await v.execAllContentLoadedHooks(b),console.log("šŸ“¦ Syncing public assets..."),await m(u,f,!1),console.log(" Public assets synced\n"),console.log("šŸŽØ Copying theme CSS..."),await a(f,h,!1),console.log(" Theme CSS copied\n"),h.markdown?.markoTags?.enabled&&(console.log("šŸŽØ Extracting custom tag styles..."),await d(f,h,!1),console.log(" Custom tag styles extracted\n")),!1!==h.search?.enabled){console.log("šŸ” Building search index...");const t=[],r=o.resolve(u,h.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 g(a,h.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 p(t,h.search),n=o.join(f,"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 P=t("npx",["marko-run",...T],{stdio:"inherit",cwd:f});P.on("error",e=>{console.error("Failed to start dev server:",e),process.exit(1)}),P.on("exit",e=>{0!==e&&(console.error("Dev server exited with code "+e),process.exit(e||1))}),process.on("SIGINT",()=>{P.kill("SIGINT"),process.exit(0)}),process.on("SIGTERM",()=>{P.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 a,copyThemeCSS as i,generateCatchAllRoutes as c,filePathToUrl as l,extractStylesFromMarkoTags as d,syncPublicAssets as m}from"../build/index.js";import{buildSearchIndex as p}from"../search/index.js";import{renderMarkdown as g}from"../markdown/renderer.js";import{preloadContentLanguages as u}from"../markdown/loader.js";export async function startDevServer(r={}){console.log("šŸš€ Starting MarkoPress dev server...\n");const h=r.root||process.cwd();let w=await n(h,{mode:"development",command:"dev"});console.log("āœ“ Config loaded from "+w.root);const f=o.join(h,".markopress"),v=await e.stat(f).then(e=>e.isDirectory()).catch(()=>!1)?f:h;let x;w.plugins&&w.plugins.length>0&&(x=new s(w),await x.loadPlugins(w.plugins),w=x.getConfig(),await x.execLoadContentHooks());const k=o.join(v,"src",".generated","markdown");await e.rm(k,{recursive:!0,force:!0});const y={},j=[],S=o.resolve(h,w.contentDir);try{const t=await e.readdir(S,{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||S,e.name),n=o.relative(S,t),s=l(t,S),a=n.split(o.sep),i=1===a.length?"root":a[0];r.has(i)||r.set(i,[]),r.get(i).push({id:e.name.replace(".md",""),slug:e.name.replace(".md",""),filePath:t,urlPath:s,directory:i})}for(const[e,t]of r)j.push({id:e,dir:o.join(S,"root"===e?"":e),files:t})}catch(e){console.warn("Warning: Could not scan content directory: "+e)}console.log("šŸ“ Generating routes from content...");const C=o.join(v,"src","routes"),b=r.useCatchAllRoutes??w.build.useCatchAllRoutes;await e.mkdir(C,{recursive:!0});let F={};if(x&&(F=await x.execExtendRoutesHooks(F)),b?(await c(y,C,w,j,!1,!1),console.log(" Using catch-all dynamic routes")):(await a(y,C,w,j,!1),console.log(" Using static routes")),console.log(" Routes generated\n"),x&&await x.execAllContentLoadedHooks(F),console.log("šŸ“¦ Syncing public assets..."),await m(h,v,!1),console.log(" Public assets synced\n"),console.log("šŸŽØ Copying theme CSS..."),await i(v,w,!1),console.log(" Theme CSS copied\n"),w.markdown?.markoTags?.enabled&&(console.log("šŸŽØ Extracting custom tag styles..."),await d(v,w,!1),console.log(" Custom tag styles extracted\n")),await u(j,w.markdown?.languages),!1!==w.search?.enabled){console.log("šŸ” Building search index...");const t=[],r=o.resolve(h,w.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),a=l(n,r),i=await e.readFile(n,"utf-8"),c=await g(i,w.markdown),d=s.name.replace(".md","");t.push({url:a,html:c.html,title:c.frontmatter?.title||d,frontmatter:c.frontmatter})}}catch{}try{const r=await p(t,w.search),n=o.join(v,"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 T=r.port||3e3,P=["dev"];T&&P.push("--port",T+"");const I=t("npx",["marko-run",...P],{stdio:"inherit",cwd:v});I.on("error",e=>{console.error("Failed to start dev server:",e),process.exit(1)}),I.on("exit",e=>{0!==e&&(console.error("Dev server exited with code "+e),process.exit(e||1))}),process.on("SIGINT",()=>{I.kill("SIGINT"),process.exit(0)}),process.on("SIGTERM",()=>{I.kill("SIGTERM"),process.exit(0)})}
@@ -22,6 +22,13 @@ export interface CodeBlockOptions {
22
22
  * Language aliases
23
23
  */
24
24
  languageAlias?: Record<string, string>;
25
+ /**
26
+ * Shiki themes for dual-theme highlighting
27
+ */
28
+ theme?: {
29
+ light?: string;
30
+ dark?: string;
31
+ };
25
32
  }
26
33
  export interface CodeBlockMeta {
27
34
  /**
@@ -1 +1 @@
1
- import{hastToHtml as e}from"shiki";export function parseCodeMeta(e,t=!0){const n={highlightLines:new Set,lineNumbers:t},s=e.match(/^([a-z0-9-]+)/);s&&(n.lang=s[1]);const i=e.match(/\{([0-9,-]+)\}/);if(i){const e=i[1].split(",");for(const t of e)if(t.includes("-")){const[e,s]=t.split("-").map(Number);for(let t=e;t<=s;t++)n.highlightLines.add(t)}else n.highlightLines.add(Number(t))}const r=e.match(/title="([^"]+)"|title='([^']+)'/);return r&&(n.title=r[1]||r[2]),e.includes(":line-numbers")||e.includes(":ln")?n.lineNumbers=!0:(e.includes(":no-line-numbers")||e.includes(":line-numbers=false"))&&(n.lineNumbers=!1),n}export function processCodeLines(e){const t=e.split("\n"),n=[];for(const e of t){const t={content:e,classes:[]};e.includes("// [!code --]")||e.includes("// [!code -]")?(t.isDiff=!0,t.diffType="remove",t.classes.push("diff","remove"),t.content=e.replace(/\/\/ \[!code --?\]/,"").trim()):(e.includes("// [!code ++]")||e.includes("// [!code +]"))&&(t.isDiff=!0,t.diffType="add",t.classes.push("diff","add"),t.content=e.replace(/\/\/ \[!code \+\+?\]/,"").trim()),e.includes("// [!code focus]")&&(t.isFocus=!0,t.classes.push("focus"),t.content=t.content.replace(/\/\/ \[!code focus\]/,"").trim()),e.includes("// [!code error]")&&(t.isError=!0,t.classes.push("error"),t.content=t.content.replace(/\/\/ \[!code error\]/,"").trim()),e.includes("// [!code warning]")&&(t.isWarning=!0,t.classes.push("warning"),t.content=t.content.replace(/\/\/ \[!code warning\]/,"").trim()),n.push(t)}return n}export function createEnhancedHighlighter(i,r={}){return(l,c,o)=>{const a=parseCodeMeta(o,r.lineNumbers??!0),u=r.languageAlias?.[c]||c,p=processCodeLines(l),h=p.map(e=>e.content).join("\n");let d;try{d=i.codeToHast(h,{lang:u,themes:{light:"github-light",dark:"github-dark"}})}catch(e){return e.message?.includes("language")&&console.warn(`[Shiki] Language "${u}" not supported, falling back to plain text. Run build with debug=true to see available languages.`),`<pre class="shiki"><code>${s(l)}</code></pre>`}!function(e,s,i){const r=t(e,"pre");if(!r)return;const l=r.properties?.className||[],c=[...(Array.isArray(l)?l:[l]).filter(Boolean),s.lineNumbers?"line-numbers":"",s.highlightLines?.size?"has-highlights":""].filter(Boolean);r.properties={...r.properties,className:c};const o=t(r,"code");if(!o)return;const a=function(e){const t=[];let n=[];for(const s of e)if("text"===s.type){const e=s.value.split("\n");for(let s=0;s<e.length;s++)s>0&&(t.push(n),n=[]),e[s]&&n.push({type:"text",value:e[s]})}else n.push(s);return n.length>0&&t.push(n),t}(o.children||[]),u=[];for(let e=0;e<a.length;e++){const t=a[e],r=e+1,l=i[e]||{classes:[]};if(0===t.length||1===t.length&&n(t[0]))continue;const c=["line"];s.highlightLines?.has(r)&&c.push("highlighted"),c.push(...l.classes);const o=t.find(e=>"element"===e.type);if(o){const e=o.properties?.className||[],t=[...(Array.isArray(e)?e:[e]).filter(Boolean),...c];o.properties={...o.properties,className:t},s.lineNumbers&&(o.properties["data-line"]=r)}else if(t[0]){const e={type:"element",tagName:"span",properties:{className:c,...s.lineNumbers&&{"data-line":r}},children:t};u.push(e);continue}u.push(...t)}o.children=[];for(let e=0;e<u.length;e++)o.children.push(u[e]),e<u.length-1&&o.children.push({type:"text",value:"\n"})}(d,a,p);let f=e(d);return a.title&&(f=`<div class="code-block">\n <div class="code-title">${s(a.title)}</div>\n ${f}\n</div>`),f}}function t(e,n){if("element"!==e.type)return null;if(e.tagName===n)return e;for(const s of e.children||[])if("element"===s.type){const e=t(s,n);if(e)return e}return null}function n(e){return"text"===e.type&&!e.value}function s(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#039;")}
1
+ import{hastToHtml as e}from"shiki";export function parseCodeMeta(e,s=!0){const t={highlightLines:new Set,lineNumbers:s},n=e.match(/^([a-z0-9-]+)/);n&&(t.lang=n[1]);const i=e.match(/\{([0-9,-]+)\}/);if(i){const e=i[1].split(",");for(const s of e)if(s.includes("-")){const[e,n]=s.split("-").map(Number);for(let s=e;s<=n;s++)t.highlightLines.add(s)}else t.highlightLines.add(Number(s))}const r=e.match(/title="([^"]+)"|title='([^']+)'/);return r&&(t.title=r[1]||r[2]),e.includes(":line-numbers")||e.includes(":ln")?t.lineNumbers=!0:(e.includes(":no-line-numbers")||e.includes(":line-numbers=false"))&&(t.lineNumbers=!1),t}export function processCodeLines(e){const s=e.split("\n"),t=[];for(const e of s){const s={content:e,classes:[]};e.includes("// [!code --]")||e.includes("// [!code -]")?(s.isDiff=!0,s.diffType="remove",s.classes.push("diff","remove"),s.content=e.replace(/\/\/ \[!code --?\]/,"").trim()):(e.includes("// [!code ++]")||e.includes("// [!code +]"))&&(s.isDiff=!0,s.diffType="add",s.classes.push("diff","add"),s.content=e.replace(/\/\/ \[!code \+\+?\]/,"").trim()),e.includes("// [!code focus]")&&(s.isFocus=!0,s.classes.push("focus"),s.content=s.content.replace(/\/\/ \[!code focus\]/,"").trim()),e.includes("// [!code error]")&&(s.isError=!0,s.classes.push("error"),s.content=s.content.replace(/\/\/ \[!code error\]/,"").trim()),e.includes("// [!code warning]")&&(s.isWarning=!0,s.classes.push("warning"),s.content=s.content.replace(/\/\/ \[!code warning\]/,"").trim()),t.push(s)}return t}export function createEnhancedHighlighter(n,i={}){return(r,c,l)=>{const o=parseCodeMeta(l,i.lineNumbers??!0),a=i.languageAlias?.[c]||c,u=processCodeLines(r),p=u.map(e=>e.content).join("\n");let d;try{d=n.codeToHast(p,{lang:a,themes:{light:i.theme?.light||"vitesse-light",dark:i.theme?.dark||"vitesse-dark"}})}catch(e){return e.message?.includes("language")&&console.warn(`[Shiki] Language "${a}" not supported, falling back to plain text. Run build with debug=true to see available languages.`),`<pre class="shiki"><code>${t(r)}</code></pre>`}!function(e,t,n){const i=s(e,"pre");if(!i)return;const r=i.properties?.class??i.properties?.className??[],c=[...(Array.isArray(r)?r:[r]).filter(Boolean),t.lineNumbers?"line-numbers":"",t.highlightLines?.size?"has-highlights":""].filter(Boolean);i.properties={...i.properties,class:c},delete i.properties.className;const l=s(i,"code");if(!l)return;const o=(l.children||[]).filter(e=>"element"===e.type&&"span"===e.tagName&&function(e){const s=e.properties?.class??e.properties?.className;return!!s&&(Array.isArray(s)?s:[s]).includes("line")}(e));for(let e=0;e<o.length;e++){const s=o[e],i=e+1,r=n[e]||{classes:[]},c=["line"];t.highlightLines?.has(i)&&c.push("highlighted"),c.push(...r.classes),s.properties={...s.properties,class:c},delete s.properties.className,t.lineNumbers&&(s.properties["data-line"]=i)}}(d,o,u);let h=e(d);return o.title&&(h=`<div class="code-block">\n <div class="code-title">${t(o.title)}</div>\n ${h}\n</div>`),h}}function s(e,t){if("element"===e.type&&e.tagName===t)return e;for(const n of e.children||[])if("element"===n.type){const e=s(n,t);if(e)return e}return null}function t(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#039;")}
@@ -28,3 +28,17 @@ export declare function validateFrontmatter(frontmatter: Record<string, unknown>
28
28
  valid: boolean;
29
29
  errors: string[];
30
30
  };
31
+ /**
32
+ * Extract unique language identifiers from backtick-fenced code blocks in markdown content.
33
+ * Strips meta attributes (e.g. ```python {1,3} → "python").
34
+ */
35
+ export declare function scanCodeBlockLanguages(content: string): string[];
36
+ /**
37
+ * Scan content modules for code block languages, merge with config languages,
38
+ * and preload them into the Shiki highlighter.
39
+ */
40
+ export declare function preloadContentLanguages(modules: Array<{
41
+ files: Array<{
42
+ filePath: string;
43
+ }>;
44
+ }>, configLanguages?: string[], debug?: boolean): Promise<void>;
@@ -1 +1 @@
1
- import e from"gray-matter";import t from"markdown-it";import r from"markdown-it-anchor";import a from"markdown-it-attrs";import*as s from"markdown-it-emoji";import{createHighlighter as o}from"shiki";import{setupContainers as n,setupDetails as i}from"./containers.js";import{createEnhancedHighlighter as c}from"./code.js";import{preprocessIncludesWithRegions as l}from"./includes.js";import{preserveTagsPlugin as p}from"./preserve-tags.js";import{globalTagValidator as g}from"./tag-validator.js";import{basePathPlugin as u}from"./base-path-plugin.js";import{mdLinkPlugin as m}from"./md-link-plugin.js";import{resolveImageTagsInHtml as d}from"../image/tag.js";let f=null;const h=new Set,y=["javascript","typescript","js","ts","bash","markdown","md"],b={js:"javascript",ts:"typescript",py:"python",rs:"rust",sh:"bash",shell:"bash",yml:"yaml",cs:"csharp",cpp:"c++"};function w(e){return b[e]||e}async function k(){return f||(f=await o({themes:["github-light","github-dark"],langs:Array.from(y)}),y.forEach(e=>h.add(w(e)))),f}export async function preloadLanguages(e){f||await k();const t=[];for(const r of e){const e=w(r);h.has(e)||t.push((async()=>{try{const{bundledLanguages:t}=await import("shiki/langs");e in t&&(await f.loadLanguage(t[e]),h.add(e))}catch{}})())}await Promise.all(t)}export async function getMarkdownIt(e={},o={}){const l=await k(),d=c(l,{lineNumbers:e.lineNumbers??!0}),f=new t({html:!0,linkify:!0,typographer:!0,highlight:(e,t,r)=>{if(!t)return"";try{return d(e,t,r)}catch(e){return""}}});return f.use(r,{slugify:_,permalink:r.permalink.linkInsideHeader({symbol:"#",placement:"before"})}),f.use(a),f.use(s.full||s.bare),n(f),i(f),e.markoTags?.enabled&&f.use(p,{tagsDir:e.markoTags?.tagsDir||"tags/",onTagDetected:(e,t)=>{g.addDetectedTag(e,o.filePath||"unknown",t)}}),f.use(m),e.base&&f.use(u,e.base),f}export async function parseMarkdown(t,r={},a={},s){const{data:o,content:n,excerpt:i}=e(t,{excerpt:!0,excerpt_separator:"\x3c!-- more --\x3e"}),c=await l(n,{root:a.rootDir??process.cwd(),currentFile:a.filePath??""}),p=s||await async function(e,t){return getMarkdownIt(e,t)}(r,a),g=await d(p.render(c,a),{base:r.base??void 0}),u=a.extractToc?function(e){const t=[],r=e.replace(/```[\s\S]*?```/g,"").replace(/~~~[\s\S]*?~~~/g,"").replace(/^(\t| {4}).+$/gm,""),a=/^(#{1,6})\s+(.+)$/gm;let s;for(;null!==(s=a.exec(r));){const e=s[1].length,r=s[2].trim(),a=v(r),o=_(r);t.push({level:e,title:a,slug:o})}return t}(c):[];return{frontmatter:o,content:c,html:g,excerpt:i,headers:$(u)}}function v(e){return e.replace(/`([^`]+)`/g,"$1").replace(/\*\*\*\+([^*]+)\*\*\+/g,"$1").replace(/___+([^_]+)___+/g,"$1").replace(/\*\*([^*]+)\*\*/g,"$1").replace(/__([^_]+)__/g,"$1").replace(/\*([^*]+)\*/g,"$1").replace(/_([^_]+)_/g,"$1").replace(/\[([^\]]+)\]\([^)]+\)/g,"$1").replace(/\[([^\]]+)\]\[[^\]]+\]/g,"$1").trim()}function $(e){const t=[],r=[];for(const a of e){const e={level:a.level,title:a.title,slug:a.slug,children:[]};for(;r.length>0&&r[r.length-1].level>=a.level;)r.pop();0===r.length?t.push(e):r[r.length-1].children.push(e),r.push(e)}return t}function _(e){return e.trim().replace(/([a-z])([A-Z])/g,"$1-$2").replace(/([0-9])([a-zA-Z])/g,"$1-$2").replace(/([a-zA-Z])([0-9])/g,"$1-$2").toLowerCase().replace(/\s+/g,"-").replace(/[^\w\u00A0-\uFFFF\-]+/g,"").replace(/^-+|-+$/g,"").replace(/-+/g,"-")}export function validateFrontmatter(e){const t=[];return void 0!==e.title&&"string"!=typeof e.title&&t.push("title must be a string"),void 0!==e.description&&"string"!=typeof e.description&&t.push("description must be a string"),void 0!==e.draft&&"boolean"!=typeof e.draft&&t.push("draft must be a boolean"),void 0!==e.date&&("string"==typeof e.date||e.date instanceof Date||t.push("date must be a string or Date")),void 0===e.tags||Array.isArray(e.tags)||t.push("tags must be an array"),void 0===e.categories||Array.isArray(e.categories)||t.push("categories must be an array"),{valid:0===t.length,errors:t}}
1
+ import e from"gray-matter";import t from"markdown-it";import r from"markdown-it-anchor";import a from"markdown-it-attrs";import*as n from"markdown-it-emoji";import{createHighlighter as o}from"shiki";import{setupContainers as s,setupDetails as i}from"./containers.js";import{createEnhancedHighlighter as c}from"./code.js";import{preprocessIncludesWithRegions as l}from"./includes.js";import{preserveTagsPlugin as g}from"./preserve-tags.js";import{globalTagValidator as p}from"./tag-validator.js";import{basePathPlugin as u}from"./base-path-plugin.js";import{mdLinkPlugin as m}from"./md-link-plugin.js";import{resolveImageTagsInHtml as f}from"../image/tag.js";let d=null;const h=new Set,y=["javascript","typescript","js","ts","bash","markdown","md"],w={js:"javascript",ts:"typescript",py:"python",rs:"rust",sh:"bash",shell:"bash",yml:"yaml",cs:"csharp",cpp:"c++"};function b(e){return w[e]||e}async function k(e){if(!d){const t=e?.light||"vitesse-light",r=e?.dark||"vitesse-dark";d=await o({themes:[t,r],langs:Array.from(y)}),y.forEach(e=>h.add(b(e)))}return d}export async function preloadLanguages(e){d||await k();const t=[];for(const r of e){const e=b(r);h.has(e)||t.push((async()=>{try{const{bundledLanguages:t}=await import("shiki/langs");e in t&&(await d.loadLanguage(t[e]),h.add(e))}catch{}})())}await Promise.all(t)}export async function getMarkdownIt(e={},o={}){const l=await k(e.theme),f=c(l,{lineNumbers:e.lineNumbers??!0,theme:e.theme}),d=new t({html:!0,linkify:!0,typographer:!0,highlight:(e,t,r)=>{if(!t)return"";try{return f(e,t,r)}catch(e){return""}}}),h=d.renderer.rules.fence;return d.renderer.rules.fence=(e,t,r,a,n)=>{const o=e[t],s=(o.info||"").trim().split(/\s+/)[0]||"";let i="";if(o.attrs){for(const[e]of o.attrs)i+=`{${e}} `;i=i.trim()}if(s){const e=o.content;try{const t=f(e,s,i);if(t)return t}catch(e){console.warn(`[MarkoPress] Failed to highlight code block (lang: ${s}):`,e instanceof Error?e.message:e)}}return h(e,t,r,a,n)},d.use(r,{slugify:x,permalink:r.permalink.linkInsideHeader({symbol:"#",placement:"before"})}),d.use(a),d.use(n.full||n.bare),s(d),i(d),e.markoTags?.enabled&&d.use(g,{tagsDir:e.markoTags?.tagsDir||"tags/",onTagDetected:(e,t)=>{p.addDetectedTag(e,o.filePath||"unknown",t)}}),d.use(m),e.base&&d.use(u,e.base),d}export async function parseMarkdown(t,r={},a={},n){const{data:o,content:s,excerpt:i}=e(t,{excerpt:!0,excerpt_separator:"\x3c!-- more --\x3e"}),c=await l(s,{root:a.rootDir??process.cwd(),currentFile:a.filePath??""}),g=n||await async function(e,t){return getMarkdownIt(e,t)}(r,a),p=await f(g.render(c,a),{base:r.base??void 0}),u=a.extractToc?function(e){const t=[],r=e.replace(/```[\s\S]*?```/g,"").replace(/~~~[\s\S]*?~~~/g,"").replace(/^(\t| {4}).+$/gm,""),a=/^(#{1,6})\s+(.+)$/gm;let n;for(;null!==(n=a.exec(r));){const e=n[1].length,r=n[2].trim(),a=$(r),o=x(r);t.push({level:e,title:a,slug:o})}return t}(c):[];return{frontmatter:o,content:c,html:p,excerpt:i,headers:v(u)}}function $(e){return e.replace(/`([^`]+)`/g,"$1").replace(/\*\*\*\+([^*]+)\*\*\+/g,"$1").replace(/___+([^_]+)___+/g,"$1").replace(/\*\*([^*]+)\*\*/g,"$1").replace(/__([^_]+)__/g,"$1").replace(/\*([^*]+)\*/g,"$1").replace(/_([^_]+)_/g,"$1").replace(/\[([^\]]+)\]\([^)]+\)/g,"$1").replace(/\[([^\]]+)\]\[[^\]]+\]/g,"$1").trim()}function v(e){const t=[],r=[];for(const a of e){const e={level:a.level,title:a.title,slug:a.slug,children:[]};for(;r.length>0&&r[r.length-1].level>=a.level;)r.pop();0===r.length?t.push(e):r[r.length-1].children.push(e),r.push(e)}return t}function x(e){return e.trim().replace(/([a-z])([A-Z])/g,"$1-$2").replace(/([0-9])([a-zA-Z])/g,"$1-$2").replace(/([a-zA-Z])([0-9])/g,"$1-$2").toLowerCase().replace(/\s+/g,"-").replace(/[^\w\u00A0-\uFFFF\-]+/g,"").replace(/^-+|-+$/g,"").replace(/-+/g,"-")}export function validateFrontmatter(e){const t=[];return void 0!==e.title&&"string"!=typeof e.title&&t.push("title must be a string"),void 0!==e.description&&"string"!=typeof e.description&&t.push("description must be a string"),void 0!==e.draft&&"boolean"!=typeof e.draft&&t.push("draft must be a boolean"),void 0!==e.date&&("string"==typeof e.date||e.date instanceof Date||t.push("date must be a string or Date")),void 0===e.tags||Array.isArray(e.tags)||t.push("tags must be an array"),void 0===e.categories||Array.isArray(e.categories)||t.push("categories must be an array"),{valid:0===t.length,errors:t}}export function scanCodeBlockLanguages(e){const t=new Set,r=/^```([\w+-]+)/gm;let a;for(;null!==(a=r.exec(e));)t.add(a[1]);return Array.from(t)}export async function preloadContentLanguages(e,t=[],r=!1){const a=new Set;for(const t of e)for(const e of t.files)try{const t=await import("node:fs/promises").then(t=>t.readFile(e.filePath,"utf-8"));for(const e of scanCodeBlockLanguages(t))a.add(e)}catch(t){r&&console.warn(` Warning: Could not read ${e.filePath} for language scanning:`,t instanceof Error?t.message:t)}const n=[...new Set([...a,...t])];n.length>0&&(await preloadLanguages(n),r&&console.log(` Preloaded ${n.length} syntax highlighting languages: ${n.join(", ")}\n`))}
@@ -1 +1 @@
1
- import e from"gray-matter";import r from"markdown-it";import{getMarkdownIt as t}from"./loader.js";import{resolveImageTagsInHtml as l}from"../image/tag.js";let n,a=null;export async function renderMarkdown(r,g){if(!a){const e={...g,markoTags:{enabled:!0,...g?.markoTags}};a=await t(e),n=e}const{data:s,content:i,excerpt:u}=e(r,{excerpt:!0,excerpt_separator:"\x3c!-- more --\x3e"});return{frontmatter:s,content:i,html:await l(a.render(i),{base:g?.base??void 0}),excerpt:u,headers:o(function(e){const r=[],t=e.replace(/```[\s\S]*?```/g,"").replace(/~~~[\s\S]*?~~~/g,"").replace(/^(\t| {4}).+$/gm,""),l=/^(#{1,6})\s+(.+)$/gm;let n;for(;null!==(n=l.exec(t));){const e=n[1].length,t=n[2].trim(),l=c(t),a=p(t);r.push({level:e,title:l,slug:a})}return r}(i))}}export function resetRendererCache(){a=null,n=void 0}export function getRendererInstance(){return a}function c(e){return e.replace(/`([^`]+)`/g,"$1").replace(/\*\*\*\+([^*]+)\*\*\+/g,"$1").replace(/___+([^_]+)___+/g,"$1").replace(/\*\*([^*]+)\*\*/g,"$1").replace(/__([^_]+)__/g,"$1").replace(/\*([^*]+)\*/g,"$1").replace(/_([^_]+)_/g,"$1").replace(/\[([^\]]+)\]\([^)]+\)/g,"$1").replace(/\[([^\]]+)\]\[[^\]]+\]/g,"$1").trim()}function o(e){const r=[],t=[];for(const l of e){const e={level:l.level,title:l.title,slug:l.slug,children:[]};for(;t.length>0&&t[t.length-1].level>=l.level;)t.pop();0===t.length?r.push(e):t[t.length-1].children.push(e),t.push(e)}return r}function p(e){return e.trim().replace(/([a-z])([A-Z])/g,"$1-$2").replace(/([0-9])([a-zA-Z])/g,"$1-$2").replace(/([a-zA-Z])([0-9])/g,"$1-$2").toLowerCase().replace(/\s+/g,"-").replace(/[^\w\u00A0-\uFFFF\-]+/g,"").replace(/^-+|-+$/g,"").replace(/-+/g,"-")}
1
+ import e from"gray-matter";import r from"markdown-it";import{getMarkdownIt as t,preloadLanguages as a}from"./loader.js";import{resolveImageTagsInHtml as l}from"../image/tag.js";let n,c=null,g=!1;export async function renderMarkdown(r,u){if(c)!g&&u?.languages?.length&&(await a(u.languages),g=!0);else{const e={...u,markoTags:{enabled:!0,...u?.markoTags}};c=await t(e),e.languages?.length&&await a(e.languages),n=e,g=!0}const{data:i,content:m,excerpt:$}=e(r,{excerpt:!0,excerpt_separator:"\x3c!-- more --\x3e"});return{frontmatter:i,content:m,html:await l(c.render(m),{base:u?.base??void 0}),excerpt:$,headers:p(function(e){const r=[],t=e.replace(/```[\s\S]*?```/g,"").replace(/~~~[\s\S]*?~~~/g,"").replace(/^(\t| {4}).+$/gm,""),a=/^(#{1,6})\s+(.+)$/gm;let l;for(;null!==(l=a.exec(t));){const e=l[1].length,t=l[2].trim(),a=o(t),n=s(t);r.push({level:e,title:a,slug:n})}return r}(m))}}export function resetRendererCache(){c=null,n=void 0}export function getRendererInstance(){return c}function o(e){return e.replace(/`([^`]+)`/g,"$1").replace(/\*\*\*\+([^*]+)\*\*\+/g,"$1").replace(/___+([^_]+)___+/g,"$1").replace(/\*\*([^*]+)\*\*/g,"$1").replace(/__([^_]+)__/g,"$1").replace(/\*([^*]+)\*/g,"$1").replace(/_([^_]+)_/g,"$1").replace(/\[([^\]]+)\]\([^)]+\)/g,"$1").replace(/\[([^\]]+)\]\[[^\]]+\]/g,"$1").trim()}function p(e){const r=[],t=[];for(const a of e){const e={level:a.level,title:a.title,slug:a.slug,children:[]};for(;t.length>0&&t[t.length-1].level>=a.level;)t.pop();0===t.length?r.push(e):t[t.length-1].children.push(e),t.push(e)}return r}function s(e){return e.trim().replace(/([a-z])([A-Z])/g,"$1-$2").replace(/([0-9])([a-zA-Z])/g,"$1-$2").replace(/([a-zA-Z])([0-9])/g,"$1-$2").toLowerCase().replace(/\s+/g,"-").replace(/[^\w\u00A0-\uFFFF\-]+/g,"").replace(/^-+|-+$/g,"").replace(/-+/g,"-")}
@@ -7,6 +7,10 @@ export interface MarkdownOptions {
7
7
  light?: string;
8
8
  dark?: string;
9
9
  };
10
+ /**
11
+ * Additional languages to preload for syntax highlighting.
12
+ */
13
+ languages?: string[];
10
14
  /**
11
15
  * Marko tags support
12
16
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "markopress",
3
- "version": "0.0.19",
3
+ "version": "0.0.21",
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",
@@ -124,7 +124,7 @@ export async function GET(context, next) {
124
124
  return next();
125
125
  }
126
126
 
127
- const rendered = await renderMarkdown(source, { base: base !== '/' ? base : undefined });
127
+ const rendered = await renderMarkdown(source, { base: base !== '/' ? base : undefined, languages: config.markdown?.languages, theme: config.markdown?.theme });
128
128
  frontmatter = rendered.frontmatter;
129
129
  headers = rendered.headers || [];
130
130
 
@@ -1,27 +0,0 @@
1
- /**
2
- * Filesystem-based content source.
3
- *
4
- * Scans local markdown files and produces ContentItems with frontmatter metadata.
5
- * This is the default content source — it replaces the inline scanning logic
6
- * that was previously embedded in build/index.ts.
7
- */
8
- import type { ContentItem, ContentSource } from './source.js';
9
- export interface FilesystemSourceOptions {
10
- /** Root directory containing content (e.g., 'content/') */
11
- contentDir: string;
12
- /** Base URL path for generating urlPath (default: '/') */
13
- basePath?: string;
14
- }
15
- export declare class FilesystemSource implements ContentSource {
16
- readonly id = "filesystem";
17
- private contentDir;
18
- private basePath;
19
- private items;
20
- private itemIndex;
21
- constructor(options: FilesystemSourceOptions);
22
- scan(): Promise<ContentItem[]>;
23
- read(id: string): Promise<ContentItem | null>;
24
- private filePathToUrl;
25
- private getModuleId;
26
- private fileToItem;
27
- }
@@ -1 +0,0 @@
1
- import{promises as t}from"node:fs";import e from"node:path";import i from"gray-matter";export class FilesystemSource{id="filesystem";contentDir;basePath;items=[];itemIndex=new Map;constructor(t){this.contentDir=e.resolve(t.contentDir),this.basePath=t.basePath||"/"}async scan(){let s;this.items=[],this.itemIndex.clear();try{s=await t.readdir(this.contentDir,{withFileTypes:!0,recursive:!0})}catch{return[]}for(const r of s){if(!r.isFile()||!r.name.endsWith(".md"))continue;const s=e.join(r.path||r.parentPath||this.contentDir,r.name),n=e.relative(this.contentDir,s),a=this.filePathToUrl(n),o=this.getModuleId(n),h=await t.readFile(s,"utf-8");let d={};try{d=i(h).data}catch{}const l=this.fileToItem(s,n,a,o,d);this.items.push(l),this.itemIndex.set(l.id,l)}return this.items}async read(t){return this.itemIndex.get(t)??null}filePathToUrl(t){const i=t.replace(/\.md$/,"").split(e.sep).join("/");return"index"===i?"/":i.endsWith("/index")?"/"+i.replace("/index",""):"/"+i}getModuleId(t){const i=t.split(e.sep);return 1===i.length?"root":i[0]}fileToItem(t,i,s,r,n){return{id:`${r}/${e.basename(i,".md")}`,moduleId:r,metadata:{...n},urlPath:s}}}