markopress 0.0.16 → 0.0.18

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.
@@ -19,6 +19,7 @@ export interface BuildResult {
19
19
  pages: number;
20
20
  errors: string[];
21
21
  }
22
+ export declare function syncPublicAssets(sourceRoot: string, appRoot: string, debug: boolean): Promise<void>;
22
23
  /**
23
24
  * Build the MarkoPress site for production
24
25
  */
@@ -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 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();let m,y=await a(h,{mode:"production",command:"build"});if(n.end(),y.plugins&&y.plugins.length>0){console.log("šŸ”Œ Loading plugins...");const e=v("Plugin loading");e.start(),m=new d(y),await m.loadPlugins(y.plugins),y=m.getConfig(),e.end(),console.log("")}if(m){console.log("šŸ“¦ Loading plugin content...");const e=v("Plugin loadContent hooks");e.start(),await m.execLoadContentHooks(),e.end(),console.log(" Plugin content loaded\n")}const j={},$=[],C=o.resolve(h,y.contentDir);try{const t=await e.readdir(C,{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||C,a.name),r=o.relative(C,t),i=filePathToUrl(t,C),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=y.content[e]||{},s=new Map;$.push({id:e,dir:o.join(C,"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(m&&$.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 m.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=(y.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!==y.search?.enabled){console.log("šŸ” Building search index...");const t=v("Search index");t.start();const n=[],s=(y.site?.base||"/").replace(/\/$/,"");for(const o of $)for(const t of o.files)try{const o=await e.readFile(t.filePath,"utf-8"),a=await f(o,{...y.markdown,base:s});n.push({url:t.urlPath,html:a.html,title:a.frontmatter?.title||t.id,frontmatter:a.frontmatter})}catch(e){console.warn(` Warning: Could not index ${t.filePath}:`,e)}try{const t=await p(n,y.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(y.markdown.markoTags?.enabled){const e=o.join(T,y.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 P=o.join(T,"src","routes");await e.mkdir(P,{recursive:!0});let F={};if(m){const e=v("Extend routes hooks");e.start(),F=await m.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??y.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=(y.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||[],headTop:s.headTop||[],headBottom:s.headBottom||[]},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,P,y,$,i,!0),console.log(" Using catch-all dynamic routes")):(await generateRoutes(j,P,y,$,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}`),m){const t=[...m.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,P,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"),m){console.log("šŸ”Œ Processing plugin allContentLoaded hooks...");const e=v("AllContentLoaded hooks");e.start(),await m.execAllContentLoadedHooks(F),e.end(),console.log(" All content processed\n")}if(y.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,y,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,y,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 B=v("@marko/run build");B.start();const U=r||y.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]})})})}(U,i,T);if(B.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(),m){console.log("šŸ”Œ Processing plugin postBuild hooks...");const e=v("Post-build hooks");e.start(),await m.execPostBuildHooks(y,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,y,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 C(0,0,0,0,a),l++;else for(const o of e)await $(0,0,0,0,a),c++}await P(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 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"),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||{},_headInject:n._headInject||void 0},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 P(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";
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 a from"gray-matter";import{loadConfig as s}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,"..",".."),y=o.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,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 syncPublicAssets(t,n,a){if(t===n)return;const s=o.join(t,"public"),i=o.join(n,"public"),r=o.join(n,"src",".generated","public-assets-manifest.json"),c=await async function(o){try{const t=await e.readFile(o,"utf-8"),n=JSON.parse(t);return Array.isArray(n)?n.filter(e=>"string"==typeof e):[]}catch{return[]}}(r);let l=[];try{if(!(await e.stat(s)).isDirectory())return await D(i,c,l),void await _(r,l)}catch{return await D(i,c,l),void await _(r,l)}await e.mkdir(i,{recursive:!0}),l=await E(s,i),await D(i,c,l),await _(r,l),a&&(console.log(" Synced public assets from: "+s),console.log(" Output: "+i))}export async function build(n={}){const{outDir:i,debug:r=!1,useCatchAllRoutes:u,root:m}=n,h=m||process.cwd(),w=[],y=new Map,k=new Map,j=o.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 o=k.get(e)||0,t=performance.now()-o;y.set(e,t)}});try{console.log("šŸš€ Building MarkoPress site...\n");const n=v("Config loading");n.start();let m,k=await s(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(),m=new d(k),await m.loadPlugins(k.plugins),k=m.getConfig(),e.end(),console.log("")}if(m){console.log("šŸ“¦ Loading plugin content...");const e=v("Plugin loadContent hooks");e.start(),await m.execLoadContentHooks(),e.end(),console.log(" Plugin content loaded\n")}const j={},P=[],$=o.resolve(h,k.contentDir);try{const t=await e.readdir($,{withFileTypes:!0,recursive:!0}),n=new Map;for(const s of t){if(!s.isFile()||!s.name.endsWith(".md"))continue;const t=o.join(s.path||s.parentPath||$,s.name),i=o.relative($,t),r=filePathToUrl(t,$),c=i.split(o.sep),l=1===c.length?"root":c[0],d=await e.readFile(t,"utf-8");let u={};try{u=a(d).data}catch{}n.has(l)||n.set(l,[]),n.get(l).push({id:s.name.replace(".md",""),slug:s.name.replace(".md",""),filePath:t,urlPath:r,directory:l,processed:{frontmatter:u}})}for(const[e,t]of n){const n=k.content[e]||{},a=new Map;P.push({id:e,dir:o.join($,"root"===e?"":e),config:n,features:n,files:t,enhance(e,o){a.set(e,o)},getEnhancement:e=>a.get(e),_enhancements:a})}}catch(e){console.warn("Warning: Could not scan content directory: "+e)}if(m&&P.length>0){console.log("šŸ”Œ Enhancing modules with plugin metadata...");const t=v("Module enhancement");t.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 m.execEnhanceModulesHooks(P),t.end(),console.log(` Enhanced ${P.length} module(s)\n`);const n=o.join(T,"src",".generated");await e.mkdir(n,{recursive:!0});const a=(k.site?.base||"/").replace(/\/$/,""),s={};for(const e of P){const o={},t=e._enhancements.entries();for(const[e,n]of t)o[e]=n;Object.keys(o).length>0&&(a&&b(o,a),s[e.id]=o)}const i=o.join(n,"module-enhancements.js"),r=`// Auto-generated by MarkoPress - Do not edit\nexport default ${JSON.stringify(s,null,2)};\n`;await e.writeFile(i,r,"utf-8"),console.log(" Wrote module enhancements to src/.generated/module-enhancements.js\n")}console.log("šŸ“¦ Syncing public assets...");const F=v("Public assets sync");if(F.start(),await syncPublicAssets(h,T,r),F.end(),console.log(" Public assets synced\n"),!1!==k.search?.enabled){console.log("šŸ” Building search index...");const t=v("Search index");t.start();const n=[],a=(k.site?.base||"/").replace(/\/$/,"");for(const o of P)for(const t of o.files)try{const o=await e.readFile(t.filePath,"utf-8"),s=await f(o,{...k.markdown,base:a});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,k.search),a=o.join(T,"public","search-index.json");await e.mkdir(o.dirname(a),{recursive:!0}),await e.writeFile(a,t),console.log(` Search index built (${n.length} pages)\n`)}catch(e){console.warn(" Warning: Failed to build search index:",e)}t.end()}if(k.markdown.markoTags?.enabled){const e=o.join(T,k.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 S={};if(m){const e=v("Extend routes hooks");e.start(),S=await m.execExtendRoutesHooks(S),e.end(),console.log("šŸ”Œ Extended routes manifest:",Object.keys(S).length)}console.log("šŸ“ Generating routes from content...");const x=v("Route generation");x.start();const E=u??k.build.useCatchAllRoutes;console.log("šŸ“„ Pre-rendering markdown to .marko files...");const _=v("Pre-render markdown");_.start();const D=o.join(T,"src",".generated","markdown");await e.mkdir(D,{recursive:!0});const A={};let N=0;for(const t of P){const n=o.join(D,t.id);await e.mkdir(n,{recursive:!0});for(const a of t.files)try{const s=await e.readFile(a.filePath,"utf-8"),i=(k.site?.base||"/").replace(/\/$/,""),r=await f(s,{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,a.slug+".marko");await e.writeFile(d,l),A[`${t.id}/${a.slug}`]={frontmatter:r.frontmatter,headers:r.headers||[],headTop:a.headTop||[],headBottom:a.headBottom||[]},N++}catch(e){console.warn(` Warning: Failed to pre-render ${t.id}/${a.slug}:`,e)}}const O=o.join(T,"src",".generated","content-metadata.js"),M=`// Auto-generated by MarkoPress - Do not edit\nexport default ${JSON.stringify(A,null,2)};\n`;await e.writeFile(O,M),_.end(),console.log(` Pre-rendered ${N} markdown files\n`),E?(await generateCatchAllRoutes(j,C,k,P,r,!0),console.log(" Using catch-all dynamic routes")):(await generateRoutes(j,C,k,P,r),console.log(" Using static routes")),x.end(),console.log(" Routes generated\n");const I=[];for(const[e,o]of Object.entries(S))(o.handler||o.component)&&(I.push({path:e,...o}),console.log(" Found plugin route: "+e));if(console.log(`šŸ”Œ Total manifest routes: ${Object.keys(S).length}, Plugin routes: ${I.length}`),m){const t=[...m.getPluginRoutes(),...I];if(t.length>0){console.log(`šŸ”Œ Generating ${t.length} plugin routes...`);const n=v("Plugin route generation");n.start(),await async function(t,n,a,s){for(const a of t){const t=a.path.slice(1),i=o.join(n,t,"+page");if(await e.mkdir(o.dirname(i),{recursive:!0}),a.handler){const t=o.join(o.dirname(i),"+handler.js");await e.writeFile(t,a.handler)}if(a.component){const o=i+".marko";await e.writeFile(o,a.component)}s&&console.log(" Generated plugin route: "+a.path)}}(t,C,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"),m){console.log("šŸ”Œ Processing plugin allContentLoaded hooks...");const e=v("AllContentLoaded hooks");e.start(),await m.execAllContentLoadedHooks(S),e.end(),console.log(" All content processed\n")}if(k.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 W=v("Theme CSS copy");W.start(),await copyThemeCSS(T,k,r),W.end(),console.log(" Theme CSS copied\n"),console.log("šŸŽØ Extracting styles from Marko components...");const R=v("Marko component styles extraction");R.start(),await extractStylesFromMarkoTags(T,k,r),R.end(),console.log(" Component styles extracted\n");const G=[];for(const e of P)for(const o of e.files)G.push(o.urlPath);for(const e of Object.keys(S))G.includes(e)||G.push(e);const B=o.join(T,"src",".generated","static-urls.json");await e.mkdir(o.dirname(B),{recursive:!0}),await e.writeFile(B,JSON.stringify(G,null,2)),r&&console.log(` Generated static URL manifest: ${G.length} URLs`),console.log("šŸ”Ø Building with @marko/run...");const U=v("@marko/run build");U.start();const V=i||k.build.outDir,H=await async function(e,n,a){return new Promise(s=>{const i=["build"];e&&i.push("--output",e),n&&i.push("--debug");const r=t("npx",["marko-run",...i],{stdio:"inherit",cwd:a});r.on("close",t=>{if(0===t){const t=e||"dist";s({success:!0,outDir:o.join(a,t),errors:[]})}else s({success:!1,outDir:"",errors:["Build process exited with code "+t]})}),r.on("error",e=>{s({success:!1,outDir:"",errors:["Failed to start build process: "+e.message]})})})}(V,r,T);if(U.end(),!H.success)return w.push(...H.errors),{success:!1,outDir:"",pages:0,errors:w};const J=v("Collect build assets");J.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}(H.outDir);if(J.end(),m){console.log("šŸ”Œ Processing plugin postBuild hooks...");const e=v("Post-build hooks");e.start(),await m.execPostBuildHooks(k,H.outDir,S,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,H.outDir,k,r),q.end(),console.log(" Tags directory copied\n"),console.log("\nāœ… Build completed successfully!"),console.log(" Output: "+H.outDir),console.log(" Pages: Generated dynamically at request time"),console.log("\nā±ļø Build timing:");const z=Array.from(y.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:H.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,a,s){await cleanupGeneratedRoutes(t,e,s);const i=[];let r=0,c=0,l=0;for(const[o,t]of Object.entries(e)){if(!Array.isArray(t))continue;i.push(o);const e=t;if("pages"===o)for(const o of e)await v(0,0,0,0,s),r++;else if("blog"===o)for(const o of e)await $(0,0,0,0,s),l++;else for(const o of e)await P(0,0,0,0,s),c++}await F(t,n,s);const d=o.resolve(t,"..","..");await generateViteConfig(d,s),await C(t,n,s),s&&(console.log(` Generated ${r} page routes`),console.log(` Generated ${c} doc routes`),console.log(` Generated ${l} blog routes`))}export async function cleanupGeneratedRoutes(t,n,a){const s=[],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))a&&console.log(" Preserving: "+l);else try{await e.unlink(n),a&&console.log(" Deleted: "+l)}catch(e){if("ENOENT"!==e.code){const o=e instanceof Error?e.message:e+"";s.push(`Failed to delete ${l}: ${o}`)}}}for(const e of r){const n=o.join(t,e);try{await T(n)}catch{}}s.length>0&&(console.warn("āš ļø Cleanup warnings:"),s.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,a){a&&console.log(" Warning: Static routes deprecated, use catch-all routes")}async function P(e,o,t,n,a){a&&console.log(" Warning: Static routes deprecated, use catch-all routes")}async function $(e,o,t,n,a){a&&console.log(" Warning: Static routes deprecated, use catch-all routes")}async function F(t,n,a){const s=o.join(t,"_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(s,c),a&&console.log(" Generated: "+s)}async function C(t,n,a){const s=o.join(t,"+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(s,d),a&&console.log(" Generated: "+s)}async function S(t,n){const a=o.dirname(new URL(import.meta.url).pathname),s=o.join(a,"..",".."),i=o.join(s,"templates",t);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,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 a=o.join(t,"vite.config.js");try{if((await e.readFile(a,"utf-8")).includes("markdownContentPlugin"))return void(n&&console.log(" Vite config already has markdownContentPlugin"))}catch{}await e.writeFile(a,"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,a){const s=o.join(t,"public","_markopress","theme");await e.mkdir(s,{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(y,"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(s,c);return void await e.writeFile(n,t)}const m=o.join(s,c);await e.writeFile(m,d),a&&(console.log(` Copied ${c} from: ${u}`),console.log(" Output: "+m));const g="styles.css",f=[...j(i)?[o.join(y,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(s,g);await e.writeFile(i,n),a&&(console.log(` Copied ${g} from: ${t}`),console.log(" Output: "+i));break}catch{}}export async function extractStylesFromMarkoTags(t,n,a){const s=n.markdown?.markoTags?.tagsDir||"src/tags",i=o.join(t,s),r=o.join(t,"public");await e.mkdir(r,{recursive:!0});const c=o.join(r,"markopress-components.css");if(!n.markdown?.markoTags?.enabled){a&&console.log(" Marko tags not enabled, skipping style extraction");try{await e.unlink(c)}catch{}return}try{await e.access(i)}catch{return void(a&&console.log(" No tags directory found at: "+i))}const l=[];if(await async function t(n){const a=await e.readdir(n,{withFileTypes:!0});for(const e of a){const a=o.join(n,e.name);e.isDirectory()?await t(a):e.isFile()&&e.name.endsWith(".marko")&&l.push(a)}}(i),0===l.length)return void(a&&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 t of l){const n=o.relative(i,t),a=""===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,s=Array.from(o.matchAll(n));if(s.length>0){d.push(`/* ${a}.marko */`);for(const e of s){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*)/),a=n?n[1].length:0,s=" ".repeat(Math.floor(a/2)),i=t.trim().replace(/:global\(([^)]+)\)/g,"$1");d.push(s+i)}}}d.push("")}}catch(e){console.warn(` Warning: Could not read file ${t}:`,e)}}const u=d.join("\n");await e.writeFile(c,u),a&&(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}),a=await Promise.all(n.map(e=>{const n=o.resolve(t,e.name);return e.isDirectory()?x(n):n}));return Array.prototype.concat(...a).filter(e=>e.endsWith(".marko"))}export async function copyThemeComponents(t,n,a){const s=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(s)?[o.join(y,"tags")]:[],o.join(t,"..","node_modules",s,"dist","tags"),o.join(t,"node_modules",s,"dist","tags"),o.join(t,"..","node_modules",s,"src","components"),o.join(t,"node_modules",s,"src","components")];let l=null;for(const o of c)try{await e.access(o),l=o;break}catch{}if(!l)return void(a&&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),s=o.join(r,n);let i=!1;try{await e.access(s),i=!0}catch{}i?a&&console.log(" Skipped component (user override exists): "+n):(await e.mkdir(o.dirname(s),{recursive:!0}),await e.copyFile(t,s),u++)}a&&(console.log(` Copied ${u} theme components from: ${l}`),console.log(" Output: "+r))}export async function copyTagsDirectory(t,n,a,s){const i=a.markdown?.markoTags?.tagsDir||"src/tags",r=o.join(t,i),c=o.join(n,"tags");try{await e.access(r)}catch{return void(s&&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),a=o.join(c,t.name);if(t.isDirectory()){await e.mkdir(a,{recursive:!0});const t=await e.readdir(n,{withFileTypes:!0});for(const s of t){const t=o.join(n,s.name),i=o.join(a,s.name);s.isDirectory()||(await e.copyFile(t,i),d++)}}else t.isFile()&&(await e.copyFile(n,a),d++)}s&&(console.log(` Copied ${d} tag files from: ${r}`),console.log(" Output: "+c))}async function E(t,n,a=t){const s=await e.readdir(t,{withFileTypes:!0}),i=[];for(const r of s){const s=o.join(t,r.name),c=o.join(n,r.name);r.isDirectory()?(await e.mkdir(c,{recursive:!0}),i.push(...await E(s,c,a))):r.isFile()&&(await e.copyFile(s,c),i.push(o.relative(a,s)))}return i}async function _(t,n){await e.mkdir(o.dirname(t),{recursive:!0}),await e.writeFile(t,JSON.stringify(n,null,2))}async function D(t,n,a){const s=new Set(a),i=n.filter(e=>!s.has(e)).sort((e,o)=>o.length-e.length);for(const n of i){const a=o.join(t,n);await e.rm(a,{force:!0}),await A(o.dirname(a),t)}}async function A(t,n){let a=t;for(;a.startsWith(n)&&a!==n;){let t;try{t=await e.readdir(a)}catch{break}if(t.length>0)break;await e.rmdir(a),a=o.dirname(a)}}export async function generateCatchAllRoutes(t,n,a,s,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(a.content||{}),l=new Set(s.map(e=>e.id)),d=[...new Set([...c,...l])];if(s.some(e=>"root"===e.id)){const t=o.join(n,"$$slug");await e.mkdir(t,{recursive:!0});const a=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(o.join(t,"+handler.js"),a);const s=await S("catch-all-page.marko.template",{CONTENT_TYPE_CLASS:"page"});await e.writeFile(o.join(t,"+page.marko"),s),i&&console.log(" Generated root pages catch-all route")}for(const t of d){if("root"===t)continue;const a=o.join(n,t,"$$slug");await e.mkdir(a,{recursive:!0});const s=await S("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(a,"+handler.js"),s);const c=await S("catch-all-page.marko.template",{CONTENT_TYPE_CLASS:t});await e.writeFile(o.join(a,"+page.marko"),c),i&&console.log(` Generated ${t} catch-all route`)}await F(n,a,i);const u=o.resolve(n,"..","..");await generateViteConfig(u,i),await C(n,a,i)}export{u as loadMarkdownModule,m as registerMarkdownContent};export{markdownContentPlugin}from"./vite-markdown-plugin.js";
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}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();let 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),u=f.getConfig(),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
+ 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)})}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * MarkoPress Image Utilities
3
+ */
4
+ export interface ImageMetadata {
5
+ width: number;
6
+ height: number;
7
+ format?: string;
8
+ aspectRatio: number;
9
+ }
10
+ /**
11
+ * Get image dimensions from a file path
12
+ */
13
+ export declare function getImageDimensions(imagePath: string): Promise<ImageMetadata>;
14
+ /**
15
+ * Generate a blur placeholder from an image
16
+ */
17
+ export declare function generateBlurPlaceholder(imagePath: string, size?: number): Promise<string>;
18
+ /**
19
+ * Validate image URL/path
20
+ */
21
+ export declare function isValidImagePath(imagePath: string): boolean;
22
+ /**
23
+ * Get image format from filename
24
+ */
25
+ export declare function getImageFormat(imagePath: string): string | null;
@@ -0,0 +1 @@
1
+ import t from"node:path";import e from"sharp";const a={jpg:"jpeg",jpeg:"jpeg",png:"png",webp:"webp",avif:"avif",gif:"gif",svg:"svg"};function i(t,e){if(!t.width||!t.height)throw Error("Could not determine dimensions for image: "+e)}export async function getImageDimensions(t){const a=await e(t,{animated:!0}).metadata();return i(a,t),{width:a.width,height:a.height,format:a.format,aspectRatio:a.width/a.height}}export async function generateBlurPlaceholder(t,a=20){const n=await e(t,{animated:!0}).metadata();i(n,t);const r=Math.max(1,Math.min(a,n.width)),o=Math.max(1,Math.round(n.height/n.width*r)),s=n.hasAlpha?"png":"jpeg";let g=e(t,{animated:!0}).resize({width:r,height:o,fit:"inside",withoutEnlargement:!0}).blur(12);return`data:image/${s};base64,${("png"===s?await g.png({compressionLevel:9,palette:!0}).toBuffer():await g.jpeg({quality:40,mozjpeg:!0}).toBuffer()).toString("base64")}`}export function isValidImagePath(e){if(!e)return!1;if(e.startsWith("data:image/"))return!0;try{const t=new URL(e);if("http:"===t.protocol||"https:"===t.protocol)return!0}catch{}if(t.isAbsolute(e)||e.startsWith("./")||e.startsWith("../"))return!0;const a=e.split(/[?#]/,1)[0];return/\.(avif|gif|jpe?g|png|svg|webp)$/i.test(a)}export function getImageFormat(e){if(!e)return null;const i=e.match(/^data:image\/([a-zA-Z0-9.+-]+)[;,]/);if(i)return a[i[1].toLowerCase()]||i[1].toLowerCase();const n=e.split(/[?#]/,1)[0],r=t.extname(n).slice(1).toLowerCase();return a[r]||null}
@@ -0,0 +1,24 @@
1
+ type ImageLayout = 'fixed' | 'fluid' | 'responsive';
2
+ export interface ResolveImageTagOptions {
3
+ src: string;
4
+ width?: number;
5
+ height?: number;
6
+ layout?: ImageLayout;
7
+ sizes?: string;
8
+ srcset?: string;
9
+ placeholder?: 'blur' | 'none';
10
+ quality?: number;
11
+ appRoot?: string;
12
+ base?: string;
13
+ }
14
+ export interface ResolvedImageTagData {
15
+ src: string;
16
+ width?: number;
17
+ height?: number;
18
+ sizes?: string;
19
+ srcset?: string;
20
+ blurDataURL?: string;
21
+ }
22
+ export declare function resolveImageTag(options: ResolveImageTagOptions): Promise<ResolvedImageTagData>;
23
+ export declare function resolveImageTagsInHtml(html: string, options?: Pick<ResolveImageTagOptions, 'appRoot' | 'base'>): Promise<string>;
24
+ export {};
@@ -0,0 +1 @@
1
+ import t from"node:fs/promises";import e from"node:path";import{createHash as i}from"node:crypto";import{resolveAppRoot as r}from"../config/app-root.js";import{generateBlurPlaceholder as n,getImageDimensions as s,getImageFormat as o}from"./index.js";import{transformImage as a}from"./transform.js";const u=new Set(["jpeg","png","webp","avif"]),c=[320,480,640,768,960,1280,1536,1920],l=new Map;export async function resolveImageTag(t){const{src:u,width:g,height:m,layout:w="responsive",sizes:b,srcset:v,placeholder:y="none",quality:$,appRoot:j=r(),base:x="/"}=t,P=await async function(t,i,r){if(!t||t.startsWith("data:image/"))return null;try{const e=new URL(t);if("http:"===e.protocol||"https:"===e.protocol)return null}catch{}const n=function(t,e){const i=S(e);return i?t===i?"/":t.startsWith(i+"/")?t.slice(i.length):t:t}(t.split(/[?#]/,1)[0],r),s=e.join(i,"public");if(n.startsWith("/")){const t=n.replace(/^\/+/,""),i=e.join(s,t);if(await h(i))return{sourcePath:i,publicPath:t}}if(e.isAbsolute(n)&&await h(n))return{sourcePath:n,publicPath:e.basename(n)};const o=e.resolve(n);return await h(o)?{sourcePath:o,publicPath:e.basename(o)}:null}(u,j,x);if(!P)return{src:u,width:g,height:m,sizes:b??f(w,g),srcset:v};const W=await s(P.sourcePath),{width:z,height:R}=function(t,e,i,r){return i&&r?{width:i,height:r}:i?{width:i,height:Math.round(e/t*i)}:r?{width:Math.round(t/e*r),height:r}:{width:t,height:e}}(W.width,W.height,g,m),N=o(P.sourcePath),V={src:u,width:z,height:R,sizes:b??f(w,z),srcset:v};if("blur"===y&&d(N)&&(V.blurDataURL=await function(t){let e=l.get(t);return e||(e=n(t),l.set(t,e)),e}(P.sourcePath)),v||!d(N))return V;const k=function(t,e,i){const r=new Set;if("fixed"===t){const t=i??e;r.add(Math.min(t,e)),2*t<=e?r.add(2*t):r.add(e)}else{for(const t of c)t<e&&r.add(t);i&&r.add(Math.min(i,e)),r.add(e)}return[...r].filter(t=>Number.isFinite(t)&&t>0).sort((t,e)=>t-e)}(w,W.width,z);if(0===k.length)return V;const q=e.join(j,"public","_markopress","image",function(t){const r=e.dirname(t),n=i("sha1").update(t).digest("hex").slice(0,8);return e.join("."===r?"":r,n)}(P.publicPath)),D=function(t){return"jpeg"===t?"jpg":t}(N),L=[];for(const t of k)L.push(await a({src:P.sourcePath,width:t,quality:$,format:D,fit:"inside",outDir:q}));return V.src=p(j,L[L.length-1].src,x),V.srcset=L.map(t=>`${p(j,t.src,x)} ${t.width}w`).join(", "),V}export async function resolveImageTagsInHtml(t,e={}){const i=[...t.matchAll(/<image(?=[\s/>])[\s\S]*?(?:\/>|>)/g)];if(0===i.length)return t;let r="",n=0;for(const s of i){const i=s[0],o=s.index??0;r+=t.slice(n,o),r+=await g(i,e),n=o+i.length}return r+=t.slice(n),r}async function h(e){try{return(await t.stat(e)).isFile()}catch{return!1}}function f(t,e){return"fixed"===t&&e?e+"px":"fluid"===t?"100vw":"responsive"===t&&e?`(max-width: ${e}px) 100vw, ${e}px`:void 0}function d(t){return null!==t&&u.has(t)}function p(t,i,r){const n=e.join(t,"public");return function(t,e){const i=S(e);return i?!t.startsWith("/")||t.startsWith("//")||t===i||t.startsWith(i+"/")?t:`${i}${t}`:t}("/"+e.relative(n,i).split(e.sep).join("/"),r)}async function g(t,e){const i=function(t){const e=t.trimEnd().endsWith("/>"),i=t.replace(/^<image\b/,"").replace(e?/\/>\s*$/:/>\s*$/,""),r=[];let n=0;for(;n<i.length;){for(;n<i.length&&/\s/.test(i[n]);)n++;if(n>=i.length||"/"===i[n])break;const t=n;for(;n<i.length&&!/[\s=/]/.test(i[n]);)n++;const e=i.slice(t,n);if(!e)break;for(;n<i.length&&/\s/.test(i[n]);)n++;let s;if("="===i[n]){for(n++;n<i.length&&/\s/.test(i[n]);)n++;const t=m(i,n);s=t.value,n=t.end}r.push({name:e,rawValue:s})}return{attributes:r,selfClosing:e}}(t);if(!function(t){return[["src",j],["width",x],["height",x],["layout",P],["sizes",j],["srcset",j],["placeholder",W],["quality",x]].every(([e,i])=>{const r=w(t,e);return void 0===r||i(r)})}(i.attributes))return t;const r=y(w(i.attributes,"src"));if(!r)return t;const n=await resolveImageTag({src:r,width:$(w(i.attributes,"width")),height:$(w(i.attributes,"height")),layout:b(w(i.attributes,"layout")),sizes:y(w(i.attributes,"sizes")),srcset:y(w(i.attributes,"srcset")),placeholder:v(w(i.attributes,"placeholder")),quality:$(w(i.attributes,"quality")),appRoot:e.appRoot,base:e.base}),s=i.attributes.map(t=>({...t}));R(s,"src",N(n.src)),void 0!==n.width&&R(s,"width",n.width+""),void 0!==n.height&&R(s,"height",n.height+""),n.sizes&&R(s,"sizes",N(n.sizes)),n.srcset&&R(s,"srcset",N(n.srcset)),n.blurDataURL&&R(s,"blurDataURL",N(n.blurDataURL));const o=s.map(t=>function(t){return t.name?void 0===t.rawValue?t.name:`${t.name}=${t.rawValue}`:""}(t)).filter(Boolean).join(" ");return o?`<image ${o}${i.selfClosing?" />":">"}`:"<image"+(i.selfClosing?" />":">")}function m(t,e){const i=t[e];if('"'===i||"'"===i){let r=e+1;for(;r<t.length;)if("\\"!==t[r]){if(t[r]===i){r++;break}r++}else r+=2;return{value:t.slice(e,r),end:r}}if("{"===i){let i=0,r=null,n=e;for(;n<t.length;){const e=t[n];if(r){if("\\"===e){n+=2;continue}e===r&&(r=null),n++}else if('"'!==e&&"'"!==e&&"`"!==e){if("{"===e)i++;else if("}"===e&&(i--,0===i)){n++;break}n++}else r=e,n++}return{value:t.slice(e,n),end:n}}let r=e;for(;r<t.length&&!/\s/.test(t[r]);)r++;return{value:t.slice(e,r),end:r}}function w(t,e){return t.find(t=>t.name===e)?.rawValue}function b(t){const e=y(t);if("fixed"===e||"fluid"===e||"responsive"===e)return e}function v(t){const e=y(t);if("blur"===e||"none"===e)return e}function y(t){if(!t)return;const e=t.trim(),i=z(e);if(void 0!==i)return i;const r=e.match(/^\{\s*([\s\S]+)\s*\}$/);return r?z(r[1].trim()):void 0}function $(t){if(!t)return;const e=t.trim(),i=e.match(/^\{\s*([-+]?\d+(?:\.\d+)?)\s*\}$|^([-+]?\d+(?:\.\d+)?)$/),r=i?.[1]??i?.[2];if(!r){const t=y(e);if(!t||""===t.trim())return;const i=Number(t);return Number.isFinite(i)?i:void 0}const n=Number(r);return Number.isFinite(n)?n:void 0}function j(t){return void 0!==y(t)}function x(t){return void 0!==$(t)}function P(t){return void 0!==b(t)}function W(t){return void 0!==v(t)}function z(t){if(t.startsWith('"')&&t.endsWith('"'))try{return JSON.parse(t)}catch{return t.slice(1,-1)}if(t.startsWith("'")&&t.endsWith("'"))return t.slice(1,-1).replace(/\\\\/g,"\\").replace(/\\'/g,"'").replace(/\\"/g,'"')}function R(t,e,i){const r=t.find(t=>t.name===e);r?r.rawValue=i:t.push({name:e,rawValue:i})}function N(t){return JSON.stringify(t)}function S(t){return t&&"/"!==t?t.replace(/\/$/,""):""}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * MarkoPress Image Transformation
3
+ */
4
+ export interface TransformOptions {
5
+ src: string;
6
+ width?: number;
7
+ height?: number;
8
+ quality?: number;
9
+ format?: 'webp' | 'jpg' | 'png' | 'avif';
10
+ fit?: 'cover' | 'contain' | 'fill' | 'inside' | 'outside';
11
+ outDir: string;
12
+ }
13
+ export interface TransformResult {
14
+ src: string;
15
+ width: number;
16
+ height: number;
17
+ format: string;
18
+ size: number;
19
+ }
20
+ /**
21
+ * Transform an image with specified options
22
+ */
23
+ export declare function transformImage(options: TransformOptions): Promise<TransformResult>;
24
+ /**
25
+ * Generate multiple responsive variants of an image
26
+ */
27
+ export declare function generateResponsiveVariants(src: string, widths: number[], quality: number, outDir: string): Promise<TransformResult[]>;
@@ -0,0 +1 @@
1
+ import t from"node:fs/promises";import e from"node:path";import i from"sharp";import{getImageFormat as a}from"./index.js";function r(t,e){if(!t.width||!t.height)throw Error("Could not determine dimensions for image: "+e)}export async function transformImage(o){const{src:n,width:h,height:s,quality:m,format:d,fit:c="cover",outDir:w}=o,g=e.resolve(n),p=await i(g,{animated:!0}).metadata();r(p,n);const u=function(t,e,i,a){return i&&a?{width:i,height:a}:i?{width:i,height:Math.round(e/t*i)}:a?{width:Math.round(t/e*a),height:a}:{width:t,height:e}}(p.width,p.height,h,s),f=d??a(n)??"webp",l=function(t){return Math.max(1,Math.min(100,t??75))}(m);await t.mkdir(w,{recursive:!0});const j=e.basename(n,e.extname(n)),b="jpg"===f?"jpg":f,y=e.join(w,`${j}-${u.width}x${u.height}.${b}`);try{await t.access(y);const e=await i(y,{animated:!0}).metadata();r(e,y);const a=await t.stat(y);return{src:y,width:e.width,height:e.height,format:"jpg"===f?"jpeg":f,size:a.size}}catch{}let v=i(g,{animated:!0}).rotate().resize({width:u.width,height:u.height,fit:c,withoutEnlargement:!0});switch(f){case"jpg":v=v.jpeg({quality:l,mozjpeg:!0});break;case"png":v=v.png({quality:l,compressionLevel:9});break;case"webp":v=v.webp({quality:l});break;case"avif":v=v.avif({quality:l});break;default:throw Error("Unsupported output format: "+f)}await v.toFile(y);const q=await i(y,{animated:!0}).metadata();r(q,y);const x=await t.stat(y);return{src:y,width:q.width,height:q.height,format:"jpg"===f?"jpeg":f,size:x.size}}export async function generateResponsiveVariants(t,e,i=75,a){const r=[...new Set(e)].filter(t=>Number.isFinite(t)&&t>0).sort((t,e)=>t-e),o=[];for(const e of r){const r=await transformImage({src:t,width:e,quality:i,format:"webp",fit:"inside",outDir:a});o.push(r)}return o}
@@ -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 n}from"shiki";import{setupContainers as o,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";let d=null;const f=new Set,h=["javascript","typescript","js","ts","bash","markdown","md"],y={js:"javascript",ts:"typescript",py:"python",rs:"rust",sh:"bash",shell:"bash",yml:"yaml",cs:"csharp",cpp:"c++"};function b(e){return y[e]||e}async function w(){return d||(d=await n({themes:["github-light","github-dark"],langs:Array.from(h)}),h.forEach(e=>f.add(b(e)))),d}export async function preloadLanguages(e){d||await w();const t=[];for(const r of e){const e=b(r);f.has(e)||t.push((async()=>{try{const{bundledLanguages:t}=await import("shiki/langs");e in t&&(await d.loadLanguage(t[e]),f.add(e))}catch{}})())}await Promise.all(t)}export async function getMarkdownIt(e={},n={}){const l=await w(),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:v,permalink:r.permalink.linkInsideHeader({symbol:"#",placement:"before"})}),f.use(a),f.use(s.full||s.bare),o(f),i(f),e.markoTags?.enabled&&f.use(p,{tagsDir:e.markoTags?.tagsDir||"tags/",onTagDetected:(e,t)=>{g.addDetectedTag(e,n.filePath||"unknown",t)}}),f.use(m),e.base&&f.use(u,e.base),f}export async function parseMarkdown(t,r={},a={},s){const{data:n,content:o,excerpt:i}=e(t,{excerpt:!0,excerpt_separator:"\x3c!-- more --\x3e"}),c=await l(o,{root:a.rootDir??process.cwd(),currentFile:a.filePath??""}),p=s||await async function(e,t){return getMarkdownIt(e,t)}(r,a),g=p.render(c,a),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=k(r),n=v(r);t.push({level:e,title:a,slug:n})}return t}(c):[];return{frontmatter:n,content:c,html:g,excerpt:i,headers:$(u)}}function k(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 v(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 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 +1 @@
1
- const t=new Set(["div","p","h1","h2","h3","h4","h5","h6","hr","blockquote","pre","figure","figcaption","span","a","strong","em","b","i","u","s","sub","sup","code","small","ul","ol","li","dl","dt","dd","table","thead","tbody","tfoot","tr","th","td","caption","col","colgroup","form","input","button","select","datalist","optgroup","option","textarea","label","fieldset","legend","img","video","audio","source","track","canvas","map","area","script","style","link","meta","noscript","html","head","body","title","base","section","article","aside","header","footer","nav","main","address","details","summary","dialog","iframe","object","param","embed"]);function e(t){return`<div data-marko-tag="${t}"></div>`}function n(t,e){if(!t||!t.trim())return"";if(function(t){return/<[a-z][a-z0-9-]*(?:\s[^>]*?)?>/.test(t)||/<@[\w-]+/.test(t)}(t))return t;let n=e.render(t);const r=n.trim();if(r.startsWith("<p>")&&r.endsWith("</p>")){const t=r.slice(3,-4);t.includes("</div>")||t.includes("<pre>")||(n=t)}return n}function r(t,e){const n=/<([a-z][a-z0-9-]*)(\s[^>]*?)?(\/)?>/g;n.lastIndex=e;const r=n.exec(t);return r?{name:r[1],attrs:r[2]||"",selfClosing:"/"===r[3],end:r.index+r[0].length}:null}function s(e,s,i,o,a){const d=r(e,s);if(!d)return null;const c=d.name;if(t.has(c))return null;if(o&&a&&o(c,a(e,s)),d.selfClosing)return{id:0,start:s,end:d.end,originalTag:e.slice(s,d.end),content:"",processedTag:e.slice(s,d.end)};let l=d.end,u=1;const f=`</${c}>`;for(;l<e.length&&u>0;){const t=e.indexOf("<"+c,l),o=e.indexOf(f,l);if(-1===o)break;if(-1!==t&&t<o){const n=r(e,t);if(n&&n.name===c){u++,l=n.end;continue}}if(u--,0===u){const t=o+f.length,r=e.slice(d.end,o),a=n(r,i);return{id:0,start:s,end:t,originalTag:e.slice(s,t),content:r,processedTag:`<${c}${d.attrs}>${a}</${c}>`}}l=o+f.length}return{id:0,start:s,end:d.end,originalTag:e.slice(s,d.end),content:"",processedTag:`<${c}${d.attrs}/>`}}function i(t,e){return t.substring(0,e).split("\n").length}export function preserveTagsPlugin(t,n){const{onTagDetected:r}=n||{},o=t.render.bind(t);t.render=(n,a)=>{const d=function(t,e,n,r){const i=[];let o=0,a=0;for(;o<t.length;){const d=t.indexOf("<",o);if(-1===d)break;const c=s(t,d,e,n,r);c?(c.id=a++,i.push(c),o=c.end):o=d+1}return i}(n,t,r,i);let c=n,l=0;for(const t of d){const n=e(t.id),r=t.start+l,s=t.end+l;c=c.substring(0,r)+n+c.substring(s),l+=n.length-(t.end-t.start)}const u=function(t,n){let r=t;for(let t=n.length-1;t>=0;t--){const s=n[t],i=e(s.id);r=r.replace(i,s.processedTag)}return r}(o(c,a)+"",d);return u}}
1
+ const t=new Set(["div","p","h1","h2","h3","h4","h5","h6","hr","blockquote","pre","figure","figcaption","span","a","strong","em","b","i","u","s","sub","sup","code","small","ul","ol","li","dl","dt","dd","table","thead","tbody","tfoot","tr","th","td","caption","col","colgroup","form","input","button","select","datalist","optgroup","option","textarea","label","fieldset","legend","img","video","audio","source","track","canvas","map","area","script","style","link","meta","noscript","html","head","body","title","base","section","article","aside","header","footer","nav","main","address","details","summary","dialog","iframe","object","param","embed"]);function n(t){return`<div data-marko-tag="${t}"></div>`}function e(t,n){for(const e of n){if(t<e.start)return null;if(t>=e.start&&t<e.end)return e}return null}function r(t,n){if(!t||!t.trim())return"";if(function(t){return/<[a-z][a-z0-9-]*(?:\s[^>]*?)?>/.test(t)||/<@[\w-]+/.test(t)}(t))return t;let e=n.render(t);const r=e.trim();if(r.startsWith("<p>")&&r.endsWith("</p>")){const t=r.slice(3,-4);t.includes("</div>")||t.includes("<pre>")||(e=t)}return e}function s(t,n){const e=/<([a-z][a-z0-9-]*)(\s[^>]*?)?(\/)?>/g;e.lastIndex=n;const r=e.exec(t);return r&&r.index===n?{name:r[1],attrs:r[2]||"",selfClosing:"/"===r[3],end:r.index+r[0].length}:null}function i(n,e,i,o,a){const c=s(n,e);if(!c)return null;const l=c.name;if(t.has(l))return null;if(o&&a&&o(l,a(n,e)),c.selfClosing)return{id:0,start:e,end:c.end,originalTag:n.slice(e,c.end),content:"",processedTag:n.slice(e,c.end)};let d=c.end,u=1;const f=`</${l}>`;for(;d<n.length&&u>0;){const t=n.indexOf("<"+l,d),o=n.indexOf(f,d);if(-1===o)break;if(-1!==t&&t<o){const e=s(n,t);if(e&&e.name===l){u++,d=e.end;continue}}if(u--,0===u){const t=o+f.length,s=n.slice(c.end,o),a=r(s,i);return{id:0,start:e,end:t,originalTag:n.slice(e,t),content:s,processedTag:`<${l}${c.attrs}>${a}</${l}>`}}d=o+f.length}return{id:0,start:e,end:c.end,originalTag:n.slice(e,c.end),content:"",processedTag:`<${l}${c.attrs}/>`}}function o(t,n){return t.substring(0,n).split("\n").length}export function preserveTagsPlugin(t,r){const{onTagDetected:s}=r||{},a=t.render.bind(t);t.render=(r,c)=>{const l=function(t,n,r,s){const o=[],a=function(t){const n=[],r=t.split("\n");let s=0,i=null;for(const t of r){const e=t.length+1,r=t.match(/^( {0,3})(`{3,}|~{3,})(.*)$/);i?RegExp(`^${i.indent}[${i.marker[0]}]{${i.marker.length},}\\s*$`).test(t)&&(n.push({start:i.start,end:s+e}),i=null):r&&(i={indent:r[1],marker:r[2],start:s}),s+=e}i&&n.push({start:i.start,end:t.length});let o=0;for(;o<t.length;){const r=e(o,n);if(r){o=r.end;continue}if("`"!==t[o]){o++;continue}let s=1;for(;"`"===t[o+s];)s++;const i="`".repeat(s),a=t.indexOf(i,o+s);-1===a?o+=s:(n.push({start:o,end:a+s}),o=a+s)}return n.sort((t,n)=>t.start-n.start)}(t);let c=0,l=0;for(;c<t.length;){const d=e(c,a);if(d){c=d.end;continue}const u=t.indexOf("<",c);if(-1===u)break;const f=e(u,a);if(f){c=f.end;continue}const g=i(t,u,n,r,s);g?(g.id=l++,o.push(g),c=g.end):c=u+1}return o}(r,t,s,o);let d=r,u=0;for(const t of l){const e=n(t.id),r=t.start+u,s=t.end+u;d=d.substring(0,r)+e+d.substring(s),u+=e.length-(t.end-t.start)}const f=function(t,e){let r=t;for(let t=e.length-1;t>=0;t--){const s=e[t],i=n(s.id);r=r.replace(i,s.processedTag)}return r}(a(d,c)+"",l);return f}}
@@ -1 +1 @@
1
- import e from"gray-matter";import r from"markdown-it";import{getMarkdownIt as t}from"./loader.js";let l,n=null;export async function renderMarkdown(r,p){if(!n){const e={...p,markoTags:{enabled:!0,...p?.markoTags}};n=await t(e),l=e}const{data:g,content:s,excerpt:u}=e(r,{excerpt:!0,excerpt_separator:"\x3c!-- more --\x3e"});return{frontmatter:g,content:s,html:n.render(s),excerpt:u,headers:a(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=o(t);r.push({level:e,title:l,slug:a})}return r}(s))}}export function resetRendererCache(){n=null,l=void 0}export function getRendererInstance(){return n}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 a(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 o(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}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,"-")}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "markopress",
3
- "version": "0.0.16",
3
+ "version": "0.0.18",
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",
@@ -92,6 +92,7 @@
92
92
  "markdown-it-emoji": "^3.0.0",
93
93
  "minisearch": "^7.2.0",
94
94
  "p-limit": "^4.0.0",
95
+ "sharp": "^0.34.5",
95
96
  "shiki": "^1.0.0",
96
97
  "sitemap": "^8.0.0",
97
98
  "zod": "^4.3.5"
@@ -0,0 +1,94 @@
1
+ /*!
2
+ * MarkoPress Image Component
3
+ * Responsive image component similar to Gatsby/Astro
4
+ */
5
+
6
+ <const/layout=input.layout || 'responsive'/>
7
+ <const/loading=input.loading || 'lazy'/>
8
+ <const/decoding=input.decoding || 'async'/>
9
+ <const/width=input.width ? Number(input.width) : undefined/>
10
+ <const/height=input.height ? Number(input.height) : undefined/>
11
+ <const/aspectRatio=(width && height ? (height / width * 100).toFixed(2) : '56.25')/>
12
+ <const/sizes=input.sizes || (layout === 'fixed' && width ? `${width}px` : layout === 'fluid' ? '100vw' : undefined)/>
13
+ <const/wrapperStyle={
14
+ display: 'inline-block',
15
+ overflow: 'hidden',
16
+ width: layout === 'fixed' && width ? `${width}px` : layout === 'fluid' ? '100%' : 'auto',
17
+ position: 'relative',
18
+ aspectRatio: width && height ? `${width} / ${height}` : undefined,
19
+ }/>
20
+ <const/imageStyle={
21
+ display: 'block',
22
+ maxWidth: '100%',
23
+ height: layout === 'fixed' && height ? `${height}px` : 'auto',
24
+ width: layout === 'fixed' && width ? `${width}px` : '100%',
25
+ position: 'relative',
26
+ zIndex: '1',
27
+ ...(input.style || {}),
28
+ }/>
29
+ <const/placeholderStyle={
30
+ position: 'absolute',
31
+ top: '0',
32
+ left: '0',
33
+ width: '100%',
34
+ height: '100%',
35
+ backgroundColor: '#f3f4f6',
36
+ backgroundImage: input.blurDataURL
37
+ ? `url("${input.blurDataURL}")`
38
+ : 'linear-gradient(90deg, #f3f4f6 25%, #e5e7eb 50%, #f3f4f6 75%)',
39
+ backgroundSize: input.blurDataURL ? 'cover' : '200% 100%',
40
+ backgroundPosition: 'center',
41
+ backgroundRepeat: 'no-repeat',
42
+ animation: input.blurDataURL ? undefined : 'skeleton-loading 1.5s infinite',
43
+ paddingBottom: layout === 'fluid' ? `${aspectRatio}%` : '0',
44
+ opacity: '1',
45
+ transition: 'opacity 0.3s ease-in-out',
46
+ zIndex: '0',
47
+ }/>
48
+
49
+ <div
50
+ class={
51
+ 'markopress-image-wrapper': true,
52
+ [input.class]: input.class
53
+ }
54
+ style=wrapperStyle>
55
+
56
+ <if=input.placeholder === 'blur'>
57
+ <div class="markopress-image-placeholder" style=placeholderStyle/>
58
+ </if>
59
+
60
+ <img
61
+ src=input.src
62
+ alt=input.alt || ''
63
+ width=width
64
+ height=height
65
+ loading=loading
66
+ decoding=decoding
67
+ fetchpriority=input.fetchpriority
68
+ srcset=input.srcset
69
+ sizes=sizes
70
+ style=imageStyle
71
+ class="markopress-image"
72
+ />
73
+ </div>
74
+
75
+ <style>
76
+ .markopress-image-wrapper {
77
+ position: relative;
78
+ }
79
+
80
+ .markopress-image {
81
+ display: block;
82
+ position: relative;
83
+ z-index: 1;
84
+ }
85
+
86
+ @keyframes skeleton-loading {
87
+ 0% {
88
+ background-position: 200% 0;
89
+ }
90
+ 100% {
91
+ background-position: -200% 0;
92
+ }
93
+ }
94
+ </style>
@@ -12,27 +12,44 @@ import { search as runSearch } from 'markopress/search';
12
12
  <let/selectedIndex=0/>
13
13
  <let/searchTimeout=undefined/>
14
14
 
15
- <lifecycle onUpdate() {
16
- if (isOpen) {
17
- requestAnimationFrame(() => {
18
- const input = document.querySelector('.search-input') as HTMLInputElement;
19
- input?.focus();
20
- });
21
- }
22
- }/>
23
-
24
15
  <lifecycle onMount() {
25
16
  if (typeof window !== 'undefined') {
26
- window.addEventListener('keydown', (e: KeyboardEvent) => {
17
+ const handleKeyDown = (e: KeyboardEvent) => {
18
+ if (e.isComposing || e.keyCode === 229) {
19
+ return;
20
+ }
21
+
27
22
  if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
28
23
  e.preventDefault();
29
24
  isOpen = !isOpen;
25
+ if (isOpen) {
26
+ requestAnimationFrame(() => {
27
+ const input = document.querySelector('.search-input') as HTMLInputElement;
28
+ input?.focus();
29
+ });
30
+ }
30
31
  }
31
32
  if (e.key === '/' && !isOpen && document.activeElement?.tagName !== 'INPUT') {
32
33
  e.preventDefault();
33
34
  isOpen = true;
35
+ requestAnimationFrame(() => {
36
+ const input = document.querySelector('.search-input') as HTMLInputElement;
37
+ input?.focus();
38
+ });
34
39
  }
35
- });
40
+ if (e.key === 'Escape' && isOpen) {
41
+ e.preventDefault();
42
+ isOpen = false;
43
+ query = "";
44
+ results = [];
45
+ }
46
+ };
47
+
48
+ window.addEventListener('keydown', handleKeyDown);
49
+
50
+ return () => {
51
+ window.removeEventListener('keydown', handleKeyDown);
52
+ };
36
53
  }
37
54
  }/>
38
55
 
@@ -41,6 +58,10 @@ import { search as runSearch } from 'markopress/search';
41
58
  aria-label="Search"
42
59
  onClick() {
43
60
  isOpen = true;
61
+ requestAnimationFrame(() => {
62
+ const input = document.querySelector('.search-input') as HTMLInputElement;
63
+ input?.focus();
64
+ });
44
65
  }>
45
66
  <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
46
67
  <circle cx="11" cy="11" r="8"></circle>
@@ -81,12 +102,8 @@ import { search as runSearch } from 'markopress/search';
81
102
  placeholder="Search documentation..."
82
103
  value=query
83
104
  autofocus=true
84
- onKeydown(e) {
85
- if (e.key === 'Escape') {
86
- isOpen = false;
87
- query = "";
88
- results = [];
89
- } else if (e.key === 'ArrowDown') {
105
+ onKeyDown(e) {
106
+ if (e.key === 'ArrowDown') {
90
107
  e.preventDefault();
91
108
  selectedIndex = Math.min(selectedIndex + 1, results.length - 1);
92
109
  } else if (e.key === 'ArrowUp') {
@@ -88,11 +88,13 @@ export async function GET(context, next) {
88
88
  const { default: path } = await import('node:path');
89
89
 
90
90
  // For root-level content, use contentDir directly; for modules, use module-specific dir
91
+ // Auto-default to content/{contentType} if dir not specified
91
92
  const contentDir = contentType === 'root'
92
93
  ? config.contentDir
93
94
  : (typeof config.content[contentType] === 'string'
94
95
  ? config.content[contentType]
95
- : config.content[contentType]?.dir);
96
+ : config.content[contentType]?.dir
97
+ || `${config.contentDir}/${contentType}`); // āœ… Smart default!
96
98
 
97
99
  if (!contentDir) {
98
100
  return next();
@@ -0,0 +1,160 @@
1
+ import { config } from '{{CONFIG_PATH}}';
2
+
3
+ const IS_BUILD = {{IS_BUILD}};
4
+
5
+ // === BUILD MODE: pre-compiled .marko files + metadata JSON ===
6
+ // === DEV MODE: request-time rendering via virtual modules ===
7
+
8
+ let moduleEnhancements = {};
9
+ let allMetadata = {};
10
+ let contentModules = {};
11
+
12
+ // {{IF_BUILD_START}}
13
+ if (IS_BUILD) {
14
+ // Build mode: load pre-compiled content via import.meta.glob (Vite resolves at build time)
15
+ contentModules = import.meta.glob('/src/.generated/markdown/{{CONTENT_TYPE}}/*.marko');
16
+ try {
17
+ allMetadata = (await import('/src/.generated/content-metadata.js')).default;
18
+ } catch {
19
+ allMetadata = {};
20
+ }
21
+ }
22
+
23
+ // Also load module enhancements in build mode
24
+ if (IS_BUILD) {
25
+ try {
26
+ moduleEnhancements = (await import('/src/.generated/module-enhancements.js')).default;
27
+ } catch {
28
+ moduleEnhancements = {};
29
+ }
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}}
46
+
47
+ export async function GET(context, next) {
48
+ const slug = context.params.slug || 'index';
49
+ const contentType = '{{CONTENT_TYPE}}';
50
+
51
+ // Set base path for links (strip trailing slash for prefix use)
52
+ const base = (config.site.base || '/').replace(/\/$/, '');
53
+ context.base = base || '/';
54
+
55
+ let contentComponent;
56
+ let frontmatter = {};
57
+ let headers = [];
58
+
59
+ // {{IF_BUILD_START}}
60
+ if (IS_BUILD) {
61
+ // === BUILD MODE ===
62
+ // Look up pre-compiled .marko module from glob map
63
+ const modulePath = `/src/.generated/markdown/${contentType}/${slug}.marko`;
64
+ const loader = contentModules[modulePath];
65
+
66
+ if (!loader) {
67
+ return next();
68
+ }
69
+
70
+ const mod = await loader();
71
+ contentComponent = mod.default;
72
+
73
+ // Look up metadata from pre-generated JSON
74
+ const metaKey = `${contentType}/${slug}`;
75
+ const meta = allMetadata[metaKey] || {};
76
+ frontmatter = meta.frontmatter || {};
77
+ headers = meta.headers || [];
78
+ }
79
+ // {{IF_BUILD_END}}
80
+
81
+ // {{IF_DEV_START}}
82
+ if (!IS_BUILD) {
83
+ // === DEV MODE ===
84
+ // Render markdown at request time using virtual modules
85
+ const { renderMarkdown } = await import('markopress/markdown');
86
+ const { loadMarkdownModule, registerMarkdownContent } = await import('{{VITE_PLUGIN_PATH}}');
87
+ const { promises: fs } = await import('node:fs');
88
+ const { default: path } = await import('node:path');
89
+
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);
96
+
97
+ if (!contentDir) {
98
+ return next();
99
+ }
100
+
101
+ const filePath = path.join(config.root, contentDir, `${slug}.md`);
102
+
103
+ try {
104
+ const source = await fs.readFile(filePath, 'utf-8').catch(() => null);
105
+
106
+ if (!source) {
107
+ return next();
108
+ }
109
+
110
+ const rendered = await renderMarkdown(source, { base: base !== '/' ? base : undefined });
111
+ frontmatter = rendered.frontmatter;
112
+ headers = rendered.headers || [];
113
+
114
+ const contentId = `${contentType}/${slug}`;
115
+ await registerMarkdownContent(contentId, rendered.html);
116
+ contentComponent = (await loadMarkdownModule(`virtual:markdown-content/${contentId}`)).default;
117
+ } catch (error) {
118
+ console.error(`Error rendering ${contentType}/${slug}:`, error);
119
+ return next();
120
+ }
121
+ }
122
+ // {{IF_DEV_END}}
123
+
124
+ // === SHARED: Set context for template ===
125
+ // Prefix navbar links with base path
126
+ context.navbar = config.theme.options.navbar || [];
127
+ context.lang = config.site.lang || 'en-US';
128
+ context.siteHead = config.site.head || [];
129
+ context.footer = config.theme.options.footer || null;
130
+ context.hasComponentStyles = !!(config.markdown?.markoTags?.enabled);
131
+
132
+ // Head injection from plugin
133
+ context.headTop = config._headInject?.headTop || [];
134
+ context.headBottom = config._headInject?.headBottom || [];
135
+
136
+ context.title = frontmatter.title || slug;
137
+ context.description = frontmatter.description || '';
138
+ context.contentComponent = contentComponent;
139
+ context.slug = slug;
140
+
141
+ // Load module enhancements for this content type (sidebar, etc.)
142
+ const enhancements = moduleEnhancements[contentType] || {};
143
+ context.sidebar = enhancements.sidebar || null;
144
+
145
+ // TOC (table of contents)
146
+ if (headers && headers.length > 0) {
147
+ context.toc = headers;
148
+ }
149
+
150
+ // Blog-specific fields
151
+ if (frontmatter.date) {
152
+ context.date = frontmatter.date;
153
+ }
154
+ if (frontmatter.author) {
155
+ context.author = frontmatter.author;
156
+ }
157
+ if (frontmatter.tags) {
158
+ context.tags = frontmatter.tags;
159
+ }
160
+ }