@beforesemicolon/builder 1.8.20 → 1.8.23

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,8 +303,10 @@ A template config exports an object:
303
303
 
304
304
  ```js
305
305
  export default {
306
+ meta: {},
306
307
  markedOptions: {},
307
308
  markdownLayouts: {},
309
+ headScripts: {},
308
310
  scripts: {},
309
311
  theme: {
310
312
  light: {},
@@ -318,7 +320,8 @@ Config from `docs/_template/template.config.js` is merged into the selected temp
318
320
  Merge behavior:
319
321
 
320
322
  - `markdownLayouts` are shallow-merged by layout name.
321
- - `scripts` are shallow-merged by script name.
323
+ - `headScripts` and `scripts` are shallow-merged by script name.
324
+ - `meta` is shallow-merged by metadata field.
322
325
  - `theme.light` and `theme.dark` are shallow-merged by CSS variable name.
323
326
  - Other top-level config values use the docs source config value when provided.
324
327
 
@@ -328,6 +331,12 @@ Example docs source extension:
328
331
  import pricingCards from './layouts/pricing-cards.js'
329
332
 
330
333
  export default {
334
+ meta: {
335
+ siteName: 'Example',
336
+ title: 'Example Docs',
337
+ description: 'Documentation for Example.',
338
+ image: '/assets/site-image.jpg',
339
+ },
331
340
  markdownLayouts: {
332
341
  'pricing-cards': pricingCards,
333
342
  },
@@ -594,6 +603,8 @@ export default {
594
603
  }
595
604
  ```
596
605
 
606
+ Use `headScripts` for scripts that must render in `<head>`, such as analytics bootstrap tags. Use `scripts` for scripts that can render near `</body>`, such as interaction handlers.
607
+
597
608
  Script definitions:
598
609
 
599
610
  ```ts
@@ -624,12 +635,15 @@ Behavior:
624
635
  - A function matcher receives the rendered page HTML and returns a boolean.
625
636
  - A script can be disabled by setting its registry value to `false` in an extending config.
626
637
 
627
- Layouts receive scripts through `props.scripts` and must insert them where appropriate, usually before `</body>`.
638
+ Layouts receive scripts through `props.headScripts` and `props.scripts`. Template layouts must insert them where appropriate.
628
639
 
629
640
  ```js
630
641
  export default (props) => `
631
642
  <!doctype html>
632
643
  <html>
644
+ <head>
645
+ ${props.headScripts?.join('') || ''}
646
+ </head>
633
647
  <body>
634
648
  ${props.content}
635
649
  ${props.scripts?.join('') || ''}
@@ -794,8 +808,12 @@ export default {
794
808
  markdownLayouts: {
795
809
  'pricing-cards': pricingCards,
796
810
  },
811
+ headScripts: {
812
+ analytics: () =>
813
+ `<script async src="https://example.com/analytics.js"></script>`,
814
+ },
797
815
  scripts: {
798
- analytics: {
816
+ pageView: {
799
817
  match: '<main',
800
818
  render: () => `<script>console.log('page viewed')</script>`,
801
819
  },
@@ -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 ge=Object.create;var C=Object.defineProperty;var ye=Object.getOwnPropertyDescriptor;var we=Object.getOwnPropertyNames;var ve=Object.getPrototypeOf,Se=Object.prototype.hasOwnProperty;var m=(e,t)=>C(e,"name",{value:t,configurable:!0});var De=(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 we(t))!Se.call(e,n)&&n!==r&&C(e,n,{get:()=>t[n],enumerable:!(a=ye(t,n))||a.enumerable});return e};var v=(e,t,r)=>(r=e!=null?ge(ve(e)):{},Q(t||!e||!e.__esModule?C(r,"default",{value:e,enumerable:!0}):r,e)),be=e=>Q(C({},"__esModule",{value:!0}),e);var Ue={};De(Ue,{buildDocs:()=>Ne});module.exports=be(Ue);const ze={};var re=require("marked"),c=require("fs/promises"),o=v(require("path"),1),oe=v(require("front-matter"),1),ae=v(require("isomorphic-dompurify"),1),ne=require("marked-highlight"),_=v(require("highlight.js"),1),ie=v(require("./templates/default.template.js"),1),le=v(require("./renderer/index.js"),1),ce=require("./markdown-layout/index.js"),me=v(require("clean-css"),1),ue=v(require("html-minifier"),1),X=v(require("@putout/minify"),1),P=require("url"),f=v(require("fs"),1);let V,A="";const Y=new me.default({inline:["none"]}),R=new Map;R.set("default",ie.default);let W;const Z=m((e,t)=>t===!1?!1:t===void 0?e:e===!1||e===void 0?t:{...e,...t},"mergeThemeMode"),xe=m((e,t)=>{if(!(!e&&!t))return{light:Z(e?.light,t?.light),dark:Z(e?.dark,t?.dark)}},"mergeTheme"),$e=m((e,t)=>({...e,...t,markdownLayouts:{...e.markdownLayouts,...t.markdownLayouts},headScripts:{...e.headScripts,...t.headScripts},scripts:{...e.scripts,...t.scripts},theme:xe(e.theme,t.theme),meta:{...e.meta,...t.meta}}),"mergeTemplateConfig"),Pe=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"),ee=m((e,t)=>e?Object.values(e).filter(r=>r!==!1).filter(r=>Pe(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,10 +7,10 @@ ${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"),te=m((e,t)=>e?`${e.replace(/\/$/,"")}${t==="/"?"/":t}`:t,"canonicalUrl"),Ce=m(async(e,t)=>f.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");f.default.existsSync(t)&&await Promise.all(["logo.light.svg","logo.dark.svg"].map(async r=>{const a=o.default.resolve(e,r);f.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
- <loc>${Te(ee(t,n.urlPath))}</loc>
13
+ <loc>${Te(te(t,n.urlPath))}</loc>
14
14
  <lastmod>${r}</lastmod>
15
15
  <changefreq>weekly</changefreq>
16
16
  <priority>${u}</priority>
@@ -35,7 +35,7 @@ This file is generated at build time from the Markdown documentation. Use it to
35
35
 
36
36
  ${e.map(r=>`## ${r.title}
37
37
 
38
- URL: ${ee(t,r.urlPath)}
38
+ URL: ${te(t,r.urlPath)}
39
39
  Source: docs/${r.sourcePath}
40
40
  Description: ${r.description}
41
41
 
@@ -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"),se=m((e={},t,r)=>{const a=new re.Marked((0,ne.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:le.default}),a.use((0,ce.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=se(e,t),n=m(h=>a.parse(h),"renderMarkdown"),u=se(e,t,n);return u.use({hooks:{postprocess(h){const{layout:w="default",...k}=W.attributes,T=ee(e.headScripts,h),M=ee(e.scripts,h);h=ae.default.sanitize(h);const b=[...h.matchAll(/<h([0-6])\sid="[^"]+".*?>.*?<a\s+href="([^"]+)".*?>([\s\S]*?)<\/a><\/h\1>/gm)].map(p=>({path:p[2],label:p[3].replace(/<[^>]+>/g,"").replace(/\s+/g," ").trim(),level:p[1]}));return R.get(w)?.({...k,content:h,siteMap:W.siteMap,tableOfContent:b,projectMeta:V,renderMarkdown:n,headScripts:T,scripts:M,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");f.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 b={};if(w)try{let s=o.default.resolve(w,"template.config.js");if(f.default.existsSync(s)||(s=s.replace(/\.js$/,".ts")),f.default.existsSync(s)){const i=await import((0,P.pathToFileURL)(s).href);b=i.default||i}}catch(s){console.error(`Failed to load template config for ${a}`,s)}const p=e??o.default.resolve(process.cwd(),"docs"),d=t??o.default.resolve(process.cwd(),"website"),pe=Ge(u),D=o.default.resolve(p,"_template"),B=o.default.resolve(D,"assets"),N=o.default.resolve(D,"stylesheets"),U=o.default.resolve(D,"scripts"),z=o.default.resolve(D,"layouts"),q=o.default.resolve(p,"_layouts"),F=o.default.resolve(p,"assets"),H=o.default.resolve(p,"stylesheets"),L=o.default.resolve(p,"scripts");try{let s=o.default.resolve(D,"template.config.js");if(f.default.existsSync(s)||(s=s.replace(/\.js$/,".ts")),f.default.existsSync(s)){const i=await import((0,P.pathToFileURL)(s).href),l=i.default||i;b=$e(b,l)}}catch(s){console.error("Failed to load docs _template config",s)}const fe=Be(b,r,n);if(await(0,c.mkdir)(d,{recursive:!0}),k&&f.default.existsSync(k))try{await(0,c.cp)(k,F.replace(p,d),{recursive:!0})}catch(s){console.warn(s)}if(f.default.existsSync(F))try{await(0,c.cp)(F,F.replace(p,d),{recursive:!0})}catch(s){console.warn(s)}if(f.default.existsSync(B))try{await(0,c.cp)(B,o.default.resolve(d,"assets"),{recursive:!0})}catch(s){console.warn(s)}await Fe(o.default.resolve(d,"assets"));try{const s=new Set;if(T&&f.default.existsSync(T)&&(await S(T)).forEach(l=>s.add(l)),f.default.existsSync(H)&&(await S(H)).forEach(l=>s.add(l)),f.default.existsSync(N)&&(await S(N)).forEach(l=>s.add(l)),s.size){await(0,c.mkdir)(o.default.join(d,"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(D)?l.replace(D,d):l.startsWith(p)?l.replace(p,d):o.default.join(d,"stylesheets",o.default.basename(l));await(0,c.writeFile)(j,y.styles??g)}}const i=ke(b.theme);if(i){const l=Y.minify(i);l.errors.length&&l.errors.forEach(console.error),await(0,c.mkdir)(o.default.join(d,"stylesheets"),{recursive:!0}),await(0,c.writeFile)(o.default.join(d,"stylesheets","theme.css"),l.styles??i)}}catch(s){console.error(s)}if(f.default.existsSync(L))try{const s=await S(L);s.length&&await(0,c.mkdir)(L.replace(p,d),{recursive:!0});for(const i of s)try{const l=await(0,c.readFile)(i,"utf-8");await(0,c.writeFile)(i.replace(p,d),X.minify(l))}catch(l){console.error(l)}}catch(s){console.warn(s)}if(f.default.existsSync(U))try{const s=await S(U);s.length&&await(0,c.mkdir)(o.default.join(d,"scripts"),{recursive:!0});for(const i of s)try{const l=await(0,c.readFile)(i,"utf-8");await(0,c.writeFile)(i.replace(D,d),X.minify(l))}catch(l){console.error(l)}}catch(s){console.warn(s)}try{const s=new Map;M&&f.default.existsSync(M)&&(await S(M)).forEach(i=>{/\.(j|t)s$/.test(i)&&s.set(o.default.basename(i).replace(/\.(j|t)s$/,""),i)}),f.default.existsSync(q)&&(await S(q)).forEach(i=>{/\.(j|t)s$/.test(i)&&s.set(o.default.basename(i).replace(/\.(j|t)s$/,""),i)}),f.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 de=await S(p),I=new Map,J=(await Promise.all(de.filter(s=>s.endsWith(".md")).map(async s=>{const i=await(0,c.readFile)(s,"utf-8");return{filePath:s,...(0,oe.default)(i)}}))).sort((s,i)=>(s.attributes.order??0)-(i.attributes.order??0)).map(({attributes:s,body:i,filePath:l})=>{const g=l.replace(p,"").replace(/\.md/,".html"),y=l.replace(p,d).replace(".md",".html"),j=y.replace(o.default.basename(y),""),E=o.default.basename(g)||"/",he=g.replace(E,"").replace(/\/$/,"");let x=I;he.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(p,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 fe.parse(i);await(0,c.mkdir)(l,{recursive:!0}),await(0,c.writeFile)(g,ue.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:p,docsSiteDir:d,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:d,generatedFiles:pe})},"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.
@@ -618,7 +637,7 @@ The landing and documentation stylesheets import or rely on the shared styles as
618
637
 
619
638
  ## Scripts
620
639
 
621
- Template scripts are registered by marker:
640
+ Template scripts are registered by marker. The template renders `headScripts` inside `<head>` and `scripts` before `</body>`.
622
641
 
623
642
  ```js
624
643
  scripts: {
@@ -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,17 @@
1
- /* global process */
2
- const isDev = process.env.NODE_ENV === 'development'
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`
1
+ const fallbackSiteUrl = 'https://beforesemicolon.com'
2
+ const fallbackSiteName = 'Before Semicolon'
3
+ const fallbackTitle = 'Before Semicolon Documentation'
4
+ const fallbackDescription =
5
+ 'Documentation for Before Semicolon projects, libraries, and developer tools.'
6
+ const fallbackImage = '/assets/site-image.jpg'
10
7
  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',
8
+ 'Before Semicolon',
9
+ 'documentation',
10
+ 'JavaScript',
11
+ 'developer tools',
12
+ 'web development',
13
+ 'frontend development',
14
+ 'open source',
20
15
  ]
21
16
 
22
17
  const escapeHTML = (value = '') =>
@@ -38,15 +33,20 @@ const canonicalPath = (path = '/') => {
38
33
  return cleanPath.replace(/\.html$/, '')
39
34
  }
40
35
 
41
- const absoluteUrl = (path = '/') => `${siteUrl}${canonicalPath(path)}`
36
+ const absoluteUrl = (siteUrl, path = '/') => `${siteUrl}${canonicalPath(path)}`
42
37
 
43
- const compactDescription = (description) =>
38
+ const absoluteAssetUrl = (siteUrl, imagePath) => {
39
+ if (/^https?:\/\//i.test(imagePath)) return imagePath
40
+ return `${siteUrl}${imagePath.startsWith('/') ? imagePath : `/${imagePath}`}`
41
+ }
42
+
43
+ const compactDescription = (description, defaultDescription) =>
44
44
  (description || defaultDescription).replace(/\s+/g, ' ').trim()
45
45
 
46
46
  const pageKeywords = ({ title, name, keywords } = {}) => {
47
47
  const values = [
48
48
  ...baseKeywords,
49
- title?.replace(/ - Markup by Before Semicolon$/, ''),
49
+ title?.replace(/ - Before Semicolon Documentation$/, ''),
50
50
  name,
51
51
  ...(Array.isArray(keywords)
52
52
  ? keywords
@@ -64,7 +64,7 @@ const jsonScript = (data) =>
64
64
  .replace(/>/g, '\\u003e')
65
65
  .replace(/&/g, '\\u0026')}</script>`
66
66
 
67
- const breadcrumbJson = ({ title, path }) => {
67
+ const breadcrumbJson = ({ siteUrl, title, path }) => {
68
68
  const cleanPath = canonicalPath(path)
69
69
  const parts = cleanPath.split('/').filter(Boolean)
70
70
  const items = [
@@ -84,7 +84,7 @@ const breadcrumbJson = ({ title, path }) => {
84
84
  position: index + 2,
85
85
  name:
86
86
  index === parts.length - 1
87
- ? title.replace(/ - Markup by Before Semicolon$/, '')
87
+ ? title.replace(/ - Before Semicolon Documentation$/, '')
88
88
  : part
89
89
  .split('-')
90
90
  .map((word) => word[0]?.toUpperCase() + word.slice(1))
@@ -100,9 +100,17 @@ const breadcrumbJson = ({ title, path }) => {
100
100
  }
101
101
  }
102
102
 
103
- const pageJson = ({ title, description, path, name }) => {
103
+ const pageJson = ({
104
+ siteUrl,
105
+ siteName,
106
+ defaultImage,
107
+ title,
108
+ description,
109
+ path,
110
+ name,
111
+ }) => {
104
112
  const isDocsPage = canonicalPath(path).startsWith('/documentation')
105
- const url = absoluteUrl(path)
113
+ const url = absoluteUrl(siteUrl, path)
106
114
 
107
115
  if (isDocsPage) {
108
116
  return {
@@ -116,14 +124,14 @@ const pageJson = ({ title, description, path, name }) => {
116
124
  inLanguage: 'en-US',
117
125
  isPartOf: {
118
126
  '@type': 'TechArticle',
119
- name: 'Markup Documentation',
127
+ name: `${siteName} Documentation`,
120
128
  url: `${siteUrl}/documentation`,
121
129
  },
122
130
  about: [
123
- 'Reactive HTML templating',
124
- 'JavaScript template literals',
125
- 'Markup state and effect APIs',
126
- 'Web Components',
131
+ `${siteName} documentation`,
132
+ 'JavaScript',
133
+ 'Web development',
134
+ 'Developer tools',
127
135
  ],
128
136
  publisher: {
129
137
  '@type': 'Organization',
@@ -141,15 +149,12 @@ const pageJson = ({ title, description, path, name }) => {
141
149
  return {
142
150
  '@context': 'https://schema.org',
143
151
  '@type': 'SoftwareSourceCode',
144
- name: 'Markup',
145
- alternateName: '@beforesemicolon/markup',
152
+ name: siteName,
146
153
  description,
147
154
  url,
148
155
  image: defaultImage,
149
- codeRepository: 'https://github.com/beforesemicolon/markup',
150
156
  programmingLanguage: 'JavaScript',
151
157
  runtimePlatform: 'Browser and Node.js',
152
- license: 'https://github.com/beforesemicolon/markup/blob/main/LICENSE',
153
158
  publisher: {
154
159
  '@type': 'Organization',
155
160
  name: 'Before Semicolon',
@@ -159,9 +164,20 @@ const pageJson = ({ title, description, path, name }) => {
159
164
  }
160
165
 
161
166
  export default (props = {}) => {
167
+ const meta = props.templateConfig?.meta || {}
168
+ const siteUrl = String(
169
+ meta.siteUrl || props.siteUrl || fallbackSiteUrl
170
+ ).replace(/\/$/, '')
171
+ const siteName = meta.siteName || fallbackSiteName
172
+ const defaultTitle = meta.title || fallbackTitle
173
+ const defaultDescription = meta.description || fallbackDescription
174
+ const defaultImage = absoluteAssetUrl(siteUrl, meta.image || fallbackImage)
162
175
  const title = props.title || defaultTitle
163
- const description = compactDescription(props.description)
164
- const url = absoluteUrl(props.path)
176
+ const description = compactDescription(
177
+ props.description,
178
+ defaultDescription
179
+ )
180
+ const url = absoluteUrl(siteUrl, props.path)
165
181
  const canonical = escapeHTML(url)
166
182
  const image = props.image || defaultImage
167
183
  const cacheKey = props.cacheKey ? `?v=${escapeHTML(props.cacheKey)}` : ''
@@ -213,20 +229,7 @@ export default (props = {}) => {
213
229
  <link rel="manifest" href="/assets/favicon/site.webmanifest" />
214
230
  <link rel="icon" type="image/x-icon" href="/assets/favicon/favicon.ico" />
215
231
  <link rel="stylesheet" href="/stylesheets/github-dark.hightlighter.css${cacheKey}">
216
- ${jsonScript(pageJson({ ...props, title, description }))}
217
- ${jsonScript(breadcrumbJson({ title, path: props.path }))}
218
- ${
219
- isDev
220
- ? ''
221
- : `<!-- Google tag (gtag.js) -->
222
- <script async src="https://www.googletagmanager.com/gtag/js?id=G-N3MXGDP5PS"></script>
223
- <script>
224
- window.dataLayer = window.dataLayer || [];
225
- function gtag(){dataLayer.push(arguments);}
226
- gtag('js', new Date());
227
-
228
- gtag('config', 'G-N3MXGDP5PS');
229
- </script>`
230
- }
232
+ ${jsonScript(pageJson({ ...props, siteUrl, siteName, defaultImage, title, description }))}
233
+ ${jsonScript(breadcrumbJson({ siteUrl, title, path: props.path }))}
231
234
  `
232
235
  }
@@ -46,6 +46,7 @@ export default (props) => {
46
46
  <html lang="en">
47
47
  <head>
48
48
  ${meta(props)}
49
+ ${props.headScripts?.join('') || ''}
49
50
  <link rel="stylesheet" href="/stylesheets/documentation.css${cacheKey}">
50
51
  ${
51
52
  props.themeStylesheet
@@ -10,6 +10,7 @@ export default (props) => {
10
10
  <html lang="en">
11
11
  <head>
12
12
  ${meta(props)}
13
+ ${props.headScripts?.join('') || ''}
13
14
  <link rel="stylesheet" href="/stylesheets/landing.css${cacheKey}">
14
15
  ${
15
16
  props.themeStylesheet
@@ -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 ie=Object.defineProperty;var c=(e,t)=>ie(e,"name",{value:t,configurable:!0});import{Marked as le}from"marked";import{cp as j,mkdir as S,readdir as ce,readFile as C,writeFile as x}from"fs/promises";import o from"path";import me from"front-matter";import ue from"isomorphic-dompurify";import{markedHighlight as pe}from"marked-highlight";import Q from"highlight.js";import fe from"./templates/default.template.js";import de from"./renderer/index.js";import{createMarkdownLayoutExtension as he}from"./markdown-layout/index.js";import ge from"clean-css";import ye from"html-minifier";import*as X from"@putout/minify";import{fileURLToPath as we,pathToFileURL as A}from"url";import p from"fs";let V,R="";const Y=new ge({inline:["none"]}),W=new Map;W.set("default",fe);let _;const Z=c((e,t)=>t===!1?!1:t===void 0?e:e===!1||e===void 0?t:{...e,...t},"mergeThemeMode"),ve=c((e,t)=>{if(!(!e&&!t))return{light:Z(e?.light,t?.light),dark:Z(e?.dark,t?.dark)}},"mergeTheme"),Se=c((e,t)=>({...e,...t,markdownLayouts:{...e.markdownLayouts,...t.markdownLayouts},headScripts:{...e.headScripts,...t.headScripts},scripts:{...e.scripts,...t.scripts},theme:ve(e.theme,t.theme),meta:{...e.meta,...t.meta}}),"mergeTemplateConfig"),De=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"),ee=c((e,t)=>e?Object.values(e).filter(r=>r!==!1).filter(r=>De(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,17 +7,17 @@ ${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"),te=c((e,t)=>e?`${e.replace(/\/$/,"")}${t==="/"?"/":t}`:t,"canonicalUrl"),ke=c(async(e,t)=>p.existsSync(e)?(await S(o.dirname(t),{recursive:!0}),await j(e,t),!0):!1,"copyIfExists"),Te=c(async e=>{const t=o.resolve(e,"logo.svg");p.existsSync(t)&&await Promise.all(["logo.light.svg","logo.dark.svg"].map(async r=>{const a=o.resolve(e,r);p.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
- <loc>${xe(ee(t,i.urlPath))}</loc>
13
+ <loc>${xe(te(t,i.urlPath))}</loc>
14
14
  <lastmod>${r}</lastmod>
15
15
  <changefreq>weekly</changefreq>
16
16
  <priority>${m}</priority>
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
@@ -35,7 +35,7 @@ This file is generated at build time from the Markdown documentation. Use it to
35
35
 
36
36
  ${e.map(r=>`## ${r.title}
37
37
 
38
- URL: ${ee(t,r.urlPath)}
38
+ URL: ${te(t,r.urlPath)}
39
39
  Source: docs/${r.sourcePath}
40
40
  Description: ${r.description}
41
41
 
@@ -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 S(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"),se=c((e={},t,r)=>{const a=new le(pe({langPrefix:"hljs language-",highlight(i,m){const d=Q.getLanguage(m)?m:"plaintext";return Q.highlight(i,{language:d}).value}}));return a.use({renderer:de}),a.use(he({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=se(e,t),i=c(d=>a.parse(d),"renderMarkdown"),m=se(e,t,i);return m.use({hooks:{postprocess(d){const{layout:y="default",...P}=_.attributes,k=ee(e.headScripts,d),T=ee(e.scripts,d);d=ue.sanitize(d);const D=[...d.matchAll(/<h([0-6])\sid="[^"]+".*?>.*?<a\s+href="([^"]+)".*?>([\s\S]*?)<\/a><\/h\1>/gm)].map(u=>({path:u[2],label:u[3].replace(/<[^>]+>/g,"").replace(/\s+/g," ").trim(),level:u[1]}));return W.get(y)?.({...P,content:d,siteMap:_.siteMap,tableOfContent:D,projectMeta:V,renderMarkdown:i,headScripts:k,scripts:T,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 ce(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");p.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(we(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 D={};if(y)try{let s=o.resolve(y,"template.config.js");if(p.existsSync(s)||(s=s.replace(/\.js$/,".ts")),p.existsSync(s)){const n=await import(A(s).href);D=n.default||n}}catch(s){console.error(`Failed to load template config for ${a}`,s)}const u=e??o.resolve(process.cwd(),"docs"),f=t??o.resolve(process.cwd(),"website"),re=Me(m),v=o.resolve(u,"_template"),B=o.resolve(v,"assets"),N=o.resolve(v,"stylesheets"),U=o.resolve(v,"scripts"),z=o.resolve(v,"layouts"),q=o.resolve(u,"_layouts"),F=o.resolve(u,"assets"),H=o.resolve(u,"stylesheets"),L=o.resolve(u,"scripts");try{let s=o.resolve(v,"template.config.js");if(p.existsSync(s)||(s=s.replace(/\.js$/,".ts")),p.existsSync(s)){const n=await import(A(s).href),l=n.default||n;D=Se(D,l)}}catch(s){console.error("Failed to load docs _template config",s)}const oe=Ae(D,r,i);if(await S(f,{recursive:!0}),P&&p.existsSync(P))try{await j(P,F.replace(u,f),{recursive:!0})}catch(s){console.warn(s)}if(p.existsSync(F))try{await j(F,F.replace(u,f),{recursive:!0})}catch(s){console.warn(s)}if(p.existsSync(B))try{await j(B,o.resolve(f,"assets"),{recursive:!0})}catch(s){console.warn(s)}await Te(o.resolve(f,"assets"));try{const s=new Set;if(k&&p.existsSync(k)&&(await w(k)).forEach(l=>s.add(l)),p.existsSync(H)&&(await w(H)).forEach(l=>s.add(l)),p.existsSync(N)&&(await w(N)).forEach(l=>s.add(l)),s.size){await S(o.join(f,"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(v)?l.replace(v,f):l.startsWith(u)?l.replace(u,f):o.join(f,"stylesheets",o.basename(l));await x(M,g.styles??h)}}const n=be(D.theme);if(n){const l=Y.minify(n);l.errors.length&&l.errors.forEach(console.error),await S(o.join(f,"stylesheets"),{recursive:!0}),await x(o.join(f,"stylesheets","theme.css"),l.styles??n)}}catch(s){console.error(s)}if(p.existsSync(L))try{const s=await w(L);s.length&&await S(L.replace(u,f),{recursive:!0});for(const n of s)try{const l=await C(n,"utf-8");await x(n.replace(u,f),X.minify(l))}catch(l){console.error(l)}}catch(s){console.warn(s)}if(p.existsSync(U))try{const s=await w(U);s.length&&await S(o.join(f,"scripts"),{recursive:!0});for(const n of s)try{const l=await C(n,"utf-8");await x(n.replace(v,f),X.minify(l))}catch(l){console.error(l)}}catch(s){console.warn(s)}try{const s=new Map;T&&p.existsSync(T)&&(await w(T)).forEach(n=>{/\.(j|t)s$/.test(n)&&s.set(o.basename(n).replace(/\.(j|t)s$/,""),n)}),p.existsSync(q)&&(await w(q)).forEach(n=>{/\.(j|t)s$/.test(n)&&s.set(o.basename(n).replace(/\.(j|t)s$/,""),n)}),p.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 ae=await w(u),I=new Map,J=(await Promise.all(ae.filter(s=>s.endsWith(".md")).map(async s=>{const n=await C(s,"utf-8");return{filePath:s,...me(n)}}))).sort((s,n)=>(s.attributes.order??0)-(n.attributes.order??0)).map(({attributes:s,body:n,filePath:l})=>{const h=l.replace(u,"").replace(/\.md/,".html"),g=l.replace(u,f).replace(".md",".html"),M=g.replace(o.basename(g),""),E=o.basename(h)||"/",ne=h.replace(E,"").replace(/\/$/,"");let b=I;ne.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(u,l),siteMap:I}});for(const{attributes:s,body:n,fileDirWebsitePath:l,fileWebsitePath:h,siteMap:g}of J){_={attributes:s,siteMap:g};const M=await oe.parse(n);await S(l,{recursive:!0}),await x(h,ye.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:u,docsSiteDir:f,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:f,generatedFiles:re})},"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.
@@ -618,7 +637,7 @@ The landing and documentation stylesheets import or rely on the shared styles as
618
637
 
619
638
  ## Scripts
620
639
 
621
- Template scripts are registered by marker:
640
+ Template scripts are registered by marker. The template renders `headScripts` inside `<head>` and `scripts` before `</body>`.
622
641
 
623
642
  ```js
624
643
  scripts: {
@@ -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,17 @@
1
- /* global process */
2
- const isDev = process.env.NODE_ENV === 'development'
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`
1
+ const fallbackSiteUrl = 'https://beforesemicolon.com'
2
+ const fallbackSiteName = 'Before Semicolon'
3
+ const fallbackTitle = 'Before Semicolon Documentation'
4
+ const fallbackDescription =
5
+ 'Documentation for Before Semicolon projects, libraries, and developer tools.'
6
+ const fallbackImage = '/assets/site-image.jpg'
10
7
  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',
8
+ 'Before Semicolon',
9
+ 'documentation',
10
+ 'JavaScript',
11
+ 'developer tools',
12
+ 'web development',
13
+ 'frontend development',
14
+ 'open source',
20
15
  ]
21
16
 
22
17
  const escapeHTML = (value = '') =>
@@ -38,15 +33,20 @@ const canonicalPath = (path = '/') => {
38
33
  return cleanPath.replace(/\.html$/, '')
39
34
  }
40
35
 
41
- const absoluteUrl = (path = '/') => `${siteUrl}${canonicalPath(path)}`
36
+ const absoluteUrl = (siteUrl, path = '/') => `${siteUrl}${canonicalPath(path)}`
42
37
 
43
- const compactDescription = (description) =>
38
+ const absoluteAssetUrl = (siteUrl, imagePath) => {
39
+ if (/^https?:\/\//i.test(imagePath)) return imagePath
40
+ return `${siteUrl}${imagePath.startsWith('/') ? imagePath : `/${imagePath}`}`
41
+ }
42
+
43
+ const compactDescription = (description, defaultDescription) =>
44
44
  (description || defaultDescription).replace(/\s+/g, ' ').trim()
45
45
 
46
46
  const pageKeywords = ({ title, name, keywords } = {}) => {
47
47
  const values = [
48
48
  ...baseKeywords,
49
- title?.replace(/ - Markup by Before Semicolon$/, ''),
49
+ title?.replace(/ - Before Semicolon Documentation$/, ''),
50
50
  name,
51
51
  ...(Array.isArray(keywords)
52
52
  ? keywords
@@ -64,7 +64,7 @@ const jsonScript = (data) =>
64
64
  .replace(/>/g, '\\u003e')
65
65
  .replace(/&/g, '\\u0026')}</script>`
66
66
 
67
- const breadcrumbJson = ({ title, path }) => {
67
+ const breadcrumbJson = ({ siteUrl, title, path }) => {
68
68
  const cleanPath = canonicalPath(path)
69
69
  const parts = cleanPath.split('/').filter(Boolean)
70
70
  const items = [
@@ -84,7 +84,7 @@ const breadcrumbJson = ({ title, path }) => {
84
84
  position: index + 2,
85
85
  name:
86
86
  index === parts.length - 1
87
- ? title.replace(/ - Markup by Before Semicolon$/, '')
87
+ ? title.replace(/ - Before Semicolon Documentation$/, '')
88
88
  : part
89
89
  .split('-')
90
90
  .map((word) => word[0]?.toUpperCase() + word.slice(1))
@@ -100,9 +100,17 @@ const breadcrumbJson = ({ title, path }) => {
100
100
  }
101
101
  }
102
102
 
103
- const pageJson = ({ title, description, path, name }) => {
103
+ const pageJson = ({
104
+ siteUrl,
105
+ siteName,
106
+ defaultImage,
107
+ title,
108
+ description,
109
+ path,
110
+ name,
111
+ }) => {
104
112
  const isDocsPage = canonicalPath(path).startsWith('/documentation')
105
- const url = absoluteUrl(path)
113
+ const url = absoluteUrl(siteUrl, path)
106
114
 
107
115
  if (isDocsPage) {
108
116
  return {
@@ -116,14 +124,14 @@ const pageJson = ({ title, description, path, name }) => {
116
124
  inLanguage: 'en-US',
117
125
  isPartOf: {
118
126
  '@type': 'TechArticle',
119
- name: 'Markup Documentation',
127
+ name: `${siteName} Documentation`,
120
128
  url: `${siteUrl}/documentation`,
121
129
  },
122
130
  about: [
123
- 'Reactive HTML templating',
124
- 'JavaScript template literals',
125
- 'Markup state and effect APIs',
126
- 'Web Components',
131
+ `${siteName} documentation`,
132
+ 'JavaScript',
133
+ 'Web development',
134
+ 'Developer tools',
127
135
  ],
128
136
  publisher: {
129
137
  '@type': 'Organization',
@@ -141,15 +149,12 @@ const pageJson = ({ title, description, path, name }) => {
141
149
  return {
142
150
  '@context': 'https://schema.org',
143
151
  '@type': 'SoftwareSourceCode',
144
- name: 'Markup',
145
- alternateName: '@beforesemicolon/markup',
152
+ name: siteName,
146
153
  description,
147
154
  url,
148
155
  image: defaultImage,
149
- codeRepository: 'https://github.com/beforesemicolon/markup',
150
156
  programmingLanguage: 'JavaScript',
151
157
  runtimePlatform: 'Browser and Node.js',
152
- license: 'https://github.com/beforesemicolon/markup/blob/main/LICENSE',
153
158
  publisher: {
154
159
  '@type': 'Organization',
155
160
  name: 'Before Semicolon',
@@ -159,9 +164,20 @@ const pageJson = ({ title, description, path, name }) => {
159
164
  }
160
165
 
161
166
  export default (props = {}) => {
167
+ const meta = props.templateConfig?.meta || {}
168
+ const siteUrl = String(
169
+ meta.siteUrl || props.siteUrl || fallbackSiteUrl
170
+ ).replace(/\/$/, '')
171
+ const siteName = meta.siteName || fallbackSiteName
172
+ const defaultTitle = meta.title || fallbackTitle
173
+ const defaultDescription = meta.description || fallbackDescription
174
+ const defaultImage = absoluteAssetUrl(siteUrl, meta.image || fallbackImage)
162
175
  const title = props.title || defaultTitle
163
- const description = compactDescription(props.description)
164
- const url = absoluteUrl(props.path)
176
+ const description = compactDescription(
177
+ props.description,
178
+ defaultDescription
179
+ )
180
+ const url = absoluteUrl(siteUrl, props.path)
165
181
  const canonical = escapeHTML(url)
166
182
  const image = props.image || defaultImage
167
183
  const cacheKey = props.cacheKey ? `?v=${escapeHTML(props.cacheKey)}` : ''
@@ -213,20 +229,7 @@ export default (props = {}) => {
213
229
  <link rel="manifest" href="/assets/favicon/site.webmanifest" />
214
230
  <link rel="icon" type="image/x-icon" href="/assets/favicon/favicon.ico" />
215
231
  <link rel="stylesheet" href="/stylesheets/github-dark.hightlighter.css${cacheKey}">
216
- ${jsonScript(pageJson({ ...props, title, description }))}
217
- ${jsonScript(breadcrumbJson({ title, path: props.path }))}
218
- ${
219
- isDev
220
- ? ''
221
- : `<!-- Google tag (gtag.js) -->
222
- <script async src="https://www.googletagmanager.com/gtag/js?id=G-N3MXGDP5PS"></script>
223
- <script>
224
- window.dataLayer = window.dataLayer || [];
225
- function gtag(){dataLayer.push(arguments);}
226
- gtag('js', new Date());
227
-
228
- gtag('config', 'G-N3MXGDP5PS');
229
- </script>`
230
- }
232
+ ${jsonScript(pageJson({ ...props, siteUrl, siteName, defaultImage, title, description }))}
233
+ ${jsonScript(breadcrumbJson({ siteUrl, title, path: props.path }))}
231
234
  `
232
235
  }
@@ -46,6 +46,7 @@ export default (props) => {
46
46
  <html lang="en">
47
47
  <head>
48
48
  ${meta(props)}
49
+ ${props.headScripts?.join('') || ''}
49
50
  <link rel="stylesheet" href="/stylesheets/documentation.css${cacheKey}">
50
51
  ${
51
52
  props.themeStylesheet
@@ -10,6 +10,7 @@ export default (props) => {
10
10
  <html lang="en">
11
11
  <head>
12
12
  ${meta(props)}
13
+ ${props.headScripts?.join('') || ''}
13
14
  <link rel="stylesheet" href="/stylesheets/landing.css${cacheKey}">
14
15
  ${
15
16
  props.themeStylesheet
@@ -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;
@@ -31,9 +38,12 @@ export interface PageProps {
31
38
  [key: string]: unknown;
32
39
  };
33
40
  renderMarkdown?: (markdown: string) => string;
41
+ headScripts?: string[];
34
42
  scripts?: string[];
35
43
  themeStylesheet?: string;
36
44
  cacheKey?: string;
45
+ siteUrl?: string;
46
+ templateConfig?: DocsTemplateConfig;
37
47
  }
38
48
  export interface CustomOptions {
39
49
  name?: string;
@@ -58,6 +68,8 @@ export type SiteMap = Map<string, CustomOptions | SiteMap>;
58
68
  export interface DocsTemplateConfig {
59
69
  markedOptions?: MarkedExtension;
60
70
  markdownLayouts?: Record<string, MarkdownLayoutHandler>;
71
+ headScripts?: DocsScriptRegistry;
61
72
  scripts?: DocsScriptRegistry;
62
73
  theme?: DocsTheme;
74
+ meta?: DocsMetaConfig;
63
75
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@beforesemicolon/builder",
3
- "version": "1.8.20",
3
+ "version": "1.8.23",
4
4
  "description": "Utilities to build npm packages and documentation website",
5
5
  "engines": {
6
6
  "node": ">=18.16.0"