@beforesemicolon/builder 1.8.20 → 1.8.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -303,6 +303,7 @@ A template config exports an object:
303
303
 
304
304
  ```js
305
305
  export default {
306
+ meta: {},
306
307
  markedOptions: {},
307
308
  markdownLayouts: {},
308
309
  scripts: {},
@@ -319,6 +320,7 @@ Merge behavior:
319
320
 
320
321
  - `markdownLayouts` are shallow-merged by layout name.
321
322
  - `scripts` are shallow-merged by script name.
323
+ - `meta` is shallow-merged by metadata field.
322
324
  - `theme.light` and `theme.dark` are shallow-merged by CSS variable name.
323
325
  - Other top-level config values use the docs source config value when provided.
324
326
 
@@ -328,6 +330,12 @@ Example docs source extension:
328
330
  import pricingCards from './layouts/pricing-cards.js'
329
331
 
330
332
  export default {
333
+ meta: {
334
+ siteName: 'Example',
335
+ title: 'Example Docs',
336
+ description: 'Documentation for Example.',
337
+ image: '/assets/site-image.jpg',
338
+ },
331
339
  markdownLayouts: {
332
340
  'pricing-cards': pricingCards,
333
341
  },
@@ -1,4 +1,4 @@
1
- "use strict";var he=Object.create;var j=Object.defineProperty;var ge=Object.getOwnPropertyDescriptor;var ye=Object.getOwnPropertyNames;var we=Object.getPrototypeOf,ve=Object.prototype.hasOwnProperty;var m=(e,t)=>j(e,"name",{value:t,configurable:!0});var Se=(e,t)=>{for(var r in t)j(e,r,{get:t[r],enumerable:!0})},Q=(e,t,r,a)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of ye(t))!ve.call(e,n)&&n!==r&&j(e,n,{get:()=>t[n],enumerable:!(a=ge(t,n))||a.enumerable});return e};var w=(e,t,r)=>(r=e!=null?he(we(e)):{},Q(t||!e||!e.__esModule?j(r,"default",{value:e,enumerable:!0}):r,e)),De=e=>Q(j({},"__esModule",{value:!0}),e);var Ue={};Se(Ue,{buildDocs:()=>Ne});module.exports=De(Ue);const ze={};var se=require("marked"),c=require("fs/promises"),o=w(require("path"),1),re=w(require("front-matter"),1),oe=w(require("isomorphic-dompurify"),1),ae=require("marked-highlight"),_=w(require("highlight.js"),1),ne=w(require("./templates/default.template.js"),1),ie=w(require("./renderer/index.js"),1),le=require("./markdown-layout/index.js"),ce=w(require("clean-css"),1),me=w(require("html-minifier"),1),X=w(require("@putout/minify"),1),$=require("url"),p=w(require("fs"),1);let V,A="";const Y=new ce.default({inline:["none"]}),R=new Map;R.set("default",ne.default);let W;const Z=m((e,t)=>t===!1?!1:t===void 0?e:e===!1||e===void 0?t:{...e,...t},"mergeThemeMode"),be=m((e,t)=>{if(!(!e&&!t))return{light:Z(e?.light,t?.light),dark:Z(e?.dark,t?.dark)}},"mergeTheme"),xe=m((e,t)=>({...e,...t,markdownLayouts:{...e.markdownLayouts,...t.markdownLayouts},scripts:{...e.scripts,...t.scripts},theme:be(e.theme,t.theme)}),"mergeTemplateConfig"),$e=m((e,t)=>{if(typeof e=="function")return!0;const{match:r}=e;return r?typeof r=="string"?t.includes(r):Array.isArray(r)?r.some(a=>t.includes(a)):r instanceof RegExp?r.test(t):r(t):!0},"scriptMatches"),Pe=m((e,t)=>e?Object.values(e).filter(r=>r!==!1).filter(r=>$e(r,t)).map(r=>typeof r=="function"?r():r.render()):[],"renderPageScripts"),G=m((e,t)=>{const r=Object.entries(t).filter(([a])=>a.startsWith("--")).map(([a,n])=>` ${a}: ${n};`).join(`
1
+ "use strict";var he=Object.create;var C=Object.defineProperty;var ge=Object.getOwnPropertyDescriptor;var ye=Object.getOwnPropertyNames;var we=Object.getPrototypeOf,ve=Object.prototype.hasOwnProperty;var m=(e,t)=>C(e,"name",{value:t,configurable:!0});var Se=(e,t)=>{for(var r in t)C(e,r,{get:t[r],enumerable:!0})},Q=(e,t,r,a)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of ye(t))!ve.call(e,n)&&n!==r&&C(e,n,{get:()=>t[n],enumerable:!(a=ge(t,n))||a.enumerable});return e};var v=(e,t,r)=>(r=e!=null?he(we(e)):{},Q(t||!e||!e.__esModule?C(r,"default",{value:e,enumerable:!0}):r,e)),De=e=>Q(C({},"__esModule",{value:!0}),e);var Ue={};Se(Ue,{buildDocs:()=>Ne});module.exports=De(Ue);const ze={};var se=require("marked"),c=require("fs/promises"),o=v(require("path"),1),re=v(require("front-matter"),1),oe=v(require("isomorphic-dompurify"),1),ae=require("marked-highlight"),_=v(require("highlight.js"),1),ne=v(require("./templates/default.template.js"),1),ie=v(require("./renderer/index.js"),1),le=require("./markdown-layout/index.js"),ce=v(require("clean-css"),1),me=v(require("html-minifier"),1),X=v(require("@putout/minify"),1),P=require("url"),p=v(require("fs"),1);let V,A="";const Y=new ce.default({inline:["none"]}),R=new Map;R.set("default",ne.default);let W;const Z=m((e,t)=>t===!1?!1:t===void 0?e:e===!1||e===void 0?t:{...e,...t},"mergeThemeMode"),be=m((e,t)=>{if(!(!e&&!t))return{light:Z(e?.light,t?.light),dark:Z(e?.dark,t?.dark)}},"mergeTheme"),xe=m((e,t)=>({...e,...t,markdownLayouts:{...e.markdownLayouts,...t.markdownLayouts},scripts:{...e.scripts,...t.scripts},theme:be(e.theme,t.theme)}),"mergeTemplateConfig"),$e=m((e,t)=>{if(typeof e=="function")return!0;const{match:r}=e;return r?typeof r=="string"?t.includes(r):Array.isArray(r)?r.some(a=>t.includes(a)):r instanceof RegExp?r.test(t):r(t):!0},"scriptMatches"),Pe=m((e,t)=>e?Object.values(e).filter(r=>r!==!1).filter(r=>$e(r,t)).map(r=>typeof r=="function"?r():r.render()):[],"renderPageScripts"),G=m((e,t)=>{const r=Object.entries(t).filter(([a])=>a.startsWith("--")).map(([a,n])=>` ${a}: ${n};`).join(`
2
2
  `);return r?`${e} {
3
3
  ${r}
4
4
  }`:""},"renderThemeBlock"),ke=m(e=>{if(!e)return"";const t=e.light===!1,r=e.dark===!1,a=e.light===!1?void 0:e.light,n=r?void 0:e.dark,u=a||n;return u?[G(":root",u),n&&!t?`@media (prefers-color-scheme: dark) {
@@ -7,7 +7,7 @@ ${G(" :root",n)}
7
7
  .site-logo-dark { display: block; }`:"",r&&a?`.site-logo-light { display: block; }
8
8
  .site-logo-dark { display: none; }`:""].filter(Boolean).join(`
9
9
 
10
- `):""},"renderThemeStylesheet"),Te=m((e="")=>String(e).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&apos;"),"escapeXML"),Me=m((e="")=>e.replace(/```[\s\S]*?```/g," ").replace(/`([^`]+)`/g,"$1").replace(/!\[[^\]]*]\([^)]*\)/g," ").replace(/\[([^\]]+)]\([^)]*\)/g,"$1").replace(/^#{1,6}\s+/gm,"").replace(/^---+$/gm," ").replace(/[>*_~]/g,"").replace(/\s+/g," ").trim(),"stripMarkdown"),Ce=m(e=>e==="/index.html"?"/":e.replace(/\/index\.html$/,"/").replace(/\.html$/,"").replace(/\/+/g,"/"),"pagePathToUrlPath"),ee=m((e,t)=>e?`${e.replace(/\/$/,"")}${t==="/"?"/":t}`:t,"canonicalUrl"),je=m(async(e,t)=>p.default.existsSync(e)?(await(0,c.mkdir)(o.default.dirname(t),{recursive:!0}),await(0,c.cp)(e,t),!0):!1,"copyIfExists"),Fe=m(async e=>{const t=o.default.resolve(e,"logo.svg");p.default.existsSync(t)&&await Promise.all(["logo.light.svg","logo.dark.svg"].map(async r=>{const a=o.default.resolve(e,r);p.default.existsSync(a)||await(0,c.cp)(t,a)}))},"ensureLogoModeAssets"),Ge=m(e=>{const t={sitemap:!0,robots:!0,llms:!0,llmsFull:!0,netlify:!1};return e===!1?{sitemap:!1,robots:!1,llms:!1,llmsFull:!1,netlify:!1}:e===!0||e===void 0?t:{...t,...e}},"normalizeGeneratedFilesConfig"),Le=m((e,t)=>{if(!t)return"";const r=new Date().toISOString().slice(0,10);return`<?xml version="1.0" encoding="UTF-8"?>
10
+ `):""},"renderThemeStylesheet"),Te=m((e="")=>String(e).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&apos;"),"escapeXML"),Me=m((e="")=>e.replace(/```[\s\S]*?```/g," ").replace(/`([^`]+)`/g,"$1").replace(/!\[[^\]]*]\([^)]*\)/g," ").replace(/\[([^\]]+)]\([^)]*\)/g,"$1").replace(/^#{1,6}\s+/gm,"").replace(/^---+$/gm," ").replace(/[>*_~]/g,"").replace(/\s+/g," ").trim(),"stripMarkdown"),je=m(e=>e==="/index.html"?"/":e.replace(/\/index\.html$/,"/").replace(/\.html$/,"").replace(/\/+/g,"/"),"pagePathToUrlPath"),ee=m((e,t)=>e?`${e.replace(/\/$/,"")}${t==="/"?"/":t}`:t,"canonicalUrl"),Ce=m(async(e,t)=>p.default.existsSync(e)?(await(0,c.mkdir)(o.default.dirname(t),{recursive:!0}),await(0,c.cp)(e,t),!0):!1,"copyIfExists"),Fe=m(async e=>{const t=o.default.resolve(e,"logo.svg");p.default.existsSync(t)&&await Promise.all(["logo.light.svg","logo.dark.svg"].map(async r=>{const a=o.default.resolve(e,r);p.default.existsSync(a)||await(0,c.cp)(t,a)}))},"ensureLogoModeAssets"),Ge=m(e=>{const t={sitemap:!0,robots:!0,llms:!0,llmsFull:!0,netlify:!1};return e===!1?{sitemap:!1,robots:!1,llms:!1,llmsFull:!1,netlify:!1}:e===!0||e===void 0?t:{...t,...e}},"normalizeGeneratedFilesConfig"),Le=m((e,t)=>{if(!t)return"";const r=new Date().toISOString().slice(0,10);return`<?xml version="1.0" encoding="UTF-8"?>
11
11
  <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
12
12
  ${e.map(n=>{const u=n.urlPath==="/"?"1.0":"0.8";return` <url>
13
13
  <loc>${Te(ee(t,n.urlPath))}</loc>
@@ -75,4 +75,4 @@ for = "/robots.txt"
75
75
  [headers.values]
76
76
  Content-Type = "text/plain; charset=utf-8"
77
77
  Cache-Control = "public, max-age=0, must-revalidate"
78
- `,"generateNetlifyToml"),x=m(async(e,t,r,a)=>{if(await je(e,t))return;const n=r();if(!n){a&&console.warn(a);return}await(0,c.mkdir)(o.default.dirname(t),{recursive:!0}),await(0,c.writeFile)(t,n)},"writeGeneratedFile"),_e=m(async({docsDir:e,docsSiteDir:t,pages:r,siteUrl:a,publicDir:n,generatedFiles:u})=>{u.sitemap&&await x(o.default.resolve(e,"sitemap.xml"),o.default.resolve(t,"sitemap.xml"),()=>Le(r,a),"Skipping generated sitemap.xml because buildDocs({ siteUrl }) is not set."),u.robots&&await x(o.default.resolve(e,"robots.txt"),o.default.resolve(t,"robots.txt"),()=>Ee(a)),u.llms&&await x(o.default.resolve(e,"llms.txt"),o.default.resolve(t,"llms.txt"),()=>Oe(r)),u.llmsFull&&await x(o.default.resolve(e,"llms-full.txt"),o.default.resolve(t,"llms-full.txt"),()=>Ae(r,a)),u.netlify&&(await x(o.default.resolve(e,"_redirects"),o.default.resolve(t,"_redirects"),()=>Re(r,u)),await x(o.default.resolve(e,"netlify.toml"),o.default.resolve(t,"netlify.toml"),()=>We(n&&o.default.basename(n))))},"writeGeneratedDocsFiles"),te=m((e={},t,r)=>{const a=new se.Marked((0,ae.markedHighlight)({langPrefix:"hljs language-",highlight(n,u){const P=_.default.getLanguage(u)?u:"plaintext";return _.default.highlight(n,{language:P}).value}}));return a.use({renderer:ie.default}),a.use((0,le.createMarkdownLayoutExtension)({marked:a,handlers:e.markdownLayouts,renderMarkdown:r})),e.markedOptions&&a.use(e.markedOptions),t&&a.use(t),a},"createBaseMarked"),Be=m((e={},t)=>{const r=te(e,t),a=m(u=>r.parse(u),"renderMarkdown"),n=te(e,t,a);return n.use({hooks:{postprocess(u){const{layout:P="default",...y}=W.attributes,k=Pe(e.scripts,u);u=oe.default.sanitize(u);const T=[...u.matchAll(/<h([0-6])\sid="[^"]+".*?>.*?<a\s+href="([^"]+)".*?>([\s\S]*?)<\/a><\/h\1>/gm)].map(D=>({path:D[2],label:D[3].replace(/<[^>]+>/g,"").replace(/\s+/g," ").trim(),level:D[1]}));return R.get(P)?.({...y,content:u,siteMap:W.siteMap,tableOfContent:T,projectMeta:V,renderMarkdown:a,scripts:k,themeStylesheet:e.theme?`/stylesheets/theme.css?v=${A}`:void 0,cacheKey:A})||u}}}),n},"createMarked"),v=m(async e=>{const t=await(0,c.readdir)(e),r=[];for(const a of t){const n=o.default.extname(a);/^[._]/.test(a)||(n?r.push(o.default.join(e,a)):/(stylesheets|assets|scripts)/.test(a)||r.push(...await v(o.default.join(e,a))))}return r},"traverseDirectory"),Ne=m(async({srcDir:e,publicDir:t,markedOptions:r,template:a,siteUrl:n,generatedFiles:u}={})=>{A=Date.now().toString(36);try{const s=o.default.resolve(process.cwd(),"package.json");p.default.existsSync(s)&&(V=JSON.parse(await(0,c.readFile)(s,"utf-8")))}catch(s){console.warn("Failed to load project package.json metadata",s)}const P=m(()=>{try{return __dirname}catch{return o.default.dirname((0,$.fileURLToPath)(ze.url))}},"getDirname"),y=a?o.default.resolve(P(),"templates",a):null,k=y?o.default.resolve(y,"assets"):null,T=y?o.default.resolve(y,"stylesheets"):null,D=y?o.default.resolve(y,"layouts"):null;let M={};if(y)try{let s=o.default.resolve(y,"template.config.js");if(p.default.existsSync(s)||(s=s.replace(/\.js$/,".ts")),p.default.existsSync(s)){const i=await import((0,$.pathToFileURL)(s).href);M=i.default||i}}catch(s){console.error(`Failed to load template config for ${a}`,s)}const d=e??o.default.resolve(process.cwd(),"docs"),f=t??o.default.resolve(process.cwd(),"website"),ue=Ge(u),S=o.default.resolve(d,"_template"),B=o.default.resolve(S,"assets"),N=o.default.resolve(S,"stylesheets"),U=o.default.resolve(S,"scripts"),z=o.default.resolve(S,"layouts"),q=o.default.resolve(d,"_layouts"),F=o.default.resolve(d,"assets"),H=o.default.resolve(d,"stylesheets"),L=o.default.resolve(d,"scripts");try{let s=o.default.resolve(S,"template.config.js");if(p.default.existsSync(s)||(s=s.replace(/\.js$/,".ts")),p.default.existsSync(s)){const i=await import((0,$.pathToFileURL)(s).href),l=i.default||i;M=xe(M,l)}}catch(s){console.error("Failed to load docs _template config",s)}const pe=Be(M,r);if(await(0,c.mkdir)(f,{recursive:!0}),k&&p.default.existsSync(k))try{await(0,c.cp)(k,F.replace(d,f),{recursive:!0})}catch(s){console.warn(s)}if(p.default.existsSync(F))try{await(0,c.cp)(F,F.replace(d,f),{recursive:!0})}catch(s){console.warn(s)}if(p.default.existsSync(B))try{await(0,c.cp)(B,o.default.resolve(f,"assets"),{recursive:!0})}catch(s){console.warn(s)}await Fe(o.default.resolve(f,"assets"));try{const s=new Set;if(T&&p.default.existsSync(T)&&(await v(T)).forEach(l=>s.add(l)),p.default.existsSync(H)&&(await v(H)).forEach(l=>s.add(l)),p.default.existsSync(N)&&(await v(N)).forEach(l=>s.add(l)),s.size){await(0,c.mkdir)(o.default.join(f,"stylesheets"),{recursive:!0});for(const l of s){const h=await(0,c.readFile)(l,"utf-8"),g=Y.minify(h);g.errors.length&&g.errors.forEach(console.error);const C=l.startsWith(S)?l.replace(S,f):l.startsWith(d)?l.replace(d,f):o.default.join(f,"stylesheets",o.default.basename(l));await(0,c.writeFile)(C,g.styles??h)}}const i=ke(M.theme);if(i){const l=Y.minify(i);l.errors.length&&l.errors.forEach(console.error),await(0,c.mkdir)(o.default.join(f,"stylesheets"),{recursive:!0}),await(0,c.writeFile)(o.default.join(f,"stylesheets","theme.css"),l.styles??i)}}catch(s){console.error(s)}if(p.default.existsSync(L))try{const s=await v(L);s.length&&await(0,c.mkdir)(L.replace(d,f),{recursive:!0});for(const i of s)try{const l=await(0,c.readFile)(i,"utf-8");await(0,c.writeFile)(i.replace(d,f),X.minify(l))}catch(l){console.error(l)}}catch(s){console.warn(s)}if(p.default.existsSync(U))try{const s=await v(U);s.length&&await(0,c.mkdir)(o.default.join(f,"scripts"),{recursive:!0});for(const i of s)try{const l=await(0,c.readFile)(i,"utf-8");await(0,c.writeFile)(i.replace(S,f),X.minify(l))}catch(l){console.error(l)}}catch(s){console.warn(s)}try{const s=new Map;D&&p.default.existsSync(D)&&(await v(D)).forEach(i=>{/\.(j|t)s$/.test(i)&&s.set(o.default.basename(i).replace(/\.(j|t)s$/,""),i)}),p.default.existsSync(q)&&(await v(q)).forEach(i=>{/\.(j|t)s$/.test(i)&&s.set(o.default.basename(i).replace(/\.(j|t)s$/,""),i)}),p.default.existsSync(z)&&(await v(z)).forEach(i=>{/\.(j|t)s$/.test(i)&&s.set(o.default.basename(i).replace(/\.(j|t)s$/,""),i)});for(const[i,l]of s.entries()){const h=await import((0,$.pathToFileURL)(l).href);R.set(i,h.default)}}catch(s){console.error("Template import failed",s)}const fe=await v(d),I=new Map,J=(await Promise.all(fe.filter(s=>s.endsWith(".md")).map(async s=>{const i=await(0,c.readFile)(s,"utf-8");return{filePath:s,...(0,re.default)(i)}}))).sort((s,i)=>(s.attributes.order??0)-(i.attributes.order??0)).map(({attributes:s,body:i,filePath:l})=>{const h=l.replace(d,"").replace(/\.md/,".html"),g=l.replace(d,f).replace(".md",".html"),C=g.replace(o.default.basename(g),""),E=o.default.basename(h)||"/",de=h.replace(E,"").replace(/\/$/,"");let b=I;de.split("/").filter(Boolean).forEach(O=>{b.has(O)||b.set(O,new Map),b=b.get(O)});const K={...s,path:h};return b.has(E)||b.set(E,K),{attributes:K,fileWebsitePath:g,fileDirWebsitePath:C,body:i,sourcePath:o.default.relative(d,l),siteMap:I}});for(const{attributes:s,body:i,fileDirWebsitePath:l,fileWebsitePath:h,siteMap:g}of J){W={attributes:s,siteMap:g};const C=await pe.parse(i);await(0,c.mkdir)(l,{recursive:!0}),await(0,c.writeFile)(h,me.default.minify(C,{includeAutoGeneratedTags:!0,removeAttributeQuotes:!0,removeComments:!0,removeRedundantAttributes:!0,removeScriptTypeAttributes:!0,removeStyleLinkTypeAttributes:!0,sortClassName:!0,useShortDoctype:!0,collapseWhitespace:!0,minifyJS:!0,minifyCSS:!0}))}await _e({docsDir:d,docsSiteDir:f,pages:J.map(({attributes:s,body:i,sourcePath:l})=>{const h=Ce(String(s.path)),g=Me(i);return{sourcePath:l,urlPath:h,title:String(s.title||s.name||"").trim()||"Documentation",name:String(s.name||s.title||"").trim()||"Documentation",description:String(s.description||"").trim()||g.slice(0,156)||"Documentation page.",order:Number(s.order??999),summary:g.slice(0,320)}}),siteUrl:n,publicDir:f,generatedFiles:ue})},"buildDocs");0&&(module.exports={buildDocs});
78
+ `,"generateNetlifyToml"),$=m(async(e,t,r,a)=>{if(await Ce(e,t))return;const n=r();if(!n){a&&console.warn(a);return}await(0,c.mkdir)(o.default.dirname(t),{recursive:!0}),await(0,c.writeFile)(t,n)},"writeGeneratedFile"),_e=m(async({docsDir:e,docsSiteDir:t,pages:r,siteUrl:a,publicDir:n,generatedFiles:u})=>{u.sitemap&&await $(o.default.resolve(e,"sitemap.xml"),o.default.resolve(t,"sitemap.xml"),()=>Le(r,a),"Skipping generated sitemap.xml because buildDocs({ siteUrl }) is not set."),u.robots&&await $(o.default.resolve(e,"robots.txt"),o.default.resolve(t,"robots.txt"),()=>Ee(a)),u.llms&&await $(o.default.resolve(e,"llms.txt"),o.default.resolve(t,"llms.txt"),()=>Oe(r)),u.llmsFull&&await $(o.default.resolve(e,"llms-full.txt"),o.default.resolve(t,"llms-full.txt"),()=>Ae(r,a)),u.netlify&&(await $(o.default.resolve(e,"_redirects"),o.default.resolve(t,"_redirects"),()=>Re(r,u)),await $(o.default.resolve(e,"netlify.toml"),o.default.resolve(t,"netlify.toml"),()=>We(n&&o.default.basename(n))))},"writeGeneratedDocsFiles"),te=m((e={},t,r)=>{const a=new se.Marked((0,ae.markedHighlight)({langPrefix:"hljs language-",highlight(n,u){const h=_.default.getLanguage(u)?u:"plaintext";return _.default.highlight(n,{language:h}).value}}));return a.use({renderer:ie.default}),a.use((0,le.createMarkdownLayoutExtension)({marked:a,handlers:e.markdownLayouts,renderMarkdown:r})),e.markedOptions&&a.use(e.markedOptions),t&&a.use(t),a},"createBaseMarked"),Be=m((e={},t,r)=>{const a=te(e,t),n=m(h=>a.parse(h),"renderMarkdown"),u=te(e,t,n);return u.use({hooks:{postprocess(h){const{layout:w="default",...k}=W.attributes,T=Pe(e.scripts,h);h=oe.default.sanitize(h);const M=[...h.matchAll(/<h([0-6])\sid="[^"]+".*?>.*?<a\s+href="([^"]+)".*?>([\s\S]*?)<\/a><\/h\1>/gm)].map(D=>({path:D[2],label:D[3].replace(/<[^>]+>/g,"").replace(/\s+/g," ").trim(),level:D[1]}));return R.get(w)?.({...k,content:h,siteMap:W.siteMap,tableOfContent:M,projectMeta:V,renderMarkdown:n,scripts:T,themeStylesheet:e.theme?`/stylesheets/theme.css?v=${A}`:void 0,cacheKey:A,siteUrl:r,templateConfig:e})||h}}}),u},"createMarked"),S=m(async e=>{const t=await(0,c.readdir)(e),r=[];for(const a of t){const n=o.default.extname(a);/^[._]/.test(a)||(n?r.push(o.default.join(e,a)):/(stylesheets|assets|scripts)/.test(a)||r.push(...await S(o.default.join(e,a))))}return r},"traverseDirectory"),Ne=m(async({srcDir:e,publicDir:t,markedOptions:r,template:a,siteUrl:n,generatedFiles:u}={})=>{A=Date.now().toString(36);try{const s=o.default.resolve(process.cwd(),"package.json");p.default.existsSync(s)&&(V=JSON.parse(await(0,c.readFile)(s,"utf-8")))}catch(s){console.warn("Failed to load project package.json metadata",s)}const h=m(()=>{try{return __dirname}catch{return o.default.dirname((0,P.fileURLToPath)(ze.url))}},"getDirname"),w=a?o.default.resolve(h(),"templates",a):null,k=w?o.default.resolve(w,"assets"):null,T=w?o.default.resolve(w,"stylesheets"):null,M=w?o.default.resolve(w,"layouts"):null;let D={};if(w)try{let s=o.default.resolve(w,"template.config.js");if(p.default.existsSync(s)||(s=s.replace(/\.js$/,".ts")),p.default.existsSync(s)){const i=await import((0,P.pathToFileURL)(s).href);D=i.default||i}}catch(s){console.error(`Failed to load template config for ${a}`,s)}const d=e??o.default.resolve(process.cwd(),"docs"),f=t??o.default.resolve(process.cwd(),"website"),ue=Ge(u),b=o.default.resolve(d,"_template"),B=o.default.resolve(b,"assets"),N=o.default.resolve(b,"stylesheets"),U=o.default.resolve(b,"scripts"),z=o.default.resolve(b,"layouts"),q=o.default.resolve(d,"_layouts"),F=o.default.resolve(d,"assets"),H=o.default.resolve(d,"stylesheets"),L=o.default.resolve(d,"scripts");try{let s=o.default.resolve(b,"template.config.js");if(p.default.existsSync(s)||(s=s.replace(/\.js$/,".ts")),p.default.existsSync(s)){const i=await import((0,P.pathToFileURL)(s).href),l=i.default||i;D=xe(D,l)}}catch(s){console.error("Failed to load docs _template config",s)}const pe=Be(D,r,n);if(await(0,c.mkdir)(f,{recursive:!0}),k&&p.default.existsSync(k))try{await(0,c.cp)(k,F.replace(d,f),{recursive:!0})}catch(s){console.warn(s)}if(p.default.existsSync(F))try{await(0,c.cp)(F,F.replace(d,f),{recursive:!0})}catch(s){console.warn(s)}if(p.default.existsSync(B))try{await(0,c.cp)(B,o.default.resolve(f,"assets"),{recursive:!0})}catch(s){console.warn(s)}await Fe(o.default.resolve(f,"assets"));try{const s=new Set;if(T&&p.default.existsSync(T)&&(await S(T)).forEach(l=>s.add(l)),p.default.existsSync(H)&&(await S(H)).forEach(l=>s.add(l)),p.default.existsSync(N)&&(await S(N)).forEach(l=>s.add(l)),s.size){await(0,c.mkdir)(o.default.join(f,"stylesheets"),{recursive:!0});for(const l of s){const g=await(0,c.readFile)(l,"utf-8"),y=Y.minify(g);y.errors.length&&y.errors.forEach(console.error);const j=l.startsWith(b)?l.replace(b,f):l.startsWith(d)?l.replace(d,f):o.default.join(f,"stylesheets",o.default.basename(l));await(0,c.writeFile)(j,y.styles??g)}}const i=ke(D.theme);if(i){const l=Y.minify(i);l.errors.length&&l.errors.forEach(console.error),await(0,c.mkdir)(o.default.join(f,"stylesheets"),{recursive:!0}),await(0,c.writeFile)(o.default.join(f,"stylesheets","theme.css"),l.styles??i)}}catch(s){console.error(s)}if(p.default.existsSync(L))try{const s=await S(L);s.length&&await(0,c.mkdir)(L.replace(d,f),{recursive:!0});for(const i of s)try{const l=await(0,c.readFile)(i,"utf-8");await(0,c.writeFile)(i.replace(d,f),X.minify(l))}catch(l){console.error(l)}}catch(s){console.warn(s)}if(p.default.existsSync(U))try{const s=await S(U);s.length&&await(0,c.mkdir)(o.default.join(f,"scripts"),{recursive:!0});for(const i of s)try{const l=await(0,c.readFile)(i,"utf-8");await(0,c.writeFile)(i.replace(b,f),X.minify(l))}catch(l){console.error(l)}}catch(s){console.warn(s)}try{const s=new Map;M&&p.default.existsSync(M)&&(await S(M)).forEach(i=>{/\.(j|t)s$/.test(i)&&s.set(o.default.basename(i).replace(/\.(j|t)s$/,""),i)}),p.default.existsSync(q)&&(await S(q)).forEach(i=>{/\.(j|t)s$/.test(i)&&s.set(o.default.basename(i).replace(/\.(j|t)s$/,""),i)}),p.default.existsSync(z)&&(await S(z)).forEach(i=>{/\.(j|t)s$/.test(i)&&s.set(o.default.basename(i).replace(/\.(j|t)s$/,""),i)});for(const[i,l]of s.entries()){const g=await import((0,P.pathToFileURL)(l).href);R.set(i,g.default)}}catch(s){console.error("Template import failed",s)}const fe=await S(d),I=new Map,J=(await Promise.all(fe.filter(s=>s.endsWith(".md")).map(async s=>{const i=await(0,c.readFile)(s,"utf-8");return{filePath:s,...(0,re.default)(i)}}))).sort((s,i)=>(s.attributes.order??0)-(i.attributes.order??0)).map(({attributes:s,body:i,filePath:l})=>{const g=l.replace(d,"").replace(/\.md/,".html"),y=l.replace(d,f).replace(".md",".html"),j=y.replace(o.default.basename(y),""),E=o.default.basename(g)||"/",de=g.replace(E,"").replace(/\/$/,"");let x=I;de.split("/").filter(Boolean).forEach(O=>{x.has(O)||x.set(O,new Map),x=x.get(O)});const K={...s,path:g};return x.has(E)||x.set(E,K),{attributes:K,fileWebsitePath:y,fileDirWebsitePath:j,body:i,sourcePath:o.default.relative(d,l),siteMap:I}});for(const{attributes:s,body:i,fileDirWebsitePath:l,fileWebsitePath:g,siteMap:y}of J){W={attributes:s,siteMap:y};const j=await pe.parse(i);await(0,c.mkdir)(l,{recursive:!0}),await(0,c.writeFile)(g,me.default.minify(j,{includeAutoGeneratedTags:!0,removeAttributeQuotes:!0,removeComments:!0,removeRedundantAttributes:!0,removeScriptTypeAttributes:!0,removeStyleLinkTypeAttributes:!0,sortClassName:!0,useShortDoctype:!0,collapseWhitespace:!0,minifyJS:!0,minifyCSS:!0}))}await _e({docsDir:d,docsSiteDir:f,pages:J.map(({attributes:s,body:i,sourcePath:l})=>{const g=je(String(s.path)),y=Me(i);return{sourcePath:l,urlPath:g,title:String(s.title||s.name||"").trim()||"Documentation",name:String(s.name||s.title||"").trim()||"Documentation",description:String(s.description||"").trim()||y.slice(0,156)||"Documentation page.",order:Number(s.order??999),summary:y.slice(0,320)}}),siteUrl:n,publicDir:f,generatedFiles:ue})},"buildDocs");0&&(module.exports={buildDocs});
@@ -144,9 +144,28 @@ It emits:
144
144
 
145
145
  Important current behavior:
146
146
 
147
- - The metadata helper is still Markup-specific.
148
- - It hardcodes the production site URL and some default titles/assets.
149
- - Override `_head-meta.js` in `docs/_template/layouts` for any non-Markup documentation site.
147
+ - The helper reads defaults from `template.config.js` `meta`.
148
+ - `meta.siteUrl` overrides the `buildDocs({ siteUrl })` value when needed.
149
+ - `meta.siteName`, `meta.title`, and `meta.description` customize the fallback site identity.
150
+ - `meta.image` customizes the default Open Graph and Twitter card image.
151
+ - `meta.image` can be absolute, such as `https://example.com/card.jpg`, or site-relative, such as `/assets/site-image.jpg`.
152
+ - If no image is configured, the fallback is `/assets/site-image.jpg`.
153
+
154
+ For project-specific social images, add the file under `docs/_template/assets` and point `meta.image` at it:
155
+
156
+ ```js
157
+ // docs/_template/template.config.js
158
+ export default {
159
+ meta: {
160
+ siteName: 'Example',
161
+ title: 'Example by Before Semicolon',
162
+ description: 'Documentation for Example.',
163
+ image: '/assets/site-image.jpg',
164
+ },
165
+ }
166
+ ```
167
+
168
+ Override `_head-meta.js` in `docs/_template/layouts` only when the generated metadata shape itself needs to change.
150
169
 
151
170
  ## Markdown Layout Handlers
152
171
 
@@ -590,7 +609,7 @@ docs/_template/assets/logo.svg
590
609
  docs/_template/assets/logo.light.svg
591
610
  docs/_template/assets/logo.dark.svg
592
611
  docs/_template/assets/favicon/favicon.ico
593
- docs/_template/assets/markup-banner.jpg
612
+ docs/_template/assets/site-image.jpg
594
613
  ```
595
614
 
596
615
  Because `docs/_template/assets` is copied into `publicDir/assets` after template assets, source assets can replace template assets. If only `logo.svg` is present, builder copies it to missing `logo.light.svg` and `logo.dark.svg` files in the generated site so the header and footer do not request missing assets. Projects that need correct contrast should provide explicit light and dark variants.
@@ -668,11 +687,12 @@ export default {
668
687
  Use the smallest override that solves the problem:
669
688
 
670
689
  1. Override assets for logos, favicons, images, and icons.
671
- 2. Override theme variables for color and surface tuning.
672
- 3. Add Markdown layout handlers for new page sections.
673
- 4. Override existing Markdown layout handlers only when the section HTML needs to change.
674
- 5. Override `_header.js`, `_footer.js`, or `_head-meta.js` when navigation, footer copy, metadata, or brand assumptions need to change.
675
- 6. Override `landing.js` or `document.js` only when the full page shell changes.
690
+ 2. Override `meta` fields for site identity and social card defaults.
691
+ 3. Override theme variables for color and surface tuning.
692
+ 4. Add Markdown layout handlers for new page sections.
693
+ 5. Override existing Markdown layout handlers only when the section HTML needs to change.
694
+ 6. Override `_header.js`, `_footer.js`, or `_head-meta.js` when navigation, footer copy, metadata shape, or brand assumptions need to change.
695
+ 7. Override `landing.js` or `document.js` only when the full page shell changes.
676
696
 
677
697
  Example docs extension:
678
698
 
@@ -692,6 +712,12 @@ docs/
692
712
  import pricingCards from './layouts/pricing-cards.js'
693
713
 
694
714
  export default {
715
+ meta: {
716
+ siteName: 'Example',
717
+ title: 'Example by Before Semicolon',
718
+ description: 'Documentation for Example.',
719
+ image: '/assets/site-image.jpg',
720
+ },
695
721
  markdownLayouts: {
696
722
  'pricing-cards': pricingCards,
697
723
  },
@@ -710,7 +736,6 @@ export default {
710
736
 
711
737
  Some template internals are still Markup or Before Semicolon oriented:
712
738
 
713
- - `_head-meta.js` has Markup-specific defaults and URLs.
714
739
  - `_header.js` has Markup-oriented GitHub and documentation links.
715
740
  - `_footer.js` has Before Semicolon social links, footer copy, and learning links.
716
741
  - `document.js` builds edit links against the Markup GitHub docs path.
@@ -1,22 +1,20 @@
1
1
  /* global process */
2
2
  const isDev = process.env.NODE_ENV === 'development'
3
3
 
4
- const siteUrl = 'https://markup.beforesemicolon.com'
5
- const siteName = 'Markup'
6
- const defaultTitle = 'Markup by Before Semicolon'
7
- const defaultDescription =
8
- 'Markup is a tiny reactive HTML templating system for building JavaScript user interfaces with web standards, no JSX, and no build step.'
9
- const defaultImage = `${siteUrl}/assets/markup-banner.jpg`
4
+ const fallbackSiteUrl = 'https://beforesemicolon.com'
5
+ const fallbackSiteName = 'Before Semicolon'
6
+ const fallbackTitle = 'Before Semicolon Documentation'
7
+ const fallbackDescription =
8
+ 'Documentation for Before Semicolon projects, libraries, and developer tools.'
9
+ const fallbackImage = '/assets/site-image.jpg'
10
10
  const baseKeywords = [
11
- 'Markup',
12
- '@beforesemicolon/markup',
13
- 'reactive HTML',
14
- 'JavaScript templating',
15
- 'template literals',
16
- 'web components',
17
- 'state management',
18
- 'no build step',
19
- 'vanilla JavaScript',
11
+ 'Before Semicolon',
12
+ 'documentation',
13
+ 'JavaScript',
14
+ 'developer tools',
15
+ 'web development',
16
+ 'frontend development',
17
+ 'open source',
20
18
  ]
21
19
 
22
20
  const escapeHTML = (value = '') =>
@@ -38,15 +36,20 @@ const canonicalPath = (path = '/') => {
38
36
  return cleanPath.replace(/\.html$/, '')
39
37
  }
40
38
 
41
- const absoluteUrl = (path = '/') => `${siteUrl}${canonicalPath(path)}`
39
+ const absoluteUrl = (siteUrl, path = '/') => `${siteUrl}${canonicalPath(path)}`
42
40
 
43
- const compactDescription = (description) =>
41
+ const absoluteAssetUrl = (siteUrl, imagePath) => {
42
+ if (/^https?:\/\//i.test(imagePath)) return imagePath
43
+ return `${siteUrl}${imagePath.startsWith('/') ? imagePath : `/${imagePath}`}`
44
+ }
45
+
46
+ const compactDescription = (description, defaultDescription) =>
44
47
  (description || defaultDescription).replace(/\s+/g, ' ').trim()
45
48
 
46
49
  const pageKeywords = ({ title, name, keywords } = {}) => {
47
50
  const values = [
48
51
  ...baseKeywords,
49
- title?.replace(/ - Markup by Before Semicolon$/, ''),
52
+ title?.replace(/ - Before Semicolon Documentation$/, ''),
50
53
  name,
51
54
  ...(Array.isArray(keywords)
52
55
  ? keywords
@@ -64,7 +67,7 @@ const jsonScript = (data) =>
64
67
  .replace(/>/g, '\\u003e')
65
68
  .replace(/&/g, '\\u0026')}</script>`
66
69
 
67
- const breadcrumbJson = ({ title, path }) => {
70
+ const breadcrumbJson = ({ siteUrl, title, path }) => {
68
71
  const cleanPath = canonicalPath(path)
69
72
  const parts = cleanPath.split('/').filter(Boolean)
70
73
  const items = [
@@ -84,7 +87,7 @@ const breadcrumbJson = ({ title, path }) => {
84
87
  position: index + 2,
85
88
  name:
86
89
  index === parts.length - 1
87
- ? title.replace(/ - Markup by Before Semicolon$/, '')
90
+ ? title.replace(/ - Before Semicolon Documentation$/, '')
88
91
  : part
89
92
  .split('-')
90
93
  .map((word) => word[0]?.toUpperCase() + word.slice(1))
@@ -100,9 +103,17 @@ const breadcrumbJson = ({ title, path }) => {
100
103
  }
101
104
  }
102
105
 
103
- const pageJson = ({ title, description, path, name }) => {
106
+ const pageJson = ({
107
+ siteUrl,
108
+ siteName,
109
+ defaultImage,
110
+ title,
111
+ description,
112
+ path,
113
+ name,
114
+ }) => {
104
115
  const isDocsPage = canonicalPath(path).startsWith('/documentation')
105
- const url = absoluteUrl(path)
116
+ const url = absoluteUrl(siteUrl, path)
106
117
 
107
118
  if (isDocsPage) {
108
119
  return {
@@ -116,14 +127,14 @@ const pageJson = ({ title, description, path, name }) => {
116
127
  inLanguage: 'en-US',
117
128
  isPartOf: {
118
129
  '@type': 'TechArticle',
119
- name: 'Markup Documentation',
130
+ name: `${siteName} Documentation`,
120
131
  url: `${siteUrl}/documentation`,
121
132
  },
122
133
  about: [
123
- 'Reactive HTML templating',
124
- 'JavaScript template literals',
125
- 'Markup state and effect APIs',
126
- 'Web Components',
134
+ `${siteName} documentation`,
135
+ 'JavaScript',
136
+ 'Web development',
137
+ 'Developer tools',
127
138
  ],
128
139
  publisher: {
129
140
  '@type': 'Organization',
@@ -141,15 +152,12 @@ const pageJson = ({ title, description, path, name }) => {
141
152
  return {
142
153
  '@context': 'https://schema.org',
143
154
  '@type': 'SoftwareSourceCode',
144
- name: 'Markup',
145
- alternateName: '@beforesemicolon/markup',
155
+ name: siteName,
146
156
  description,
147
157
  url,
148
158
  image: defaultImage,
149
- codeRepository: 'https://github.com/beforesemicolon/markup',
150
159
  programmingLanguage: 'JavaScript',
151
160
  runtimePlatform: 'Browser and Node.js',
152
- license: 'https://github.com/beforesemicolon/markup/blob/main/LICENSE',
153
161
  publisher: {
154
162
  '@type': 'Organization',
155
163
  name: 'Before Semicolon',
@@ -159,9 +167,20 @@ const pageJson = ({ title, description, path, name }) => {
159
167
  }
160
168
 
161
169
  export default (props = {}) => {
170
+ const meta = props.templateConfig?.meta || {}
171
+ const siteUrl = String(
172
+ meta.siteUrl || props.siteUrl || fallbackSiteUrl
173
+ ).replace(/\/$/, '')
174
+ const siteName = meta.siteName || fallbackSiteName
175
+ const defaultTitle = meta.title || fallbackTitle
176
+ const defaultDescription = meta.description || fallbackDescription
177
+ const defaultImage = absoluteAssetUrl(siteUrl, meta.image || fallbackImage)
162
178
  const title = props.title || defaultTitle
163
- const description = compactDescription(props.description)
164
- const url = absoluteUrl(props.path)
179
+ const description = compactDescription(
180
+ props.description,
181
+ defaultDescription
182
+ )
183
+ const url = absoluteUrl(siteUrl, props.path)
165
184
  const canonical = escapeHTML(url)
166
185
  const image = props.image || defaultImage
167
186
  const cacheKey = props.cacheKey ? `?v=${escapeHTML(props.cacheKey)}` : ''
@@ -213,8 +232,8 @@ export default (props = {}) => {
213
232
  <link rel="manifest" href="/assets/favicon/site.webmanifest" />
214
233
  <link rel="icon" type="image/x-icon" href="/assets/favicon/favicon.ico" />
215
234
  <link rel="stylesheet" href="/stylesheets/github-dark.hightlighter.css${cacheKey}">
216
- ${jsonScript(pageJson({ ...props, title, description }))}
217
- ${jsonScript(breadcrumbJson({ title, path: props.path }))}
235
+ ${jsonScript(pageJson({ ...props, siteUrl, siteName, defaultImage, title, description }))}
236
+ ${jsonScript(breadcrumbJson({ siteUrl, title, path: props.path }))}
218
237
  ${
219
238
  isDev
220
239
  ? ''
@@ -201,6 +201,13 @@ body {
201
201
  border-bottom-color: var(--primary-glow);
202
202
  }
203
203
 
204
+ #documentation article blockquote {
205
+ background: var(--surface-muted);
206
+ margin: 0;
207
+ padding: 5px 20px;
208
+ border-radius: 3px;
209
+ }
210
+
204
211
  #documentation article .heading {
205
212
  scroll-margin-top: 6rem;
206
213
  }
@@ -14,6 +14,13 @@ import landingInstall, {
14
14
  import landingCta from './layouts/landing-cta.js'
15
15
 
16
16
  export default {
17
+ meta: {
18
+ siteName: 'Before Semicolon',
19
+ title: 'Before Semicolon Documentation',
20
+ description:
21
+ 'Documentation for Before Semicolon projects, libraries, and developer tools.',
22
+ image: '/assets/site-image.jpg',
23
+ },
17
24
  markdownLayouts: {
18
25
  'landing-hero': landingHero,
19
26
  'landing-ecosystem': landingEcosystem,
@@ -1 +1 @@
1
- "use strict";var i=Object.defineProperty;var s=Object.getOwnPropertyDescriptor;var a=Object.getOwnPropertyNames;var g=Object.prototype.hasOwnProperty;var p=(e,t,o,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of a(t))!g.call(e,r)&&r!==o&&i(e,r,{get:()=>t[r],enumerable:!(n=s(t,r))||n.enumerable});return e};var c=e=>p(i({},"__esModule",{value:!0}),e);var m={};module.exports=c(m);
1
+ "use strict";var i=Object.defineProperty;var o=Object.getOwnPropertyDescriptor;var a=Object.getOwnPropertyNames;var g=Object.prototype.hasOwnProperty;var p=(e,t,s,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of a(t))!g.call(e,r)&&r!==s&&i(e,r,{get:()=>t[r],enumerable:!(n=o(t,r))||n.enumerable});return e};var c=e=>p(i({},"__esModule",{value:!0}),e);var m={};module.exports=c(m);
@@ -1,4 +1,4 @@
1
- var ne=Object.defineProperty;var c=(e,t)=>ne(e,"name",{value:t,configurable:!0});import{Marked as ie}from"marked";import{cp as C,mkdir as v,readdir as le,readFile as j,writeFile as b}from"fs/promises";import o from"path";import ce from"front-matter";import me from"isomorphic-dompurify";import{markedHighlight as ue}from"marked-highlight";import Q from"highlight.js";import pe from"./templates/default.template.js";import fe from"./renderer/index.js";import{createMarkdownLayoutExtension as de}from"./markdown-layout/index.js";import he from"clean-css";import ge from"html-minifier";import*as X from"@putout/minify";import{fileURLToPath as ye,pathToFileURL as A}from"url";import u from"fs";let V,R="";const Y=new he({inline:["none"]}),W=new Map;W.set("default",pe);let _;const Z=c((e,t)=>t===!1?!1:t===void 0?e:e===!1||e===void 0?t:{...e,...t},"mergeThemeMode"),we=c((e,t)=>{if(!(!e&&!t))return{light:Z(e?.light,t?.light),dark:Z(e?.dark,t?.dark)}},"mergeTheme"),ve=c((e,t)=>({...e,...t,markdownLayouts:{...e.markdownLayouts,...t.markdownLayouts},scripts:{...e.scripts,...t.scripts},theme:we(e.theme,t.theme)}),"mergeTemplateConfig"),Se=c((e,t)=>{if(typeof e=="function")return!0;const{match:r}=e;return r?typeof r=="string"?t.includes(r):Array.isArray(r)?r.some(a=>t.includes(a)):r instanceof RegExp?r.test(t):r(t):!0},"scriptMatches"),De=c((e,t)=>e?Object.values(e).filter(r=>r!==!1).filter(r=>Se(r,t)).map(r=>typeof r=="function"?r():r.render()):[],"renderPageScripts"),G=c((e,t)=>{const r=Object.entries(t).filter(([a])=>a.startsWith("--")).map(([a,i])=>` ${a}: ${i};`).join(`
1
+ var ne=Object.defineProperty;var c=(e,t)=>ne(e,"name",{value:t,configurable:!0});import{Marked as ie}from"marked";import{cp as j,mkdir as D,readdir as le,readFile as C,writeFile as x}from"fs/promises";import o from"path";import ce from"front-matter";import me from"isomorphic-dompurify";import{markedHighlight as ue}from"marked-highlight";import Q from"highlight.js";import pe from"./templates/default.template.js";import fe from"./renderer/index.js";import{createMarkdownLayoutExtension as de}from"./markdown-layout/index.js";import he from"clean-css";import ge from"html-minifier";import*as X from"@putout/minify";import{fileURLToPath as ye,pathToFileURL as A}from"url";import u from"fs";let V,R="";const Y=new he({inline:["none"]}),W=new Map;W.set("default",pe);let _;const Z=c((e,t)=>t===!1?!1:t===void 0?e:e===!1||e===void 0?t:{...e,...t},"mergeThemeMode"),we=c((e,t)=>{if(!(!e&&!t))return{light:Z(e?.light,t?.light),dark:Z(e?.dark,t?.dark)}},"mergeTheme"),ve=c((e,t)=>({...e,...t,markdownLayouts:{...e.markdownLayouts,...t.markdownLayouts},scripts:{...e.scripts,...t.scripts},theme:we(e.theme,t.theme)}),"mergeTemplateConfig"),Se=c((e,t)=>{if(typeof e=="function")return!0;const{match:r}=e;return r?typeof r=="string"?t.includes(r):Array.isArray(r)?r.some(a=>t.includes(a)):r instanceof RegExp?r.test(t):r(t):!0},"scriptMatches"),De=c((e,t)=>e?Object.values(e).filter(r=>r!==!1).filter(r=>Se(r,t)).map(r=>typeof r=="function"?r():r.render()):[],"renderPageScripts"),G=c((e,t)=>{const r=Object.entries(t).filter(([a])=>a.startsWith("--")).map(([a,i])=>` ${a}: ${i};`).join(`
2
2
  `);return r?`${e} {
3
3
  ${r}
4
4
  }`:""},"renderThemeBlock"),be=c(e=>{if(!e)return"";const t=e.light===!1,r=e.dark===!1,a=e.light===!1?void 0:e.light,i=r?void 0:e.dark,m=a||i;return m?[G(":root",m),i&&!t?`@media (prefers-color-scheme: dark) {
@@ -7,7 +7,7 @@ ${G(" :root",i)}
7
7
  .site-logo-dark { display: block; }`:"",r&&a?`.site-logo-light { display: block; }
8
8
  .site-logo-dark { display: none; }`:""].filter(Boolean).join(`
9
9
 
10
- `):""},"renderThemeStylesheet"),xe=c((e="")=>String(e).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&apos;"),"escapeXML"),$e=c((e="")=>e.replace(/```[\s\S]*?```/g," ").replace(/`([^`]+)`/g,"$1").replace(/!\[[^\]]*]\([^)]*\)/g," ").replace(/\[([^\]]+)]\([^)]*\)/g,"$1").replace(/^#{1,6}\s+/gm,"").replace(/^---+$/gm," ").replace(/[>*_~]/g,"").replace(/\s+/g," ").trim(),"stripMarkdown"),Pe=c(e=>e==="/index.html"?"/":e.replace(/\/index\.html$/,"/").replace(/\.html$/,"").replace(/\/+/g,"/"),"pagePathToUrlPath"),ee=c((e,t)=>e?`${e.replace(/\/$/,"")}${t==="/"?"/":t}`:t,"canonicalUrl"),ke=c(async(e,t)=>u.existsSync(e)?(await v(o.dirname(t),{recursive:!0}),await C(e,t),!0):!1,"copyIfExists"),Te=c(async e=>{const t=o.resolve(e,"logo.svg");u.existsSync(t)&&await Promise.all(["logo.light.svg","logo.dark.svg"].map(async r=>{const a=o.resolve(e,r);u.existsSync(a)||await C(t,a)}))},"ensureLogoModeAssets"),Me=c(e=>{const t={sitemap:!0,robots:!0,llms:!0,llmsFull:!0,netlify:!1};return e===!1?{sitemap:!1,robots:!1,llms:!1,llmsFull:!1,netlify:!1}:e===!0||e===void 0?t:{...t,...e}},"normalizeGeneratedFilesConfig"),Ce=c((e,t)=>{if(!t)return"";const r=new Date().toISOString().slice(0,10);return`<?xml version="1.0" encoding="UTF-8"?>
10
+ `):""},"renderThemeStylesheet"),xe=c((e="")=>String(e).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&apos;"),"escapeXML"),$e=c((e="")=>e.replace(/```[\s\S]*?```/g," ").replace(/`([^`]+)`/g,"$1").replace(/!\[[^\]]*]\([^)]*\)/g," ").replace(/\[([^\]]+)]\([^)]*\)/g,"$1").replace(/^#{1,6}\s+/gm,"").replace(/^---+$/gm," ").replace(/[>*_~]/g,"").replace(/\s+/g," ").trim(),"stripMarkdown"),Pe=c(e=>e==="/index.html"?"/":e.replace(/\/index\.html$/,"/").replace(/\.html$/,"").replace(/\/+/g,"/"),"pagePathToUrlPath"),ee=c((e,t)=>e?`${e.replace(/\/$/,"")}${t==="/"?"/":t}`:t,"canonicalUrl"),ke=c(async(e,t)=>u.existsSync(e)?(await D(o.dirname(t),{recursive:!0}),await j(e,t),!0):!1,"copyIfExists"),Te=c(async e=>{const t=o.resolve(e,"logo.svg");u.existsSync(t)&&await Promise.all(["logo.light.svg","logo.dark.svg"].map(async r=>{const a=o.resolve(e,r);u.existsSync(a)||await j(t,a)}))},"ensureLogoModeAssets"),Me=c(e=>{const t={sitemap:!0,robots:!0,llms:!0,llmsFull:!0,netlify:!1};return e===!1?{sitemap:!1,robots:!1,llms:!1,llmsFull:!1,netlify:!1}:e===!0||e===void 0?t:{...t,...e}},"normalizeGeneratedFilesConfig"),je=c((e,t)=>{if(!t)return"";const r=new Date().toISOString().slice(0,10);return`<?xml version="1.0" encoding="UTF-8"?>
11
11
  <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
12
12
  ${e.map(i=>{const m=i.urlPath==="/"?"1.0":"0.8";return` <url>
13
13
  <loc>${xe(ee(t,i.urlPath))}</loc>
@@ -17,7 +17,7 @@ ${e.map(i=>{const m=i.urlPath==="/"?"1.0":"0.8";return` <url>
17
17
  </url>`}).join(`
18
18
  `)}
19
19
  </urlset>
20
- `},"generateSitemap"),je=c(e=>`User-agent: *
20
+ `},"generateSitemap"),Ce=c(e=>`User-agent: *
21
21
  Allow: /
22
22
  ${e?`
23
23
  Sitemap: ${e.replace(/\/$/,"")}/sitemap.xml
@@ -75,4 +75,4 @@ for = "/robots.txt"
75
75
  [headers.values]
76
76
  Content-Type = "text/plain; charset=utf-8"
77
77
  Cache-Control = "public, max-age=0, must-revalidate"
78
- `,"generateNetlifyToml"),x=c(async(e,t,r,a)=>{if(await ke(e,t))return;const i=r();if(!i){a&&console.warn(a);return}await v(o.dirname(t),{recursive:!0}),await b(t,i)},"writeGeneratedFile"),Oe=c(async({docsDir:e,docsSiteDir:t,pages:r,siteUrl:a,publicDir:i,generatedFiles:m})=>{m.sitemap&&await x(o.resolve(e,"sitemap.xml"),o.resolve(t,"sitemap.xml"),()=>Ce(r,a),"Skipping generated sitemap.xml because buildDocs({ siteUrl }) is not set."),m.robots&&await x(o.resolve(e,"robots.txt"),o.resolve(t,"robots.txt"),()=>je(a)),m.llms&&await x(o.resolve(e,"llms.txt"),o.resolve(t,"llms.txt"),()=>Fe(r)),m.llmsFull&&await x(o.resolve(e,"llms-full.txt"),o.resolve(t,"llms-full.txt"),()=>Ge(r,a)),m.netlify&&(await x(o.resolve(e,"_redirects"),o.resolve(t,"_redirects"),()=>Le(r,m)),await x(o.resolve(e,"netlify.toml"),o.resolve(t,"netlify.toml"),()=>Ee(i&&o.basename(i))))},"writeGeneratedDocsFiles"),te=c((e={},t,r)=>{const a=new ie(ue({langPrefix:"hljs language-",highlight(i,m){const $=Q.getLanguage(m)?m:"plaintext";return Q.highlight(i,{language:$}).value}}));return a.use({renderer:fe}),a.use(de({marked:a,handlers:e.markdownLayouts,renderMarkdown:r})),e.markedOptions&&a.use(e.markedOptions),t&&a.use(t),a},"createBaseMarked"),Ae=c((e={},t)=>{const r=te(e,t),a=c(m=>r.parse(m),"renderMarkdown"),i=te(e,t,a);return i.use({hooks:{postprocess(m){const{layout:$="default",...g}=_.attributes,P=De(e.scripts,m);m=me.sanitize(m);const k=[...m.matchAll(/<h([0-6])\sid="[^"]+".*?>.*?<a\s+href="([^"]+)".*?>([\s\S]*?)<\/a><\/h\1>/gm)].map(S=>({path:S[2],label:S[3].replace(/<[^>]+>/g,"").replace(/\s+/g," ").trim(),level:S[1]}));return W.get($)?.({...g,content:m,siteMap:_.siteMap,tableOfContent:k,projectMeta:V,renderMarkdown:a,scripts:P,themeStylesheet:e.theme?`/stylesheets/theme.css?v=${R}`:void 0,cacheKey:R})||m}}}),i},"createMarked"),y=c(async e=>{const t=await le(e),r=[];for(const a of t){const i=o.extname(a);/^[._]/.test(a)||(i?r.push(o.join(e,a)):/(stylesheets|assets|scripts)/.test(a)||r.push(...await y(o.join(e,a))))}return r},"traverseDirectory"),lt=c(async({srcDir:e,publicDir:t,markedOptions:r,template:a,siteUrl:i,generatedFiles:m}={})=>{R=Date.now().toString(36);try{const s=o.resolve(process.cwd(),"package.json");u.existsSync(s)&&(V=JSON.parse(await j(s,"utf-8")))}catch(s){console.warn("Failed to load project package.json metadata",s)}const $=c(()=>{try{return __dirname}catch{return o.dirname(ye(import.meta.url))}},"getDirname"),g=a?o.resolve($(),"templates",a):null,P=g?o.resolve(g,"assets"):null,k=g?o.resolve(g,"stylesheets"):null,S=g?o.resolve(g,"layouts"):null;let T={};if(g)try{let s=o.resolve(g,"template.config.js");if(u.existsSync(s)||(s=s.replace(/\.js$/,".ts")),u.existsSync(s)){const n=await import(A(s).href);T=n.default||n}}catch(s){console.error(`Failed to load template config for ${a}`,s)}const f=e??o.resolve(process.cwd(),"docs"),p=t??o.resolve(process.cwd(),"website"),se=Me(m),w=o.resolve(f,"_template"),B=o.resolve(w,"assets"),N=o.resolve(w,"stylesheets"),U=o.resolve(w,"scripts"),z=o.resolve(w,"layouts"),q=o.resolve(f,"_layouts"),F=o.resolve(f,"assets"),H=o.resolve(f,"stylesheets"),L=o.resolve(f,"scripts");try{let s=o.resolve(w,"template.config.js");if(u.existsSync(s)||(s=s.replace(/\.js$/,".ts")),u.existsSync(s)){const n=await import(A(s).href),l=n.default||n;T=ve(T,l)}}catch(s){console.error("Failed to load docs _template config",s)}const re=Ae(T,r);if(await v(p,{recursive:!0}),P&&u.existsSync(P))try{await C(P,F.replace(f,p),{recursive:!0})}catch(s){console.warn(s)}if(u.existsSync(F))try{await C(F,F.replace(f,p),{recursive:!0})}catch(s){console.warn(s)}if(u.existsSync(B))try{await C(B,o.resolve(p,"assets"),{recursive:!0})}catch(s){console.warn(s)}await Te(o.resolve(p,"assets"));try{const s=new Set;if(k&&u.existsSync(k)&&(await y(k)).forEach(l=>s.add(l)),u.existsSync(H)&&(await y(H)).forEach(l=>s.add(l)),u.existsSync(N)&&(await y(N)).forEach(l=>s.add(l)),s.size){await v(o.join(p,"stylesheets"),{recursive:!0});for(const l of s){const d=await j(l,"utf-8"),h=Y.minify(d);h.errors.length&&h.errors.forEach(console.error);const M=l.startsWith(w)?l.replace(w,p):l.startsWith(f)?l.replace(f,p):o.join(p,"stylesheets",o.basename(l));await b(M,h.styles??d)}}const n=be(T.theme);if(n){const l=Y.minify(n);l.errors.length&&l.errors.forEach(console.error),await v(o.join(p,"stylesheets"),{recursive:!0}),await b(o.join(p,"stylesheets","theme.css"),l.styles??n)}}catch(s){console.error(s)}if(u.existsSync(L))try{const s=await y(L);s.length&&await v(L.replace(f,p),{recursive:!0});for(const n of s)try{const l=await j(n,"utf-8");await b(n.replace(f,p),X.minify(l))}catch(l){console.error(l)}}catch(s){console.warn(s)}if(u.existsSync(U))try{const s=await y(U);s.length&&await v(o.join(p,"scripts"),{recursive:!0});for(const n of s)try{const l=await j(n,"utf-8");await b(n.replace(w,p),X.minify(l))}catch(l){console.error(l)}}catch(s){console.warn(s)}try{const s=new Map;S&&u.existsSync(S)&&(await y(S)).forEach(n=>{/\.(j|t)s$/.test(n)&&s.set(o.basename(n).replace(/\.(j|t)s$/,""),n)}),u.existsSync(q)&&(await y(q)).forEach(n=>{/\.(j|t)s$/.test(n)&&s.set(o.basename(n).replace(/\.(j|t)s$/,""),n)}),u.existsSync(z)&&(await y(z)).forEach(n=>{/\.(j|t)s$/.test(n)&&s.set(o.basename(n).replace(/\.(j|t)s$/,""),n)});for(const[n,l]of s.entries()){const d=await import(A(l).href);W.set(n,d.default)}}catch(s){console.error("Template import failed",s)}const oe=await y(f),I=new Map,J=(await Promise.all(oe.filter(s=>s.endsWith(".md")).map(async s=>{const n=await j(s,"utf-8");return{filePath:s,...ce(n)}}))).sort((s,n)=>(s.attributes.order??0)-(n.attributes.order??0)).map(({attributes:s,body:n,filePath:l})=>{const d=l.replace(f,"").replace(/\.md/,".html"),h=l.replace(f,p).replace(".md",".html"),M=h.replace(o.basename(h),""),E=o.basename(d)||"/",ae=d.replace(E,"").replace(/\/$/,"");let D=I;ae.split("/").filter(Boolean).forEach(O=>{D.has(O)||D.set(O,new Map),D=D.get(O)});const K={...s,path:d};return D.has(E)||D.set(E,K),{attributes:K,fileWebsitePath:h,fileDirWebsitePath:M,body:n,sourcePath:o.relative(f,l),siteMap:I}});for(const{attributes:s,body:n,fileDirWebsitePath:l,fileWebsitePath:d,siteMap:h}of J){_={attributes:s,siteMap:h};const M=await re.parse(n);await v(l,{recursive:!0}),await b(d,ge.minify(M,{includeAutoGeneratedTags:!0,removeAttributeQuotes:!0,removeComments:!0,removeRedundantAttributes:!0,removeScriptTypeAttributes:!0,removeStyleLinkTypeAttributes:!0,sortClassName:!0,useShortDoctype:!0,collapseWhitespace:!0,minifyJS:!0,minifyCSS:!0}))}await Oe({docsDir:f,docsSiteDir:p,pages:J.map(({attributes:s,body:n,sourcePath:l})=>{const d=Pe(String(s.path)),h=$e(n);return{sourcePath:l,urlPath:d,title:String(s.title||s.name||"").trim()||"Documentation",name:String(s.name||s.title||"").trim()||"Documentation",description:String(s.description||"").trim()||h.slice(0,156)||"Documentation page.",order:Number(s.order??999),summary:h.slice(0,320)}}),siteUrl:i,publicDir:p,generatedFiles:se})},"buildDocs");export{lt as buildDocs};
78
+ `,"generateNetlifyToml"),$=c(async(e,t,r,a)=>{if(await ke(e,t))return;const i=r();if(!i){a&&console.warn(a);return}await D(o.dirname(t),{recursive:!0}),await x(t,i)},"writeGeneratedFile"),Oe=c(async({docsDir:e,docsSiteDir:t,pages:r,siteUrl:a,publicDir:i,generatedFiles:m})=>{m.sitemap&&await $(o.resolve(e,"sitemap.xml"),o.resolve(t,"sitemap.xml"),()=>je(r,a),"Skipping generated sitemap.xml because buildDocs({ siteUrl }) is not set."),m.robots&&await $(o.resolve(e,"robots.txt"),o.resolve(t,"robots.txt"),()=>Ce(a)),m.llms&&await $(o.resolve(e,"llms.txt"),o.resolve(t,"llms.txt"),()=>Fe(r)),m.llmsFull&&await $(o.resolve(e,"llms-full.txt"),o.resolve(t,"llms-full.txt"),()=>Ge(r,a)),m.netlify&&(await $(o.resolve(e,"_redirects"),o.resolve(t,"_redirects"),()=>Le(r,m)),await $(o.resolve(e,"netlify.toml"),o.resolve(t,"netlify.toml"),()=>Ee(i&&o.basename(i))))},"writeGeneratedDocsFiles"),te=c((e={},t,r)=>{const a=new ie(ue({langPrefix:"hljs language-",highlight(i,m){const d=Q.getLanguage(m)?m:"plaintext";return Q.highlight(i,{language:d}).value}}));return a.use({renderer:fe}),a.use(de({marked:a,handlers:e.markdownLayouts,renderMarkdown:r})),e.markedOptions&&a.use(e.markedOptions),t&&a.use(t),a},"createBaseMarked"),Ae=c((e={},t,r)=>{const a=te(e,t),i=c(d=>a.parse(d),"renderMarkdown"),m=te(e,t,i);return m.use({hooks:{postprocess(d){const{layout:y="default",...P}=_.attributes,k=De(e.scripts,d);d=me.sanitize(d);const T=[...d.matchAll(/<h([0-6])\sid="[^"]+".*?>.*?<a\s+href="([^"]+)".*?>([\s\S]*?)<\/a><\/h\1>/gm)].map(v=>({path:v[2],label:v[3].replace(/<[^>]+>/g,"").replace(/\s+/g," ").trim(),level:v[1]}));return W.get(y)?.({...P,content:d,siteMap:_.siteMap,tableOfContent:T,projectMeta:V,renderMarkdown:i,scripts:k,themeStylesheet:e.theme?`/stylesheets/theme.css?v=${R}`:void 0,cacheKey:R,siteUrl:r,templateConfig:e})||d}}}),m},"createMarked"),w=c(async e=>{const t=await le(e),r=[];for(const a of t){const i=o.extname(a);/^[._]/.test(a)||(i?r.push(o.join(e,a)):/(stylesheets|assets|scripts)/.test(a)||r.push(...await w(o.join(e,a))))}return r},"traverseDirectory"),lt=c(async({srcDir:e,publicDir:t,markedOptions:r,template:a,siteUrl:i,generatedFiles:m}={})=>{R=Date.now().toString(36);try{const s=o.resolve(process.cwd(),"package.json");u.existsSync(s)&&(V=JSON.parse(await C(s,"utf-8")))}catch(s){console.warn("Failed to load project package.json metadata",s)}const d=c(()=>{try{return __dirname}catch{return o.dirname(ye(import.meta.url))}},"getDirname"),y=a?o.resolve(d(),"templates",a):null,P=y?o.resolve(y,"assets"):null,k=y?o.resolve(y,"stylesheets"):null,T=y?o.resolve(y,"layouts"):null;let v={};if(y)try{let s=o.resolve(y,"template.config.js");if(u.existsSync(s)||(s=s.replace(/\.js$/,".ts")),u.existsSync(s)){const n=await import(A(s).href);v=n.default||n}}catch(s){console.error(`Failed to load template config for ${a}`,s)}const f=e??o.resolve(process.cwd(),"docs"),p=t??o.resolve(process.cwd(),"website"),se=Me(m),S=o.resolve(f,"_template"),B=o.resolve(S,"assets"),N=o.resolve(S,"stylesheets"),U=o.resolve(S,"scripts"),z=o.resolve(S,"layouts"),q=o.resolve(f,"_layouts"),F=o.resolve(f,"assets"),H=o.resolve(f,"stylesheets"),L=o.resolve(f,"scripts");try{let s=o.resolve(S,"template.config.js");if(u.existsSync(s)||(s=s.replace(/\.js$/,".ts")),u.existsSync(s)){const n=await import(A(s).href),l=n.default||n;v=ve(v,l)}}catch(s){console.error("Failed to load docs _template config",s)}const re=Ae(v,r,i);if(await D(p,{recursive:!0}),P&&u.existsSync(P))try{await j(P,F.replace(f,p),{recursive:!0})}catch(s){console.warn(s)}if(u.existsSync(F))try{await j(F,F.replace(f,p),{recursive:!0})}catch(s){console.warn(s)}if(u.existsSync(B))try{await j(B,o.resolve(p,"assets"),{recursive:!0})}catch(s){console.warn(s)}await Te(o.resolve(p,"assets"));try{const s=new Set;if(k&&u.existsSync(k)&&(await w(k)).forEach(l=>s.add(l)),u.existsSync(H)&&(await w(H)).forEach(l=>s.add(l)),u.existsSync(N)&&(await w(N)).forEach(l=>s.add(l)),s.size){await D(o.join(p,"stylesheets"),{recursive:!0});for(const l of s){const h=await C(l,"utf-8"),g=Y.minify(h);g.errors.length&&g.errors.forEach(console.error);const M=l.startsWith(S)?l.replace(S,p):l.startsWith(f)?l.replace(f,p):o.join(p,"stylesheets",o.basename(l));await x(M,g.styles??h)}}const n=be(v.theme);if(n){const l=Y.minify(n);l.errors.length&&l.errors.forEach(console.error),await D(o.join(p,"stylesheets"),{recursive:!0}),await x(o.join(p,"stylesheets","theme.css"),l.styles??n)}}catch(s){console.error(s)}if(u.existsSync(L))try{const s=await w(L);s.length&&await D(L.replace(f,p),{recursive:!0});for(const n of s)try{const l=await C(n,"utf-8");await x(n.replace(f,p),X.minify(l))}catch(l){console.error(l)}}catch(s){console.warn(s)}if(u.existsSync(U))try{const s=await w(U);s.length&&await D(o.join(p,"scripts"),{recursive:!0});for(const n of s)try{const l=await C(n,"utf-8");await x(n.replace(S,p),X.minify(l))}catch(l){console.error(l)}}catch(s){console.warn(s)}try{const s=new Map;T&&u.existsSync(T)&&(await w(T)).forEach(n=>{/\.(j|t)s$/.test(n)&&s.set(o.basename(n).replace(/\.(j|t)s$/,""),n)}),u.existsSync(q)&&(await w(q)).forEach(n=>{/\.(j|t)s$/.test(n)&&s.set(o.basename(n).replace(/\.(j|t)s$/,""),n)}),u.existsSync(z)&&(await w(z)).forEach(n=>{/\.(j|t)s$/.test(n)&&s.set(o.basename(n).replace(/\.(j|t)s$/,""),n)});for(const[n,l]of s.entries()){const h=await import(A(l).href);W.set(n,h.default)}}catch(s){console.error("Template import failed",s)}const oe=await w(f),I=new Map,J=(await Promise.all(oe.filter(s=>s.endsWith(".md")).map(async s=>{const n=await C(s,"utf-8");return{filePath:s,...ce(n)}}))).sort((s,n)=>(s.attributes.order??0)-(n.attributes.order??0)).map(({attributes:s,body:n,filePath:l})=>{const h=l.replace(f,"").replace(/\.md/,".html"),g=l.replace(f,p).replace(".md",".html"),M=g.replace(o.basename(g),""),E=o.basename(h)||"/",ae=h.replace(E,"").replace(/\/$/,"");let b=I;ae.split("/").filter(Boolean).forEach(O=>{b.has(O)||b.set(O,new Map),b=b.get(O)});const K={...s,path:h};return b.has(E)||b.set(E,K),{attributes:K,fileWebsitePath:g,fileDirWebsitePath:M,body:n,sourcePath:o.relative(f,l),siteMap:I}});for(const{attributes:s,body:n,fileDirWebsitePath:l,fileWebsitePath:h,siteMap:g}of J){_={attributes:s,siteMap:g};const M=await re.parse(n);await D(l,{recursive:!0}),await x(h,ge.minify(M,{includeAutoGeneratedTags:!0,removeAttributeQuotes:!0,removeComments:!0,removeRedundantAttributes:!0,removeScriptTypeAttributes:!0,removeStyleLinkTypeAttributes:!0,sortClassName:!0,useShortDoctype:!0,collapseWhitespace:!0,minifyJS:!0,minifyCSS:!0}))}await Oe({docsDir:f,docsSiteDir:p,pages:J.map(({attributes:s,body:n,sourcePath:l})=>{const h=Pe(String(s.path)),g=$e(n);return{sourcePath:l,urlPath:h,title:String(s.title||s.name||"").trim()||"Documentation",name:String(s.name||s.title||"").trim()||"Documentation",description:String(s.description||"").trim()||g.slice(0,156)||"Documentation page.",order:Number(s.order??999),summary:g.slice(0,320)}}),siteUrl:i,publicDir:p,generatedFiles:se})},"buildDocs");export{lt as buildDocs};
@@ -144,9 +144,28 @@ It emits:
144
144
 
145
145
  Important current behavior:
146
146
 
147
- - The metadata helper is still Markup-specific.
148
- - It hardcodes the production site URL and some default titles/assets.
149
- - Override `_head-meta.js` in `docs/_template/layouts` for any non-Markup documentation site.
147
+ - The helper reads defaults from `template.config.js` `meta`.
148
+ - `meta.siteUrl` overrides the `buildDocs({ siteUrl })` value when needed.
149
+ - `meta.siteName`, `meta.title`, and `meta.description` customize the fallback site identity.
150
+ - `meta.image` customizes the default Open Graph and Twitter card image.
151
+ - `meta.image` can be absolute, such as `https://example.com/card.jpg`, or site-relative, such as `/assets/site-image.jpg`.
152
+ - If no image is configured, the fallback is `/assets/site-image.jpg`.
153
+
154
+ For project-specific social images, add the file under `docs/_template/assets` and point `meta.image` at it:
155
+
156
+ ```js
157
+ // docs/_template/template.config.js
158
+ export default {
159
+ meta: {
160
+ siteName: 'Example',
161
+ title: 'Example by Before Semicolon',
162
+ description: 'Documentation for Example.',
163
+ image: '/assets/site-image.jpg',
164
+ },
165
+ }
166
+ ```
167
+
168
+ Override `_head-meta.js` in `docs/_template/layouts` only when the generated metadata shape itself needs to change.
150
169
 
151
170
  ## Markdown Layout Handlers
152
171
 
@@ -590,7 +609,7 @@ docs/_template/assets/logo.svg
590
609
  docs/_template/assets/logo.light.svg
591
610
  docs/_template/assets/logo.dark.svg
592
611
  docs/_template/assets/favicon/favicon.ico
593
- docs/_template/assets/markup-banner.jpg
612
+ docs/_template/assets/site-image.jpg
594
613
  ```
595
614
 
596
615
  Because `docs/_template/assets` is copied into `publicDir/assets` after template assets, source assets can replace template assets. If only `logo.svg` is present, builder copies it to missing `logo.light.svg` and `logo.dark.svg` files in the generated site so the header and footer do not request missing assets. Projects that need correct contrast should provide explicit light and dark variants.
@@ -668,11 +687,12 @@ export default {
668
687
  Use the smallest override that solves the problem:
669
688
 
670
689
  1. Override assets for logos, favicons, images, and icons.
671
- 2. Override theme variables for color and surface tuning.
672
- 3. Add Markdown layout handlers for new page sections.
673
- 4. Override existing Markdown layout handlers only when the section HTML needs to change.
674
- 5. Override `_header.js`, `_footer.js`, or `_head-meta.js` when navigation, footer copy, metadata, or brand assumptions need to change.
675
- 6. Override `landing.js` or `document.js` only when the full page shell changes.
690
+ 2. Override `meta` fields for site identity and social card defaults.
691
+ 3. Override theme variables for color and surface tuning.
692
+ 4. Add Markdown layout handlers for new page sections.
693
+ 5. Override existing Markdown layout handlers only when the section HTML needs to change.
694
+ 6. Override `_header.js`, `_footer.js`, or `_head-meta.js` when navigation, footer copy, metadata shape, or brand assumptions need to change.
695
+ 7. Override `landing.js` or `document.js` only when the full page shell changes.
676
696
 
677
697
  Example docs extension:
678
698
 
@@ -692,6 +712,12 @@ docs/
692
712
  import pricingCards from './layouts/pricing-cards.js'
693
713
 
694
714
  export default {
715
+ meta: {
716
+ siteName: 'Example',
717
+ title: 'Example by Before Semicolon',
718
+ description: 'Documentation for Example.',
719
+ image: '/assets/site-image.jpg',
720
+ },
695
721
  markdownLayouts: {
696
722
  'pricing-cards': pricingCards,
697
723
  },
@@ -710,7 +736,6 @@ export default {
710
736
 
711
737
  Some template internals are still Markup or Before Semicolon oriented:
712
738
 
713
- - `_head-meta.js` has Markup-specific defaults and URLs.
714
739
  - `_header.js` has Markup-oriented GitHub and documentation links.
715
740
  - `_footer.js` has Before Semicolon social links, footer copy, and learning links.
716
741
  - `document.js` builds edit links against the Markup GitHub docs path.
@@ -1,22 +1,20 @@
1
1
  /* global process */
2
2
  const isDev = process.env.NODE_ENV === 'development'
3
3
 
4
- const siteUrl = 'https://markup.beforesemicolon.com'
5
- const siteName = 'Markup'
6
- const defaultTitle = 'Markup by Before Semicolon'
7
- const defaultDescription =
8
- 'Markup is a tiny reactive HTML templating system for building JavaScript user interfaces with web standards, no JSX, and no build step.'
9
- const defaultImage = `${siteUrl}/assets/markup-banner.jpg`
4
+ const fallbackSiteUrl = 'https://beforesemicolon.com'
5
+ const fallbackSiteName = 'Before Semicolon'
6
+ const fallbackTitle = 'Before Semicolon Documentation'
7
+ const fallbackDescription =
8
+ 'Documentation for Before Semicolon projects, libraries, and developer tools.'
9
+ const fallbackImage = '/assets/site-image.jpg'
10
10
  const baseKeywords = [
11
- 'Markup',
12
- '@beforesemicolon/markup',
13
- 'reactive HTML',
14
- 'JavaScript templating',
15
- 'template literals',
16
- 'web components',
17
- 'state management',
18
- 'no build step',
19
- 'vanilla JavaScript',
11
+ 'Before Semicolon',
12
+ 'documentation',
13
+ 'JavaScript',
14
+ 'developer tools',
15
+ 'web development',
16
+ 'frontend development',
17
+ 'open source',
20
18
  ]
21
19
 
22
20
  const escapeHTML = (value = '') =>
@@ -38,15 +36,20 @@ const canonicalPath = (path = '/') => {
38
36
  return cleanPath.replace(/\.html$/, '')
39
37
  }
40
38
 
41
- const absoluteUrl = (path = '/') => `${siteUrl}${canonicalPath(path)}`
39
+ const absoluteUrl = (siteUrl, path = '/') => `${siteUrl}${canonicalPath(path)}`
42
40
 
43
- const compactDescription = (description) =>
41
+ const absoluteAssetUrl = (siteUrl, imagePath) => {
42
+ if (/^https?:\/\//i.test(imagePath)) return imagePath
43
+ return `${siteUrl}${imagePath.startsWith('/') ? imagePath : `/${imagePath}`}`
44
+ }
45
+
46
+ const compactDescription = (description, defaultDescription) =>
44
47
  (description || defaultDescription).replace(/\s+/g, ' ').trim()
45
48
 
46
49
  const pageKeywords = ({ title, name, keywords } = {}) => {
47
50
  const values = [
48
51
  ...baseKeywords,
49
- title?.replace(/ - Markup by Before Semicolon$/, ''),
52
+ title?.replace(/ - Before Semicolon Documentation$/, ''),
50
53
  name,
51
54
  ...(Array.isArray(keywords)
52
55
  ? keywords
@@ -64,7 +67,7 @@ const jsonScript = (data) =>
64
67
  .replace(/>/g, '\\u003e')
65
68
  .replace(/&/g, '\\u0026')}</script>`
66
69
 
67
- const breadcrumbJson = ({ title, path }) => {
70
+ const breadcrumbJson = ({ siteUrl, title, path }) => {
68
71
  const cleanPath = canonicalPath(path)
69
72
  const parts = cleanPath.split('/').filter(Boolean)
70
73
  const items = [
@@ -84,7 +87,7 @@ const breadcrumbJson = ({ title, path }) => {
84
87
  position: index + 2,
85
88
  name:
86
89
  index === parts.length - 1
87
- ? title.replace(/ - Markup by Before Semicolon$/, '')
90
+ ? title.replace(/ - Before Semicolon Documentation$/, '')
88
91
  : part
89
92
  .split('-')
90
93
  .map((word) => word[0]?.toUpperCase() + word.slice(1))
@@ -100,9 +103,17 @@ const breadcrumbJson = ({ title, path }) => {
100
103
  }
101
104
  }
102
105
 
103
- const pageJson = ({ title, description, path, name }) => {
106
+ const pageJson = ({
107
+ siteUrl,
108
+ siteName,
109
+ defaultImage,
110
+ title,
111
+ description,
112
+ path,
113
+ name,
114
+ }) => {
104
115
  const isDocsPage = canonicalPath(path).startsWith('/documentation')
105
- const url = absoluteUrl(path)
116
+ const url = absoluteUrl(siteUrl, path)
106
117
 
107
118
  if (isDocsPage) {
108
119
  return {
@@ -116,14 +127,14 @@ const pageJson = ({ title, description, path, name }) => {
116
127
  inLanguage: 'en-US',
117
128
  isPartOf: {
118
129
  '@type': 'TechArticle',
119
- name: 'Markup Documentation',
130
+ name: `${siteName} Documentation`,
120
131
  url: `${siteUrl}/documentation`,
121
132
  },
122
133
  about: [
123
- 'Reactive HTML templating',
124
- 'JavaScript template literals',
125
- 'Markup state and effect APIs',
126
- 'Web Components',
134
+ `${siteName} documentation`,
135
+ 'JavaScript',
136
+ 'Web development',
137
+ 'Developer tools',
127
138
  ],
128
139
  publisher: {
129
140
  '@type': 'Organization',
@@ -141,15 +152,12 @@ const pageJson = ({ title, description, path, name }) => {
141
152
  return {
142
153
  '@context': 'https://schema.org',
143
154
  '@type': 'SoftwareSourceCode',
144
- name: 'Markup',
145
- alternateName: '@beforesemicolon/markup',
155
+ name: siteName,
146
156
  description,
147
157
  url,
148
158
  image: defaultImage,
149
- codeRepository: 'https://github.com/beforesemicolon/markup',
150
159
  programmingLanguage: 'JavaScript',
151
160
  runtimePlatform: 'Browser and Node.js',
152
- license: 'https://github.com/beforesemicolon/markup/blob/main/LICENSE',
153
161
  publisher: {
154
162
  '@type': 'Organization',
155
163
  name: 'Before Semicolon',
@@ -159,9 +167,20 @@ const pageJson = ({ title, description, path, name }) => {
159
167
  }
160
168
 
161
169
  export default (props = {}) => {
170
+ const meta = props.templateConfig?.meta || {}
171
+ const siteUrl = String(
172
+ meta.siteUrl || props.siteUrl || fallbackSiteUrl
173
+ ).replace(/\/$/, '')
174
+ const siteName = meta.siteName || fallbackSiteName
175
+ const defaultTitle = meta.title || fallbackTitle
176
+ const defaultDescription = meta.description || fallbackDescription
177
+ const defaultImage = absoluteAssetUrl(siteUrl, meta.image || fallbackImage)
162
178
  const title = props.title || defaultTitle
163
- const description = compactDescription(props.description)
164
- const url = absoluteUrl(props.path)
179
+ const description = compactDescription(
180
+ props.description,
181
+ defaultDescription
182
+ )
183
+ const url = absoluteUrl(siteUrl, props.path)
165
184
  const canonical = escapeHTML(url)
166
185
  const image = props.image || defaultImage
167
186
  const cacheKey = props.cacheKey ? `?v=${escapeHTML(props.cacheKey)}` : ''
@@ -213,8 +232,8 @@ export default (props = {}) => {
213
232
  <link rel="manifest" href="/assets/favicon/site.webmanifest" />
214
233
  <link rel="icon" type="image/x-icon" href="/assets/favicon/favicon.ico" />
215
234
  <link rel="stylesheet" href="/stylesheets/github-dark.hightlighter.css${cacheKey}">
216
- ${jsonScript(pageJson({ ...props, title, description }))}
217
- ${jsonScript(breadcrumbJson({ title, path: props.path }))}
235
+ ${jsonScript(pageJson({ ...props, siteUrl, siteName, defaultImage, title, description }))}
236
+ ${jsonScript(breadcrumbJson({ siteUrl, title, path: props.path }))}
218
237
  ${
219
238
  isDev
220
239
  ? ''
@@ -201,6 +201,13 @@ body {
201
201
  border-bottom-color: var(--primary-glow);
202
202
  }
203
203
 
204
+ #documentation article blockquote {
205
+ background: var(--surface-muted);
206
+ margin: 0;
207
+ padding: 5px 20px;
208
+ border-radius: 3px;
209
+ }
210
+
204
211
  #documentation article .heading {
205
212
  scroll-margin-top: 6rem;
206
213
  }
@@ -14,6 +14,13 @@ import landingInstall, {
14
14
  import landingCta from './layouts/landing-cta.js'
15
15
 
16
16
  export default {
17
+ meta: {
18
+ siteName: 'Before Semicolon',
19
+ title: 'Before Semicolon Documentation',
20
+ description:
21
+ 'Documentation for Before Semicolon projects, libraries, and developer tools.',
22
+ image: '/assets/site-image.jpg',
23
+ },
17
24
  markdownLayouts: {
18
25
  'landing-hero': landingHero,
19
26
  'landing-ecosystem': landingEcosystem,
@@ -6,6 +6,13 @@ export interface DocsTheme {
6
6
  light?: DocsThemeModeConfig;
7
7
  dark?: DocsThemeModeConfig;
8
8
  }
9
+ export interface DocsMetaConfig {
10
+ siteUrl?: string;
11
+ siteName?: string;
12
+ title?: string;
13
+ description?: string;
14
+ image?: string;
15
+ }
9
16
  export type DocsScriptMatcher = string | string[] | RegExp | ((html: string) => boolean);
10
17
  export interface DocsScriptDefinition {
11
18
  match?: DocsScriptMatcher;
@@ -34,6 +41,8 @@ export interface PageProps {
34
41
  scripts?: string[];
35
42
  themeStylesheet?: string;
36
43
  cacheKey?: string;
44
+ siteUrl?: string;
45
+ templateConfig?: DocsTemplateConfig;
37
46
  }
38
47
  export interface CustomOptions {
39
48
  name?: string;
@@ -60,4 +69,5 @@ export interface DocsTemplateConfig {
60
69
  markdownLayouts?: Record<string, MarkdownLayoutHandler>;
61
70
  scripts?: DocsScriptRegistry;
62
71
  theme?: DocsTheme;
72
+ meta?: DocsMetaConfig;
63
73
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@beforesemicolon/builder",
3
- "version": "1.8.20",
3
+ "version": "1.8.22",
4
4
  "description": "Utilities to build npm packages and documentation website",
5
5
  "engines": {
6
6
  "node": ">=18.16.0"