@veluai/velu 0.1.14 → 0.1.15

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/dist/cli.js CHANGED
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
- var ut=Object.defineProperty;var k=(e,t)=>()=>(e&&(t=e(e=0)),t);var ce=(e,t)=>{for(var o in t)ut(e,o,{get:t[o],enumerable:!0})};var je={};ce(je,{initProject:()=>dt});import z from"node:fs/promises";import V from"node:path";import{fileURLToPath as ft}from"node:url";async function dt(e,t){try{if((await z.readdir(e)).length>0)throw new Error(`Directory "${t}" already exists and is not empty. Choose a different name or remove the folder first.`)}catch(s){if(s.code!=="ENOENT")throw s}await z.mkdir(e,{recursive:!0}),await z.cp(mt,e,{recursive:!0});let o=V.join(e,"velu.json"),n=JSON.parse(await z.readFile(o,"utf-8"));n.name=t,await z.writeFile(o,JSON.stringify(n,null,2)+`
3
- `,"utf-8");let r=await $e(e);console.log(`[velu] created ${t}/`);for(let s of r)console.log(` ${s}`);console.log(""),console.log("Next steps:"),console.log(` cd ${t}`),console.log(" velu dev")}async function $e(e,t=e,o=[]){let n=await z.readdir(e,{withFileTypes:!0});for(let r of n){let s=V.join(e,r.name);r.isDirectory()?await $e(s,t,o):o.push(V.relative(t,s).split(V.sep).join("/"))}return o.sort()}var pt,mt,Ae=k(()=>{pt=V.dirname(ft(import.meta.url)),mt=V.resolve(pt,"..","templates","starter")});import{visit as gt}from"unist-util-visit";import{toString as ht}from"mdast-util-to-string";import vt from"github-slugger";import{valueToEstree as yt}from"estree-util-value-to-estree";function Y(){return e=>{let t=new vt,o=[];gt(e,"heading",i=>{let a=ht(i);o.push({depth:i.depth,id:t.slug(a),label:a})});let n={children:[]},r=[{node:n,depth:0}];for(let i of o){for(;r.length>1&&r[r.length-1].depth>=i.depth;)r.pop();let a={id:i.id,label:i.label},l=r[r.length-1].node;l.children||(l.children=[]),l.children.push(a),r.push({node:a,depth:i.depth})}let s=n.children;e.children.unshift({type:"mdxjsEsm",value:"",data:{estree:{type:"Program",sourceType:"module",body:[{type:"ExportNamedDeclaration",specifiers:[],declaration:{type:"VariableDeclaration",kind:"const",declarations:[{type:"VariableDeclarator",id:{type:"Identifier",name:"toc"},init:yt(s)}]}}]}}})}}var ue=k(()=>{});import xt from"node:fs/promises";import Pe from"node:path";function fe(e={},t){let o=e.colors?.primary??wt;return{name:e.name??t??"docs",colors:{primary:o,light:e.colors?.light??o,dark:e.colors?.dark??o},favicon:e.favicon??null,font:{family:e.font?.family??bt},navigation:e.navigation??null}}async function Ce(e){let t=Pe.join(e,"velu.json"),o;try{o=await xt.readFile(t,"utf-8")}catch(r){throw r.code==="ENOENT"?new Error(`No velu.json found in ${e}. Run \`velu init <name>\` to scaffold one.`):r}let n;try{n=JSON.parse(o)}catch(r){throw new Error(`velu.json is not valid JSON: ${r.message}`)}return fe(n,Pe.basename(e))}function Ee(e){let{colors:t,font:o}=e;return[":root:root {",` --accent-color: ${t.light};`,` --font-sans: "${o.family}", sans-serif;`,"}",':root:root[data-theme="dark"] {',` --accent-color: ${t.dark};`,"}"].join(`
4
- `)}var wt,bt,Te=k(()=>{wt="#dc143c",bt="Google Sans Flex"});import kt from"github-slugger";import St from"iso-639-1";function jt(e){let t=String(e).toLowerCase();return St.getNativeName(t)||e}function Q(e){return new kt().slug(String(e??""))}function At(e){let t=String(e).replace(/^\/+|\/+$/g,"").split("/").filter(Boolean);return t[t.length-1]==="index"&&t.pop(),"/"+t.join("/")}function B(e){let t={kind:"root",children:H(e??{})};return Ne(t.children),t}function H(e){let t=Array.isArray(e.anchors)?e.anchors.map(_t):[];if(Array.isArray(e.products))return[...t,...e.products.map(Pt)];if(Array.isArray(e.versions))return[...t,...e.versions.map(Ct)];if(Array.isArray(e.languages))return[...t,...e.languages.map(Et)];if(Array.isArray(e.tabs))return[...t,...e.tabs.map(Tt)];let o=[...t];return Array.isArray(e.groups)&&o.push(...e.groups.map(_e)),Array.isArray(e.pages)&&o.push(...e.pages.map(Oe)),o}function Pt(e){return{kind:"product",label:e.name??e.product,slug:Q(e.product),icon:e.icon,color:e.color,default:!!e.default,children:H(e)}}function Ct(e){return{kind:"version",label:e.version,slug:Q(e.version),default:!!e.default,children:H(e)}}function Et(e){let t=String(e.language);return{kind:"language",code:t,label:jt(t),slug:Q(t),default:!!e.default,children:H(e)}}function Tt(e){return{kind:"tab",label:e.tab,slug:Q(e.tab),icon:e.icon,href:e.href,children:H(e)}}function _t(e){return{kind:"anchor",label:e.anchor,icon:e.icon,href:e.href,color:e.color}}function _e(e){let t=(e.pages??[]).map(Oe);return e.root&&t.unshift({kind:"page",pagePath:e.root}),{kind:"group",label:e.group,icon:e.icon,expanded:!!e.expanded,root:e.root,children:t}}function Oe(e){return typeof e=="string"?{kind:"page",pagePath:e}:_e(e)}function Ne(e){for(let t of $t){let o=e.filter(n=>n.kind===t);o.length&&!o.some(n=>n.default)&&(o[0].default=!0)}for(let t of e)t.children&&Ne(t.children)}function Le(e){let t=[],o=(n,r,s)=>{if(n.kind==="page"){t.push({pagePath:n.pagePath,url:Ot(n.pagePath,r),ctx:{...r},ancestors:s,node:n});return}let i=r;n.kind==="product"?i={...r,product:n.default?null:n.slug}:n.kind==="version"?i={...r,version:n.default?null:n.slug}:n.kind==="language"&&(i={...r,language:n.default?null:n.slug});let a=n.kind==="root"?s:[...s,n];for(let l of n.children??[])o(l,i,a)};return o(e,{product:null,version:null,language:null},[]),t}function Ot(e,t){let o=[t.product,t.version,t.language].filter(Boolean),n=At(e).split("/").filter(Boolean);return"/"+[...o,...n].join("/")}var $t,W=k(()=>{$t=["product","version","language"]});import Nt from"node:fs";import Fe from"node:path";function G(e,t){let o=[],n=[],r=new Set;for(let s of Le(e)){if(r.has(s.url)){o.push(`duplicate URL "${s.url}" (page "${s.pagePath}") \u2014 keeping the first occurrence`);continue}r.add(s.url);let i=s.pagePath.replace(/^\/+|\/+$/g,""),a=Fe.join(t,...i.split("/"))+".mdx",l=Nt.existsSync(a);l||o.push(`page "${s.pagePath}" \u2192 ${Fe.relative(t,a)} not found`),n.push({pagePath:s.pagePath,url:s.url,fileAbsPath:a,exists:l})}return{pageEntries:n,warnings:o}}var ee=k(()=>{W()});import Lt from"node:fs";import te from"node:path";function Me(e){return{name:"velu-site",resolveId(t){return t===Re?Ie:null},load(t){return t!==Ie?null:Ft(e)}}}function Ft(e){let t={};try{t=JSON.parse(Lt.readFileSync(te.join(e,"velu.json"),"utf-8"))}catch{}let o=B(t.navigation??{}),{pageEntries:n,warnings:r}=G(o,e);for(let l of r)console.warn(`[velu] nav: ${l}`);let s=[],i=[],a=0;for(let l of n)if(l.exists){let u=JSON.stringify(l.fileAbsPath.split(te.sep).join("/")),c=JSON.stringify(te.relative(e,l.fileAbsPath).split(te.sep).join("/")),f=`__p${a++}`;s.push(`import * as ${f} from ${u};`),i.push(` ${JSON.stringify(l.url)}: { Component: ${f}.default, frontmatter: ${f}.frontmatter, toc: ${f}.toc, relPath: ${c} },`)}else i.push(` ${JSON.stringify(l.url)}: { missing: true },`);return["// AUTO-GENERATED by vite-plugin-velu-site. Do not edit.",s.join(`
5
- `),"","export const pages = {",i.join(`
6
- `),"};",`export const navigation = ${JSON.stringify(o)};`,""].join(`
7
- `)}var Re,Ie,Ue=k(()=>{W();ee();Re="virtual:velu-site",Ie="\0"+Re});function h(e){return{category:"render-crash",severity:"error",file:null,line:null,column:null,frame:null,title:"Something went wrong",detail:"",hint:"",suggestion:null,...e}}function It(e,t){if(e=String(e),t=String(t),e===t)return 0;if(!e.length)return t.length;if(!t.length)return e.length;let o=Array.from({length:t.length+1},(r,s)=>s),n=new Array(t.length+1);for(let r=1;r<=e.length;r++){n[0]=r;for(let s=1;s<=t.length;s++){let i=e[r-1]===t[s-1]?0:1;n[s]=Math.min(n[s-1]+1,o[s]+1,o[s-1]+i)}[o,n]=[n,o]}return o[t.length]}function ne(e,t=[]){if(!e)return null;let o=String(e).toLowerCase(),n=null,r=1/0;for(let i of t){let a=It(o,String(i).toLowerCase());a<r&&(r=a,n=i)}let s=Math.min(3,Math.max(2,Math.floor(o.length/4)));return r<=s?n:null}function Rt(e,t){return e==="warning"?C(t,$.yellow,"\u26A0"):e==="info"?C(t,$.cyan,"\u2139"):C(t,$.red,"\u2716")}function me(e,{color:t=!1}={}){let o=pe[e.category]||e.category,n=e.line?`${e.line}${e.column?`:${e.column}`:""}`:"",r=e.file?`${e.file}${n?`:${n}`:""}`:n,s=[];if(s.push(`${Rt(e.severity,t)} ${C(t,$.bold,o)}`+(r?` ${C(t,$.dim,r)}`:"")),e.title&&s.push(` ${e.title}`),e.detail&&e.detail!==e.title&&s.push(` ${C(t,$.dim,e.detail)}`),e.frame)for(let i of String(e.frame).split(`
8
- `))s.push(` ${i}`);return e.suggestion&&s.push(` ${C(t,$.cyan,`Did you mean <${e.suggestion}>?`)}`),e.hint&&s.push(` ${C(t,$.dim,`\u2192 ${e.hint}`)}`),s.join(`
9
- `)}function oe(e,{color:t=!1}={}){if(!e.length)return C(t,$.cyan,"\u2713 No issues found.");let o=new Map;for(let a of e){let l=a.file||"velu.json";o.has(l)||o.set(l,[]),o.get(l).push(a)}let n=[];for(let[a,l]of o){n.push(C(t,$.bold,a));for(let u of l)n.push(me({...u,file:null},{color:t}).replace(/^/gm," "));n.push("")}let r=e.filter(a=>a.severity!=="warning"&&a.severity!=="info").length,s=e.length-r,i=`${r} error${r===1?"":"s"}`+(s?`, ${s} warning${s===1?"":"s"}`:"");return n.push(C(t,r?$.red:$.yellow,i)),n.join(`
10
- `)}var pe,$,C,F=k(()=>{pe={"mdx-syntax":"MDX syntax",frontmatter:"Frontmatter","unknown-component":"Unknown component","invalid-props":"Invalid options","render-crash":"Render error","config-json":"velu.json","config-schema":"velu.json","config-nav":"Navigation","config-asset":"Asset"};$={reset:"\x1B[0m",red:"\x1B[31m",yellow:"\x1B[33m",cyan:"\x1B[36m",dim:"\x1B[2m",bold:"\x1B[1m"},C=(e,t,o)=>e?`${t}${o}${$.reset}`:o});function Mt(e){let t=e,o=0;for(;t&&t.cause&&o<3&&!(t.loc||t.line||/`[^`]+` to be defined/.test(t.message||""));)t=t.cause,o+=1;return t||e}function de(e,t){if(!e)return null;let o=ze(e).replace(/[?#].*$/,""),n=t?ze(t).replace(/\/+$/,""):"";return n&&o.toLowerCase().startsWith(n.toLowerCase())?o.slice(n.length).replace(/^\/+/,""):!/^([a-zA-Z]:\/|\/)/.test(o)&&!/node_modules/.test(o)?o:null}function Ut(e){return e?String(e).replace(/([a-zA-Z]:\\|\/)[^\s'"()]+/g,t=>/node_modules|[\\/]vite[\\/]|@veluai|velu-cli|velu-ui/.test(t)?"\u2026":t).trim():""}function zt(e){let t=/\((\d+):(\d+)(?:-\d+:\d+)?\)\s*$/.exec(String(e||""));return t?{line:Number(t[1]),column:Number(t[2])}:null}function Vt(e){return String(e||"").replace(/\s*\(\d+:\d+(?:-\d+:\d+)?\)\s*$/,"").trim()}function Gt(e,t){if(!e||!t)return null;let o=String(e);return/node_modules|[\\/]vite[\\/]|@veluai[\\/]/.test(o)?null:o}function K(e,t={}){let{projectDir:o="",knownComponents:n=[],file:r=null}=t,s=null;for(let E=e,A=0;E&&A<6;E=E.cause,A+=1)if(E.veluIssue){s=E.veluIssue;break}if(s)return h({...s,file:s.file||r});let i=Mt(e)||{},a=i.message||String(e||""),l=/Expected component `([^`]+)` to be defined/.exec(a);if(l){let E=l[1],A=/referenced in your code at `(\d+):(\d+)/.exec(a),q=/\bin `([^`]+)`/.exec(a),v=q?de(q[1],o):null;return h({category:"unknown-component",file:v||r,line:A?Number(A[1]):null,column:A?Number(A[2]):null,title:`<${E}> is not a built-in component`,detail:"",hint:"Components are built in \u2014 check the spelling and capitalization (you don\u2019t import them).",suggestion:ne(E,n)})}let u=i.loc||(i.line!=null?{line:i.line,column:i.column,file:i.file}:null)||zt(i.reason||a),c=i.id||u&&u.file||i.file||null,f=de(c,o)||r,_=!!f&&de(c,o)!=null,d=Gt(i.frame,_||!!r),j=Ut(Vt(i.reason||a)),m=Bt.test(a),L=i.source==="remark-frontmatter"||/\byaml\b|frontmatter/i.test(a)||u&&u.line===1&&/unexpected|expected/i.test(a)&&/---/.test(a);return u||m||d&&_?h({category:L?"frontmatter":"mdx-syntax",file:f,line:u?u.line:null,column:u?u.column:null,frame:d,title:j||(L?"Invalid frontmatter":"MDX syntax error"),detail:"",hint:L?"Check the YAML between the --- markers \u2014 indentation, quotes, and colons.":"Make sure every <Component> has a matching closing tag and that { } braces are balanced."}):/\.map is not a function|is not iterable|reading '|undefined \(reading/i.test(a)?h({category:"invalid-props",file:f,title:"A component received an invalid value",detail:j,hint:"Check the props/options you passed to a component on this page."}):h({category:"render-crash",file:f,title:"This page failed to render",detail:j,hint:"Set VELU_DEBUG=1 for the full stack trace."})}var ze,Bt,ge=k(()=>{F();ze=e=>String(e).replace(/\\/g,"/");Bt=/closing tag|unexpected closing|could not parse (?:expression|import|export)|unexpected character|unexpected end of|expected a closing|expected the closing|unexpected `|in expression|misnested|before name/i});function Jt(e){let t=pe[e.category]||e.category,o=e.file?`${e.file}${e.line?`:${e.line}${e.column?`:${e.column}`:""}`:""}`:"",n=[];return n.push('<div class="velu-err-card">'),n.push('<div class="velu-err-head">'),n.push(`<span class="velu-err-pill">${O(t)}</span>`),n.push("</div>"),e.title&&n.push(`<div class="velu-err-title">${O(e.title)}</div>`),o&&n.push(`<a class="velu-err-loc" title="${O(o)}">${O(o)}</a>`),e.detail&&n.push(`<div class="velu-err-detail">${O(e.detail)}</div>`),e.frame&&n.push(`<pre class="velu-err-frame">${O(e.frame)}</pre>`),e.suggestion&&n.push(`<div class="velu-err-suggest">Did you mean <code>&lt;${O(e.suggestion)}&gt;</code>?</div>`),e.hint&&n.push(`<div class="velu-err-hint">${O(e.hint)}</div>`),n.push("</div>"),n.join("")}function he(e,t={}){let o=Array.isArray(e)?e:[e],n=t.title||(o.length>1?`${o.length} problems to fix`:"There\u2019s a problem to fix"),r=o.map(Jt).join(`
2
+ var vt=Object.defineProperty;var k=(e,t)=>()=>(e&&(t=e(e=0)),t);var de=(e,t)=>{for(var o in t)vt(e,o,{get:t[o],enumerable:!0})};var Ne={};de(Ne,{initProject:()=>wt});import z from"node:fs/promises";import G from"node:path";import{fileURLToPath as yt}from"node:url";async function wt(e,t){try{if((await z.readdir(e)).length>0)throw new Error(`Directory "${t}" already exists and is not empty. Choose a different name or remove the folder first.`)}catch(i){if(i.code!=="ENOENT")throw i}await z.mkdir(e,{recursive:!0}),await z.cp(bt,e,{recursive:!0});let o=G.join(e,"velu.json"),n=JSON.parse(await z.readFile(o,"utf-8"));n.name=t,await z.writeFile(o,JSON.stringify(n,null,2)+`
3
+ `,"utf-8");let r=await Ee(e);console.log(`[velu] created ${t}/`);for(let i of r)console.log(` ${i}`);console.log(""),console.log("Next steps:"),console.log(` cd ${t}`),console.log(" velu dev")}async function Ee(e,t=e,o=[]){let n=await z.readdir(e,{withFileTypes:!0});for(let r of n){let i=G.join(e,r.name);r.isDirectory()?await Ee(i,t,o):o.push(G.relative(t,i).split(G.sep).join("/"))}return o.sort()}var xt,bt,Fe=k(()=>{xt=G.dirname(yt(import.meta.url)),bt=G.resolve(xt,"..","templates","starter")});import{visit as kt}from"unist-util-visit";import{toString as St}from"mdast-util-to-string";import $t from"github-slugger";import{valueToEstree as jt}from"estree-util-value-to-estree";function D(){return e=>{let t=new $t,o=[];kt(e,"heading",a=>{let s=St(a);o.push({depth:a.depth,id:t.slug(s),label:s})});let n={children:[]},r=[{node:n,depth:0}];for(let a of o){for(;r.length>1&&r[r.length-1].depth>=a.depth;)r.pop();let s={id:a.id,label:a.label},l=r[r.length-1].node;l.children||(l.children=[]),l.children.push(s),r.push({node:s,depth:a.depth})}let i=n.children;e.children.unshift({type:"mdxjsEsm",value:"",data:{estree:{type:"Program",sourceType:"module",body:[{type:"ExportNamedDeclaration",specifiers:[],declaration:{type:"VariableDeclaration",kind:"const",declarations:[{type:"VariableDeclarator",id:{type:"Identifier",name:"toc"},init:jt(i)}]}}]}}})}}var ge=k(()=>{});import At from"node:fs/promises";import Ie from"node:path";function Y(e){if(typeof e=="string"){let t=e.trim();return t?{light:t,dark:t,href:"/"}:null}if(e&&typeof e=="object"){let t=(e.light??e.dark)?.trim?.(),o=(e.dark??e.light)?.trim?.();return!t||!o?null:{light:t,dark:o,href:e.href?.trim?.()||"/"}}return null}function ve(e){if(!e||typeof e!="object")return null;let t=[];if(e.socials&&typeof e.socials=="object")for(let[n,r]of Object.entries(e.socials))typeof r!="string"||!r.trim()||t.push({kind:_t[n]??n,href:r.trim()});let o=Array.isArray(e.links)?e.links.filter(n=>n&&typeof n=="object").map(n=>({title:(n.header??n.title??"").trim(),items:(Array.isArray(n.items)?n.items:[]).filter(r=>r&&r.label).map(r=>({label:r.label,href:r.href??"#"}))})).filter(n=>n.title||n.items.length):[];return!t.length&&!o.length?null:{socials:t,links:o}}function he(e){return/^(https?:)?\/\//.test(e)||/^(mailto|tel):/.test(e)}function ye(e){if(!e||typeof e!="object")return null;let t=(Array.isArray(e.links)?e.links:[]).filter(r=>r&&typeof r.href=="string"&&r.href.trim()).map(r=>({label:r.label??"",href:r.href.trim(),icon:r.icon??null,external:he(r.href.trim())})),o=null,n=e.primary;if(n&&typeof n=="object"&&typeof n.href=="string"&&n.href.trim()){let r=n.href.trim();o=n.type==="github"?{kind:"github",label:n.label??"GitHub",href:r,external:he(r)}:{kind:"button",label:n.label??"",href:r,external:he(r)}}return!t.length&&!o?null:{links:t,primary:o}}function xe(e={},t){let o=e.colors?.primary??Lt;return{name:e.name??t??"docs",colors:{primary:o,light:e.colors?.light??o,dark:e.colors?.dark??o},favicon:e.favicon??null,logo:Y(e.logo),footer:ve(e.footer),navbar:ye(e.navbar),font:{family:e.font?.family??Ot},navigation:e.navigation??null}}async function Re(e){let t=Ie.join(e,"velu.json"),o;try{o=await At.readFile(t,"utf-8")}catch(r){throw r.code==="ENOENT"?new Error(`No velu.json found in ${e}. Run \`velu init <name>\` to scaffold one.`):r}let n;try{n=JSON.parse(o)}catch(r){throw new Error(`velu.json is not valid JSON: ${r.message}`)}return xe(n,Ie.basename(e))}function Me(e){let{colors:t,font:o}=e;return[":root:root {",` --accent-color: ${t.light};`,` --font-sans: "${o.family}", sans-serif;`,"}",':root:root[data-theme="dark"] {',` --accent-color: ${t.dark};`,"}"].join(`
4
+ `)}var Lt,Ot,ne,oe,_t,re=k(()=>{Lt="#dc143c",Ot="Google Sans Flex",ne="/@velu-logo-light",oe="/@velu-logo-dark";_t={twitter:"x","x-twitter":"x"}});import Pt from"github-slugger";import Ct from"iso-639-1";function Et(e){let t=String(e).toLowerCase();return Ct.getNativeName(t)||e}function ie(e){return new Pt().slug(String(e??""))}function Nt(e){let t=String(e).replace(/^\/+|\/+$/g,"").split("/").filter(Boolean);return t[t.length-1]==="index"&&t.pop(),"/"+t.join("/")}function V(e){let t={kind:"root",children:K(e??{})};return Ge(t.children),t}function K(e){let t=Array.isArray(e.anchors)?e.anchors.map(Ut):[];if(Array.isArray(e.products))return[...t,...e.products.map(Ft)];if(Array.isArray(e.versions))return[...t,...e.versions.map(It)];if(Array.isArray(e.languages))return[...t,...e.languages.map(Rt)];if(Array.isArray(e.tabs))return[...t,...e.tabs.map(Mt)];let o=[...t];return Array.isArray(e.groups)&&o.push(...e.groups.map(Ue)),Array.isArray(e.pages)&&o.push(...e.pages.map(ze)),o}function Ft(e){return{kind:"product",label:e.name??e.product,slug:ie(e.product),icon:e.icon,color:e.color,default:!!e.default,children:K(e)}}function It(e){return{kind:"version",label:e.version,slug:ie(e.version),default:!!e.default,children:K(e)}}function Rt(e){let t=String(e.language);return{kind:"language",code:t,label:Et(t),slug:ie(t),default:!!e.default,children:K(e)}}function Mt(e){return{kind:"tab",label:e.tab,slug:ie(e.tab),icon:e.icon,href:e.href,children:K(e)}}function Ut(e){return{kind:"anchor",label:e.anchor,icon:e.icon,href:e.href,color:e.color}}function Ue(e){let t=(e.pages??[]).map(ze);return e.root&&t.unshift({kind:"page",pagePath:e.root}),{kind:"group",label:e.group,icon:e.icon,expanded:!!e.expanded,root:e.root,children:t}}function ze(e){return typeof e=="string"?{kind:"page",pagePath:e}:Ue(e)}function Ge(e){for(let t of Tt){let o=e.filter(n=>n.kind===t);o.length&&!o.some(n=>n.default)&&(o[0].default=!0)}for(let t of e)t.children&&Ge(t.children)}function Ve(e){let t=[],o=(n,r,i)=>{if(n.kind==="page"){t.push({pagePath:n.pagePath,url:zt(n.pagePath,r),ctx:{...r},ancestors:i,node:n});return}let a=r;n.kind==="product"?a={...r,product:n.default?null:n.slug}:n.kind==="version"?a={...r,version:n.default?null:n.slug}:n.kind==="language"&&(a={...r,language:n.default?null:n.slug});let s=n.kind==="root"?i:[...i,n];for(let l of n.children??[])o(l,a,s)};return o(e,{product:null,version:null,language:null},[]),t}function zt(e,t){let o=[t.product,t.version,t.language].filter(Boolean),n=Nt(e).split("/").filter(Boolean);return"/"+[...o,...n].join("/")}var Tt,W=k(()=>{Tt=["product","version","language"]});import Gt from"node:fs";import Be from"node:path";function B(e,t){let o=[],n=[],r=new Set;for(let i of Ve(e)){if(r.has(i.url)){o.push(`duplicate URL "${i.url}" (page "${i.pagePath}") \u2014 keeping the first occurrence`);continue}r.add(i.url);let a=i.pagePath.replace(/^\/+|\/+$/g,""),s=Be.join(t,...a.split("/"))+".mdx",l=Gt.existsSync(s);l||o.push(`page "${i.pagePath}" \u2192 ${Be.relative(t,s)} not found`),n.push({pagePath:i.pagePath,url:i.url,fileAbsPath:s,exists:l})}return{pageEntries:n,warnings:o}}var se=k(()=>{W()});import Vt from"node:fs";import Z from"node:path";function He(e){return{name:"velu-site",resolveId(t){return t===qe?Je:null},load(t){return t!==Je?null:Bt(e)}}}function Bt(e){let t={};try{t=JSON.parse(Vt.readFileSync(Z.join(e,"velu.json"),"utf-8"))}catch{}let o=V(t.navigation??{}),{pageEntries:n,warnings:r}=B(o,e);for(let c of r)console.warn(`[velu] nav: ${c}`);let i=Y(t.logo),a={name:t.name??Z.basename(e),logo:i?{light:ne,dark:oe,href:i.href,mono:i.light===i.dark}:null,footer:ve(t.footer),navbar:ye(t.navbar)},s=[],l=[],u=0;for(let c of n)if(c.exists){let f=JSON.stringify(c.fileAbsPath.split(Z.sep).join("/")),A=JSON.stringify(Z.relative(e,c.fileAbsPath).split(Z.sep).join("/")),p=`__p${u++}`;s.push(`import * as ${p} from ${f};`),l.push(` ${JSON.stringify(c.url)}: { Component: ${p}.default, frontmatter: ${p}.frontmatter, toc: ${p}.toc, relPath: ${A} },`)}else l.push(` ${JSON.stringify(c.url)}: { missing: true },`);return["// AUTO-GENERATED by vite-plugin-velu-site. Do not edit.",s.join(`
5
+ `),"","export const pages = {",l.join(`
6
+ `),"};",`export const navigation = ${JSON.stringify(o)};`,`export const site = ${JSON.stringify(a)};`,""].join(`
7
+ `)}var qe,Je,De=k(()=>{W();se();re();qe="virtual:velu-site",Je="\0"+qe});function v(e){return{category:"render-crash",severity:"error",file:null,line:null,column:null,frame:null,title:"Something went wrong",detail:"",hint:"",suggestion:null,...e}}function Jt(e,t){if(e=String(e),t=String(t),e===t)return 0;if(!e.length)return t.length;if(!t.length)return e.length;let o=Array.from({length:t.length+1},(r,i)=>i),n=new Array(t.length+1);for(let r=1;r<=e.length;r++){n[0]=r;for(let i=1;i<=t.length;i++){let a=e[r-1]===t[i-1]?0:1;n[i]=Math.min(n[i-1]+1,o[i]+1,o[i-1]+a)}[o,n]=[n,o]}return o[t.length]}function le(e,t=[]){if(!e)return null;let o=String(e).toLowerCase(),n=null,r=1/0;for(let a of t){let s=Jt(o,String(a).toLowerCase());s<r&&(r=s,n=a)}let i=Math.min(3,Math.max(2,Math.floor(o.length/4)));return r<=i?n:null}function qt(e,t){return e==="warning"?P(t,j.yellow,"\u26A0"):e==="info"?P(t,j.cyan,"\u2139"):P(t,j.red,"\u2716")}function we(e,{color:t=!1}={}){let o=be[e.category]||e.category,n=e.line?`${e.line}${e.column?`:${e.column}`:""}`:"",r=e.file?`${e.file}${n?`:${n}`:""}`:n,i=[];if(i.push(`${qt(e.severity,t)} ${P(t,j.bold,o)}`+(r?` ${P(t,j.dim,r)}`:"")),e.title&&i.push(` ${e.title}`),e.detail&&e.detail!==e.title&&i.push(` ${P(t,j.dim,e.detail)}`),e.frame)for(let a of String(e.frame).split(`
8
+ `))i.push(` ${a}`);return e.suggestion&&i.push(` ${P(t,j.cyan,`Did you mean <${e.suggestion}>?`)}`),e.hint&&i.push(` ${P(t,j.dim,`\u2192 ${e.hint}`)}`),i.join(`
9
+ `)}function ae(e,{color:t=!1}={}){if(!e.length)return P(t,j.cyan,"\u2713 No issues found.");let o=new Map;for(let s of e){let l=s.file||"velu.json";o.has(l)||o.set(l,[]),o.get(l).push(s)}let n=[];for(let[s,l]of o){n.push(P(t,j.bold,s));for(let u of l)n.push(we({...u,file:null},{color:t}).replace(/^/gm," "));n.push("")}let r=e.filter(s=>s.severity!=="warning"&&s.severity!=="info").length,i=e.length-r,a=`${r} error${r===1?"":"s"}`+(i?`, ${i} warning${i===1?"":"s"}`:"");return n.push(P(t,r?j.red:j.yellow,a)),n.join(`
10
+ `)}var be,j,P,F=k(()=>{be={"mdx-syntax":"MDX syntax",frontmatter:"Frontmatter","unknown-component":"Unknown component","invalid-props":"Invalid options","render-crash":"Render error","config-json":"velu.json","config-schema":"velu.json","config-nav":"Navigation","config-asset":"Asset"};j={reset:"\x1B[0m",red:"\x1B[31m",yellow:"\x1B[33m",cyan:"\x1B[36m",dim:"\x1B[2m",bold:"\x1B[1m"},P=(e,t,o)=>e?`${t}${o}${j.reset}`:o});function Ht(e){let t=e,o=0;for(;t&&t.cause&&o<3&&!(t.loc||t.line||/`[^`]+` to be defined/.test(t.message||""));)t=t.cause,o+=1;return t||e}function ke(e,t){if(!e)return null;let o=Ye(e).replace(/[?#].*$/,""),n=t?Ye(t).replace(/\/+$/,""):"";return n&&o.toLowerCase().startsWith(n.toLowerCase())?o.slice(n.length).replace(/^\/+/,""):!/^([a-zA-Z]:\/|\/)/.test(o)&&!/node_modules/.test(o)?o:null}function Dt(e){return e?String(e).replace(/([a-zA-Z]:\\|\/)[^\s'"()]+/g,t=>/node_modules|[\\/]vite[\\/]|@veluai|velu-cli|velu-ui/.test(t)?"\u2026":t).trim():""}function Yt(e){let t=/\((\d+):(\d+)(?:-\d+:\d+)?\)\s*$/.exec(String(e||""));return t?{line:Number(t[1]),column:Number(t[2])}:null}function Kt(e){return String(e||"").replace(/\s*\(\d+:\d+(?:-\d+:\d+)?\)\s*$/,"").trim()}function Zt(e,t){if(!e||!t)return null;let o=String(e);return/node_modules|[\\/]vite[\\/]|@veluai[\\/]/.test(o)?null:o}function X(e,t={}){let{projectDir:o="",knownComponents:n=[],file:r=null}=t,i=null;for(let C=e,S=0;C&&S<6;C=C.cause,S+=1)if(C.veluIssue){i=C.veluIssue;break}if(i)return v({...i,file:i.file||r});let a=Ht(e)||{},s=a.message||String(e||""),l=/Expected component `([^`]+)` to be defined/.exec(s);if(l){let C=l[1],S=/referenced in your code at `(\d+):(\d+)/.exec(s),q=/\bin `([^`]+)`/.exec(s),y=q?ke(q[1],o):null;return v({category:"unknown-component",file:y||r,line:S?Number(S[1]):null,column:S?Number(S[2]):null,title:`<${C}> is not a built-in component`,detail:"",hint:"Components are built in \u2014 check the spelling and capitalization (you don\u2019t import them).",suggestion:le(C,n)})}let u=a.loc||(a.line!=null?{line:a.line,column:a.column,file:a.file}:null)||Yt(a.reason||s),c=a.id||u&&u.file||a.file||null,f=ke(c,o)||r,A=!!f&&ke(c,o)!=null,p=Zt(a.frame,A||!!r),L=Dt(Kt(a.reason||s)),d=Wt.test(s),N=a.source==="remark-frontmatter"||/\byaml\b|frontmatter/i.test(s)||u&&u.line===1&&/unexpected|expected/i.test(s)&&/---/.test(s);return u||d||p&&A?v({category:N?"frontmatter":"mdx-syntax",file:f,line:u?u.line:null,column:u?u.column:null,frame:p,title:L||(N?"Invalid frontmatter":"MDX syntax error"),detail:"",hint:N?"Check the YAML between the --- markers \u2014 indentation, quotes, and colons.":"Make sure every <Component> has a matching closing tag and that { } braces are balanced."}):/\.map is not a function|is not iterable|reading '|undefined \(reading/i.test(s)?v({category:"invalid-props",file:f,title:"A component received an invalid value",detail:L,hint:"Check the props/options you passed to a component on this page."}):v({category:"render-crash",file:f,title:"This page failed to render",detail:L,hint:"Set VELU_DEBUG=1 for the full stack trace."})}var Ye,Wt,Se=k(()=>{F();Ye=e=>String(e).replace(/\\/g,"/");Wt=/closing tag|unexpected closing|could not parse (?:expression|import|export)|unexpected character|unexpected end of|expected a closing|expected the closing|unexpected `|in expression|misnested|before name/i});function Xt(e){let t=be[e.category]||e.category,o=e.file?`${e.file}${e.line?`:${e.line}${e.column?`:${e.column}`:""}`:""}`:"",n=[];return n.push('<div class="velu-err-card">'),n.push('<div class="velu-err-head">'),n.push(`<span class="velu-err-pill">${T(t)}</span>`),n.push("</div>"),e.title&&n.push(`<div class="velu-err-title">${T(e.title)}</div>`),o&&n.push(`<a class="velu-err-loc" title="${T(o)}">${T(o)}</a>`),e.detail&&n.push(`<div class="velu-err-detail">${T(e.detail)}</div>`),e.frame&&n.push(`<pre class="velu-err-frame">${T(e.frame)}</pre>`),e.suggestion&&n.push(`<div class="velu-err-suggest">Did you mean <code>&lt;${T(e.suggestion)}&gt;</code>?</div>`),e.hint&&n.push(`<div class="velu-err-hint">${T(e.hint)}</div>`),n.push("</div>"),n.join("")}function $e(e,t={}){let o=Array.isArray(e)?e:[e],n=t.title||(o.length>1?`${o.length} problems to fix`:"There\u2019s a problem to fix"),r=o.map(Xt).join(`
11
11
  `);return`<!doctype html>
12
12
  <html lang="en">
13
13
  <head>
@@ -74,20 +74,20 @@ var ut=Object.defineProperty;var k=(e,t)=>()=>(e&&(t=e(e=0)),t);var ce=(e,t)=>{f
74
74
  <body>
75
75
  <div class="velu-err-wrap">
76
76
  <div class="velu-err-brand">velu</div>
77
- <h1 class="velu-err-h">${O(n)}</h1>
77
+ <h1 class="velu-err-h">${T(n)}</h1>
78
78
  ${r}
79
79
  <div class="velu-err-foot">Fix the file and save \u2014 this page reloads automatically.</div>
80
80
  </div>
81
81
  </body>
82
- </html>`}var O,Ve=k(()=>{F();O=e=>String(e??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")});var I,ve=k(()=>{I=["Callout","Card","CardGroup","Accordion","AccordionGroup","Columns","Field","Prompt","Steps","Step","Tree","Folder","File","Image","CodeBlock","CodeGroup","MethodBadge","ApiPath","TryItBar","ApiField","ApiClient","ApiSidebar"]});import ye from"node:fs";import re from"node:path";import{fileURLToPath as qt}from"node:url";import Dt from"iso-639-1";function Z(e,t){if(!t)return null;let o=e.indexOf(`"${t}"`);return o===-1?null:e.slice(0,o).split(`
83
- `).length}function Wt(e,t){return t==null||t<0?null:e.slice(0,t).split(`
84
- `).length}function Be(e,t){if(!t)return null;let o=e.indexOf(`"${t}"`);return o===-1?null:e.slice(0,o).split(`
85
- `).length}function Kt(e,t,o){let n=[];return a(e,t,"",null),n;function r(l){if(l&&l.$ref){let u=l.$ref.replace(/^#\//,"").split("/"),c=o;for(let f of u)c=c?.[f];return c||{}}return l}function s(l){return Array.isArray(l)?"array":l===null?"null":typeof l}function i(l,u){let c=n.length;a(l,u,"",null,!0);let f=n.length===c;return f||(n.length=c),f}function a(l,u,c,f,_){let d=r(u);if(d){if(d.oneOf){d.oneOf.some(m=>i(l,m))||n.push({path:c,key:f,message:"value does not match any allowed shape"});return}if(d.type&&s(l)!==d.type){n.push({path:c,key:f,message:`expected ${d.type}, got ${s(l)}`});return}if(d.type==="string"&&d.minLength!=null&&l.length<d.minLength&&n.push({path:c,key:f,message:"must not be empty"}),d.type==="object"&&s(l)==="object"){for(let m of d.required||[])m in l||n.push({path:c?`${c}.${m}`:m,key:m,message:`missing required "${m}"`});let j=d.properties||{};if(d.additionalProperties===!1)for(let m of Object.keys(l))m in j||n.push({path:c?`${c}.${m}`:m,key:m,message:`unknown field "${m}"`});for(let[m,L]of Object.entries(j))m in l&&a(l[m],L,c?`${c}.${m}`:m,m,_)}d.type==="array"&&Array.isArray(l)&&d.items&&l.forEach((j,m)=>a(j,d.items,`${c}[${m}]`,f,_))}}}function Zt(e){if(typeof e!="string")return!1;let t=e.trim();return/^#([0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(t)||/^(rgb|rgba|hsl|hsla|color|oklch|lab|lch)\(/i.test(t)||/^[a-z][a-z0-9-]*$/i.test(t)}function ie(e){let t=[],o=re.join(e,"velu.json"),n;try{n=ye.readFileSync(o,"utf-8")}catch(i){return i.code==="ENOENT"?t.push(h({category:"config-json",file:"velu.json",title:"No velu.json found",hint:"Run `velu init <name>` to scaffold a project."})):t.push(h({category:"config-json",file:"velu.json",title:"Could not read velu.json"})),t}let r;try{r=JSON.parse(n)}catch(i){let a=/position (\d+)/.exec(i.message||""),l=a?Wt(n,Number(a[1])):null;return t.push(h({category:"config-json",file:"velu.json",line:l,title:"velu.json is not valid JSON",detail:String(i.message||"").replace(/\s+in JSON.*$/,""),hint:"Check for a trailing comma, a missing quote, or an unclosed brace."})),t}let s=null;try{s=JSON.parse(ye.readFileSync(Ht,"utf-8"))}catch{}if(s)for(let i of Kt(r,s,s)){let a=/^unknown field/.test(i.message);t.push(h({category:"config-schema",severity:a?"warning":"error",file:"velu.json",line:Z(n,i.key),title:i.path?`${i.path}: ${i.message}`:i.message,hint:a?"Remove it, or check the spelling against the velu.json schema.":""}))}for(let i of["primary","light","dark"]){let a=r.colors?.[i];a!=null&&!Zt(a)&&t.push(h({category:"config-schema",severity:"warning",file:"velu.json",line:Z(n,i),title:`colors.${i}: "${a}" doesn\u2019t look like a CSS color`,hint:"Use a hex value like #dc143c, an rgb()/hsl() function, or a CSS color name."}))}if(typeof r.favicon=="string"&&r.favicon.trim()){let i=re.resolve(e,r.favicon.replace(/^\/+/,""));ye.existsSync(i)||t.push(h({category:"config-asset",file:"velu.json",line:Z(n,"favicon"),title:`favicon not found: ${r.favicon}`,hint:"The path is relative to the project root."}))}if(r.navigation&&typeof r.navigation=="object"){let i;try{i=B(r.navigation)}catch(a){t.push(h({category:"config-nav",file:"velu.json",line:Z(n,"navigation"),title:"navigation could not be processed",detail:String(a.message||"")})),i=null}if(i){let{pageEntries:a,warnings:l}=G(i,e);for(let u of l){let c=/not found$/.test(u),f=/page "([^"]+)"/.exec(u),_=f?Be(n,f[1]):null;t.push(h({category:"config-nav",severity:c?"error":"warning",file:"velu.json",line:_,title:u,hint:c?"Create the .mdx file, or remove the page from navigation.":""}))}Xt(i,n,t)}}return t}function Xt(e,t,o){let n=new Set,r=s=>{if(!(!s||typeof s!="object")){if(s.language&&!n.has(s.language)){n.add(s.language);let i=String(s.language).toLowerCase();Dt.validate(i)||o.push(h({category:"config-nav",severity:"warning",file:"velu.json",line:Be(t,s.language)||Z(t,"language"),title:`"${s.language}" is not a valid ISO 639-1 language code`,hint:'Use a two-letter code like "en", "fr", or "ja".'}))}for(let i of Object.values(s))Array.isArray(i)?i.forEach(r):i&&typeof i=="object"&&r(i)}};r(e)}var Yt,Ht,xe=k(()=>{F();W();ee();Yt=re.dirname(qt(import.meta.url)),Ht=re.resolve(Yt,"..","..","schema","velu.schema.json")});import Qt from"node:fs/promises";import en from"node:fs";import R from"node:path";import{fileURLToPath as tn,pathToFileURL as nn}from"node:url";import{compile as on}from"@mdx-js/mdx";import rn from"remark-frontmatter";import sn from"remark-mdx-frontmatter";import an from"remark-gfm";import ln from"rehype-slug";import{visit as cn}from"unist-util-visit";function we(){if(se)return se;let e=R.dirname(tn(import.meta.url)),t=[R.resolve(e,"..","runtime","velu-ui"),R.resolve(e,"..","..","runtime","velu-ui"),R.resolve(e,"..","..","..","velu-ui","src")],o=null;for(let n of t){let r=R.join(n,"lib","component-schemas.js");if(en.existsSync(r)){o=r;break}}return se=o?import(nn(o).href).then(n=>n.validateProps).catch(()=>null):Promise.resolve(null),se}function un(e){let t={};for(let o of e.attributes||[])o.type==="mdxJsxAttribute"&&(o.value==null?t[o.name]=!0:typeof o.value=="string"&&(t[o.name]=o.value));return t}function fn(e,t){return()=>o=>{cn(o,n=>{if((n.type==="mdxJsxFlowElement"||n.type==="mdxJsxTextElement")&&typeof n.name=="string"&&/^[A-Z]/.test(n.name)&&!n.name.includes(".")&&e.push({name:n.name,props:un(n),line:n.position?.start?.line??null,column:n.position?.start?.column??null}),n.type==="mdxjsEsm"&&typeof n.value=="string")for(let r of n.value.matchAll(/\b([A-Z][A-Za-z0-9_]*)\b/g))t.add(r[1])})}}async function pn(e,t){let o=[],n;try{n=await Qt.readFile(t.fileAbsPath,"utf-8")}catch{return o}let r=R.relative(e,t.fileAbsPath).split(R.sep).join("/"),s=[],i=new Set;try{await on({value:n,path:t.fileAbsPath},{remarkPlugins:[rn,sn,an,Y,fn(s,i)],rehypePlugins:[ln],providerImportSource:"@mdx-js/react"})}catch(c){return o.push(K(c,{projectDir:e,knownComponents:I,file:r})),o}let a=new Set(I),l=await we(),u=new Set;for(let c of s)if(!i.has(c.name)){if(!a.has(c.name)){if(u.has(c.name))continue;u.add(c.name),o.push(h({category:"unknown-component",file:r,line:c.line,column:c.column,title:`<${c.name}> is not a built-in component`,hint:"Components are built in \u2014 check the spelling and capitalization (you don\u2019t import them).",suggestion:ne(c.name,I)}));continue}if(l){let f=l(c.name,c.props,{checkRequired:!1});f&&o.push(h({...f,file:r,line:c.line,column:c.column}))}}return o}async function Ge(e,t){let o=t.filter(a=>a.exists),n=[],r=8,s=0;async function i(){for(;s<o.length;){let a=o[s++],l=await pn(e,a);for(let u of l)n.push(u)}}return await Promise.all(Array.from({length:Math.min(r,o.length)},i)),n}var se,be=k(()=>{ue();F();ge();ve()});import{visit as mn}from"unist-util-visit";function dn(e,t){let o=Je(e).replace(/[?#].*$/,""),n=Je(t).replace(/\/+$/,"");return n&&o.toLowerCase().startsWith(n.toLowerCase())?o.slice(n.length).replace(/^\/+/,""):null}function qe(e,t){return()=>(o,n)=>{typeof e=="function"&&mn(o,r=>{if(r.type!=="mdxJsxFlowElement"&&r.type!=="mdxJsxTextElement"||typeof r.name!="string"||!/^[A-Z]/.test(r.name)||r.name.includes("."))return;let s={};for(let a of r.attributes||[])a.type==="mdxJsxAttribute"&&(a.value==null?s[a.name]=!0:typeof a.value=="string"&&(s[a.name]=a.value));let i=e(r.name,s,{checkRequired:!1});if(i){let a=new Error(i.title);throw a.veluIssue={...i,file:dn(n?.path,t),line:r.position?.start?.line??null,column:r.position?.start?.column??null},a.loc={line:a.veluIssue.line,column:a.veluIssue.column},a}})}}var Je,De=k(()=>{Je=e=>String(e||"").replace(/\\/g,"/")});var Qe={};ce(Qe,{startDevServer:()=>zn});import Ye from"node:fs/promises";import M from"node:fs";import gn from"node:http";import{spawn as hn}from"node:child_process";import x from"node:path";import{fileURLToPath as vn}from"node:url";import{createRequire as yn}from"node:module";import xn from"chokidar";import wn from"express";import bn from"@tailwindcss/vite";import kn from"@vitejs/plugin-react";import Sn from"@mdx-js/rollup";import $n from"remark-frontmatter";import jn from"remark-mdx-frontmatter";import An from"remark-gfm";import Pn from"rehype-slug";import{createServer as Cn,searchForWorkspaceRoot as En}from"vite";function Ln(e){return`<link rel="stylesheet" href="${`https://fonts.googleapis.com/css2?family=${encodeURIComponent(e).replace(/%20/g,"+")}:wght@300;400;500;600;700&display=swap`}" />`}function We(e,t){let o=me(e,{color:!!process.stderr.isTTY});console.error(`
82
+ </html>`}var T,Ke=k(()=>{F();T=e=>String(e??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")});var I,je=k(()=>{I=["Callout","Card","CardGroup","Accordion","AccordionGroup","Columns","Field","Prompt","Steps","Step","Tree","Folder","File","Image","CodeBlock","CodeGroup","MethodBadge","ApiPath","TryItBar","ApiField","ApiClient","ApiSidebar"]});import ce from"node:fs";import ee from"node:path";import{fileURLToPath as Qt}from"node:url";import en from"iso-639-1";function Q(e,t){if(!t)return null;let o=e.indexOf(`"${t}"`);return o===-1?null:e.slice(0,o).split(`
83
+ `).length}function on(e,t){return t==null||t<0?null:e.slice(0,t).split(`
84
+ `).length}function Ae(e,t){if(!t)return null;let o=e.indexOf(`"${t}"`);return o===-1?null:e.slice(0,o).split(`
85
+ `).length}function rn(e,t,o){let n=[];return s(e,t,"",null),n;function r(l){if(l&&l.$ref){let u=l.$ref.replace(/^#\//,"").split("/"),c=o;for(let f of u)c=c?.[f];return c||{}}return l}function i(l){return Array.isArray(l)?"array":l===null?"null":typeof l}function a(l,u){let c=n.length;s(l,u,"",null,!0);let f=n.length===c;return f||(n.length=c),f}function s(l,u,c,f,A){let p=r(u);if(p){if(p.oneOf){p.oneOf.some(d=>a(l,d))||n.push({path:c,key:f,message:"value does not match any allowed shape"});return}if(p.type&&i(l)!==p.type){n.push({path:c,key:f,message:`expected ${p.type}, got ${i(l)}`});return}if(p.type==="string"&&p.minLength!=null&&l.length<p.minLength&&n.push({path:c,key:f,message:"must not be empty"}),p.type==="object"&&i(l)==="object"){for(let d of p.required||[])d in l||n.push({path:c?`${c}.${d}`:d,key:d,message:`missing required "${d}"`});let L=p.properties||{};if(p.additionalProperties===!1)for(let d of Object.keys(l))d in L||n.push({path:c?`${c}.${d}`:d,key:d,message:`unknown field "${d}"`});for(let[d,N]of Object.entries(L))d in l&&s(l[d],N,c?`${c}.${d}`:d,d,A)}p.type==="array"&&Array.isArray(l)&&p.items&&l.forEach((L,d)=>s(L,p.items,`${c}[${d}]`,f,A))}}}function sn(e){if(typeof e!="string")return!1;let t=e.trim();return/^#([0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(t)||/^(rgb|rgba|hsl|hsla|color|oklch|lab|lch)\(/i.test(t)||/^[a-z][a-z0-9-]*$/i.test(t)}function ue(e){let t=[],o=ee.join(e,"velu.json"),n;try{n=ce.readFileSync(o,"utf-8")}catch(s){return s.code==="ENOENT"?t.push(v({category:"config-json",file:"velu.json",title:"No velu.json found",hint:"Run `velu init <name>` to scaffold a project."})):t.push(v({category:"config-json",file:"velu.json",title:"Could not read velu.json"})),t}let r;try{r=JSON.parse(n)}catch(s){let l=/position (\d+)/.exec(s.message||""),u=l?on(n,Number(l[1])):null;return t.push(v({category:"config-json",file:"velu.json",line:u,title:"velu.json is not valid JSON",detail:String(s.message||"").replace(/\s+in JSON.*$/,""),hint:"Check for a trailing comma, a missing quote, or an unclosed brace."})),t}let i=null;try{i=JSON.parse(ce.readFileSync(nn,"utf-8"))}catch{}if(i)for(let s of rn(r,i,i)){let l=/^unknown field/.test(s.message);t.push(v({category:"config-schema",severity:l?"warning":"error",file:"velu.json",line:Q(n,s.key),title:s.path?`${s.path}: ${s.message}`:s.message,hint:l?"Remove it, or check the spelling against the velu.json schema.":""}))}for(let s of["primary","light","dark"]){let l=r.colors?.[s];l!=null&&!sn(l)&&t.push(v({category:"config-schema",severity:"warning",file:"velu.json",line:Q(n,s),title:`colors.${s}: "${l}" doesn\u2019t look like a CSS color`,hint:"Use a hex value like #dc143c, an rgb()/hsl() function, or a CSS color name."}))}if(typeof r.favicon=="string"&&r.favicon.trim()){let s=ee.resolve(e,r.favicon.replace(/^\/+/,""));ce.existsSync(s)||t.push(v({category:"config-asset",file:"velu.json",line:Q(n,"favicon"),title:`favicon not found: ${r.favicon}`,hint:"The path is relative to the project root."}))}let a=Y(r.logo);if(a){let s=new Map;for(let l of["light","dark"]){let u=a[l];u&&s.set(u,[...s.get(u)||[],l])}for(let[l,u]of s)if(!ce.existsSync(ee.resolve(e,l.replace(/^\/+/,"")))){let c=u.length===2?"logo":`logo (${u[0]})`;t.push(v({category:"config-asset",file:"velu.json",line:Ae(n,l),title:`${c} not found: ${l}`,hint:"The path is relative to the project root."}))}}if(r.navigation&&typeof r.navigation=="object"){let s;try{s=V(r.navigation)}catch(l){t.push(v({category:"config-nav",file:"velu.json",line:Q(n,"navigation"),title:"navigation could not be processed",detail:String(l.message||"")})),s=null}if(s){let{pageEntries:l,warnings:u}=B(s,e);for(let c of u){let f=/not found$/.test(c),A=/page "([^"]+)"/.exec(c),p=A?Ae(n,A[1]):null;t.push(v({category:"config-nav",severity:f?"error":"warning",file:"velu.json",line:p,title:c,hint:f?"Create the .mdx file, or remove the page from navigation.":""}))}ln(s,n,t)}}return t}function ln(e,t,o){let n=new Set,r=i=>{if(!(!i||typeof i!="object")){if(i.language&&!n.has(i.language)){n.add(i.language);let a=String(i.language).toLowerCase();en.validate(a)||o.push(v({category:"config-nav",severity:"warning",file:"velu.json",line:Ae(t,i.language)||Q(t,"language"),title:`"${i.language}" is not a valid ISO 639-1 language code`,hint:'Use a two-letter code like "en", "fr", or "ja".'}))}for(let a of Object.values(i))Array.isArray(a)?a.forEach(r):a&&typeof a=="object"&&r(a)}};r(e)}var tn,nn,Le=k(()=>{F();W();se();re();tn=ee.dirname(Qt(import.meta.url)),nn=ee.resolve(tn,"..","..","schema","velu.schema.json")});import an from"node:fs/promises";import cn from"node:fs";import R from"node:path";import{fileURLToPath as un,pathToFileURL as fn}from"node:url";import{compile as pn}from"@mdx-js/mdx";import mn from"remark-frontmatter";import dn from"remark-mdx-frontmatter";import gn from"remark-gfm";import hn from"rehype-slug";import{visit as vn}from"unist-util-visit";function Oe(){if(fe)return fe;let e=R.dirname(un(import.meta.url)),t=[R.resolve(e,"..","runtime","velu-ui"),R.resolve(e,"..","..","runtime","velu-ui"),R.resolve(e,"..","..","..","velu-ui","src")],o=null;for(let n of t){let r=R.join(n,"lib","component-schemas.js");if(cn.existsSync(r)){o=r;break}}return fe=o?import(fn(o).href).then(n=>n.validateProps).catch(()=>null):Promise.resolve(null),fe}function yn(e){let t={};for(let o of e.attributes||[])o.type==="mdxJsxAttribute"&&(o.value==null?t[o.name]=!0:typeof o.value=="string"&&(t[o.name]=o.value));return t}function xn(e,t){return()=>o=>{vn(o,n=>{if((n.type==="mdxJsxFlowElement"||n.type==="mdxJsxTextElement")&&typeof n.name=="string"&&/^[A-Z]/.test(n.name)&&!n.name.includes(".")&&e.push({name:n.name,props:yn(n),line:n.position?.start?.line??null,column:n.position?.start?.column??null}),n.type==="mdxjsEsm"&&typeof n.value=="string")for(let r of n.value.matchAll(/\b([A-Z][A-Za-z0-9_]*)\b/g))t.add(r[1])})}}async function bn(e,t){let o=[],n;try{n=await an.readFile(t.fileAbsPath,"utf-8")}catch{return o}let r=R.relative(e,t.fileAbsPath).split(R.sep).join("/"),i=[],a=new Set;try{await pn({value:n,path:t.fileAbsPath},{remarkPlugins:[mn,dn,gn,D,xn(i,a)],rehypePlugins:[hn],providerImportSource:"@mdx-js/react"})}catch(c){return o.push(X(c,{projectDir:e,knownComponents:I,file:r})),o}let s=new Set(I),l=await Oe(),u=new Set;for(let c of i)if(!a.has(c.name)){if(!s.has(c.name)){if(u.has(c.name))continue;u.add(c.name),o.push(v({category:"unknown-component",file:r,line:c.line,column:c.column,title:`<${c.name}> is not a built-in component`,hint:"Components are built in \u2014 check the spelling and capitalization (you don\u2019t import them).",suggestion:le(c.name,I)}));continue}if(l){let f=l(c.name,c.props,{checkRequired:!1});f&&o.push(v({...f,file:r,line:c.line,column:c.column}))}}return o}async function We(e,t){let o=t.filter(s=>s.exists),n=[],r=8,i=0;async function a(){for(;i<o.length;){let s=o[i++],l=await bn(e,s);for(let u of l)n.push(u)}}return await Promise.all(Array.from({length:Math.min(r,o.length)},a)),n}var fe,_e=k(()=>{ge();F();Se();je()});import{visit as wn}from"unist-util-visit";function kn(e,t){let o=Ze(e).replace(/[?#].*$/,""),n=Ze(t).replace(/\/+$/,"");return n&&o.toLowerCase().startsWith(n.toLowerCase())?o.slice(n.length).replace(/^\/+/,""):null}function Xe(e,t){return()=>(o,n)=>{typeof e=="function"&&wn(o,r=>{if(r.type!=="mdxJsxFlowElement"&&r.type!=="mdxJsxTextElement"||typeof r.name!="string"||!/^[A-Z]/.test(r.name)||r.name.includes("."))return;let i={};for(let s of r.attributes||[])s.type==="mdxJsxAttribute"&&(s.value==null?i[s.name]=!0:typeof s.value=="string"&&(i[s.name]=s.value));let a=e(r.name,i,{checkRequired:!1});if(a){let s=new Error(a.title);throw s.veluIssue={...a,file:kn(n?.path,t),line:r.position?.start?.line??null,column:r.position?.start?.column??null},s.loc={line:s.veluIssue.line,column:s.veluIssue.column},s}})}}var Ze,Qe=k(()=>{Ze=e=>String(e||"").replace(/\\/g,"/")});var st={};de(st,{startDevServer:()=>Yn});import et from"node:fs/promises";import M from"node:fs";import Sn from"node:http";import{spawn as $n}from"node:child_process";import w from"node:path";import{fileURLToPath as jn}from"node:url";import{createRequire as An}from"node:module";import Ln from"chokidar";import On from"express";import _n from"@tailwindcss/vite";import Pn from"@vitejs/plugin-react";import Cn from"@mdx-js/rollup";import Tn from"remark-frontmatter";import En from"remark-mdx-frontmatter";import Nn from"remark-gfm";import Fn from"rehype-slug";import{createServer as In,searchForWorkspaceRoot as Rn}from"vite";function Vn(e){return`<link rel="stylesheet" href="${`https://fonts.googleapis.com/css2?family=${encodeURIComponent(e).replace(/%20/g,"+")}:wght@300;400;500;600;700&display=swap`}" />`}function nt(e,t){let o=we(e,{color:!!process.stderr.isTTY});console.error(`
86
86
  `+o+`
87
- `),process.env.VELU_DEBUG&&t&&console.error(t)}function Fn(){try{return JSON.parse(M.readFileSync(x.join(J,"package.json"),"utf8")).version||"unknown"}catch{return"unknown"}}function In(e,t){let o=x.resolve(e,".velu"),n=x.resolve(o,".velu-version"),r=null;try{r=M.readFileSync(n,"utf8").trim()}catch{}if(r!==t){if(r!==null)try{M.rmSync(x.join(o,"vite-cache"),{recursive:!0,force:!0}),console.log("[velu] new version detected \u2014 refreshing the preview cache\u2026")}catch{}try{M.mkdirSync(o,{recursive:!0}),M.writeFileSync(n,t)}catch{}}}async function Ke(e){try{return await Ce(e)}catch{return fe({},x.basename(e))}}async function Ze(e,t){let o=await e.moduleGraph.getModuleByUrl(t,!0),n=new Map,r=new Set;async function s(i){if(!(!i||r.has(i.url))){if(r.add(i.url),i.id&&_n.test(i.id)){let a=i.url.includes("?")?"&":"?",l=await e.transformRequest(`${i.url}${a}direct`);l?.code&&n.set(i.url,l.code)}for(let a of i.importedModules)await s(a)}}return await s(o),[...n.values()].join(`
88
- `)}function Un(e,t){let o=()=>{process.stdout.isTTY&&!process.env.VELU_NO_BELL&&process.stdout.write("\x07")},n=process.stdout.isTTY,r=f=>n&&process.stdout.write(`\x1B]${f}\x1B\\`),s=()=>{r(`0;\u23F3 ${t}`),r("9;4;3;0")},i=()=>{r(`0;${t}`),r("9;4;0;0")};if(n&&process.once("exit",()=>r("9;4;0;0")),s(),!e)return console.log("Starting Velu\u2026"),{ready:f=>{console.log(`Velu ready \u2192 ${f}`),i(),o()}};let a="\x1B[38;2;220;20;60m",l="\x1B[0m";console.log("");for(let f of Rn)console.log(`${a}${f}${l}`);console.log("");let u=null;try{u=hn(process.execPath,["-e",Mn],{stdio:["ignore","inherit","ignore"]}),u.on("error",()=>{})}catch{}let c=()=>{if(u){try{u.kill()}catch{}u=null}};return process.once("exit",c),{ready:f=>{c(),process.stdout.write("\x1B[2K\r"),console.log(` \x1B[32m\u2713${l} Velu ready \u2192 \x1B[36m${f}${l}`),i(),o()}}}async function zn(e){let t=!!process.env.VELU_PROFILE,o=performance.now(),n=(w,p)=>t&&console.log(`[velu-profile] ${w}: ${(performance.now()-p).toFixed(0)}ms`),r=Un(!!process.stdout.isTTY&&!t,x.basename(e)),s=await Ke(e),i=a();function a(){let p=ie(e).filter(b=>b.severity==="error"&&(b.category==="config-json"||b.category==="config-schema"));return p.length&&console.error(`
89
- `+oe(p,{color:!!process.stderr.isTTY})+`
90
- `),p}let l=e;try{l=M.realpathSync(e)}catch{}let u=0,c=w=>{let p=performance.now();if(p-u<500)return;u=p;let b=K(w,{projectDir:l,knownComponents:I});We(b,w)};process.on("unhandledRejection",c),process.on("uncaughtException",c);let _=yn(import.meta.url).resolve("@mdx-js/react"),d=x.resolve(J,"runtime","velu-ui"),j=M.existsSync(d)?d:x.resolve(J,"..","velu-ui","src"),m=[{find:"@mdx-js/react",replacement:_},{find:/^velu-ui$/,replacement:x.join(j,"index.js")},{find:/^velu-ui\//,replacement:j+"/"}],L=Number(process.env.PORT)||8358,E=await we();In(e,Fn());let A=wn(),q=gn.createServer(A),v=await Cn({root:J,appType:"custom",logLevel:t?"info":"warn",plugins:[Me(e),Sn({remarkPlugins:[$n,jn,An,Y,qe(E,e)],rehypePlugins:[Pn],providerImportSource:"@mdx-js/react"}),kn(),bn()],cacheDir:x.resolve(e,".velu","vite-cache"),optimizeDeps:{include:["react","react-dom","react-dom/client","react-router-dom","@mdx-js/react","lucide-react","prism-react-renderer","iso-639-1","github-slugger"],exclude:["tailwindcss","@tailwindcss/oxide"],esbuildOptions:{loader:{".mdx":"jsx"}}},resolve:{alias:m,dedupe:["react","react-dom","@mdx-js/react"]},server:{middlewareMode:!0,fs:{allow:[En(J),e,l]},hmr:{server:q}}});n("vite server created",o),A.use(v.middlewares),A.get(He,async(w,p,b)=>{if(!s.favicon)return p.status(404).end();let g=x.resolve(e,s.favicon.replace(/^\//,""));p.sendFile(g,T=>{T&&p.status(404).end()})}),A.use("*",async(w,p,b)=>{let g=w.originalUrl,T=performance.now();if(i.length)return p.status(200).set({"Content-Type":"text/html"}).end(he(i,{title:"Fix velu.json to continue"}));try{let y=performance.now(),P=await Ye.readFile(Xe,"utf-8");P=await v.transformIndexHtml(g,P),n(`transformIndexHtml ${g}`,y);let U="/src/runtime/server-entry.jsx";y=performance.now();let{render:X}=await v.ssrLoadModule(U);n(`ssrLoadModule ${g} (1st req includes dep pre-bundle)`,y),y=performance.now();let S=await X(g);n(`render ${g}`,y),y=performance.now();let D=await Ze(v,U);n(`collectSsrCss ${g}`,y);let le=D?`<style data-velu-ssr>${D}</style>`:"",it=`<style data-velu-config>
91
- ${Ee(s)}
92
- </style>`,st=On.has(s.font.family)?"":Ln(s.font.family),at=s.favicon?`<link rel="icon" href="${He}" />`:"",lt=[le,it,st,at].filter(Boolean).join(`
93
- `),ct=P.replace("<!--app-title-->",Nn(s.name)).replace("<!--app-head-->",lt).replace("<!--app-html-->",S);p.status(200).set({"Content-Type":"text/html"}).end(ct),n(`TOTAL ${g}`,T)}catch(y){v.ssrFixStacktrace(y);let P=K(y,{projectDir:l,knownComponents:I});We(P,y),p.status(200).set({"Content-Type":"text/html"}).end(he([P]))}}),xn.watch(e,{ignoreInitial:!0,ignored:/(^|[\\/])(node_modules|\.git|\.velu)([\\/]|$)/,awaitWriteFinish:{stabilityThreshold:100,pollInterval:20}}).on("all",async(w,p)=>{console.log(`[velu-cli] content ${w}: ${x.relative(e,p)}`);let b=x.basename(p)==="velu.json",g=p.endsWith(".mdx"),T=b||g&&(w==="add"||w==="unlink");if(b&&(s=await Ke(e),i=a()),T)v.moduleGraph.invalidateAll();else if(g){let y=p.split(x.sep).join("/"),P=v.moduleGraph.getModulesByFile(y);if(P)for(let U of P)v.moduleGraph.invalidateModule(U)}v.ws.send({type:"full-reload"})});async function Se(w){let p=new Set,b=12,g=0,T=[],y=()=>g<b?(g+=1,Promise.resolve()):new Promise(S=>T.push(S)).then(()=>{g+=1}),P=()=>{g-=1,T.shift()?.()},U=S=>/[\\/]\.velu[\\/]vite-cache[\\/]|[\\/]deps[\\/]/.test(S)||/[\\/]node_modules[\\/]/.test(S)&&!/velu-ui/.test(S);async function X(S){if(!(!S||p.has(S)||U(S))){p.add(S);try{await y();try{await v.transformRequest(S,{ssr:!1})}finally{P()}let D=await v.moduleGraph.getModuleByUrl(S,!1);D?.importedModules?.size&&await Promise.all([...D.importedModules].map(le=>X(le.url)))}catch{}}}return await X(w),p.size}q.listen(L,async()=>{let w=performance.now(),p=0;try{let{render:b}=await v.ssrLoadModule("/src/runtime/server-entry.jsx");await Se("/src/runtime/client-entry.jsx"),typeof v.waitForRequestsIdle=="function"&&await v.waitForRequestsIdle();let[g]=await Promise.allSettled([Se("/src/runtime/client-entry.jsx"),b("/"),v.transformIndexHtml("/",await Ye.readFile(Xe,"utf-8")),Ze(v,"/src/runtime/server-entry.jsx")]).then(T=>T.map(y=>y.status==="fulfilled"?y.value:0));p=g}catch(b){t&&console.log("[velu-profile] warmup error:",b?.message)}n(`cold-start warm-up (${p} client modules)`,w),r.ready(`http://localhost:${L}`)})}var Tn,_n,On,He,Nn,J,Xe,Rn,Mn,et=k(()=>{ue();Te();Ue();ge();Ve();F();ve();xe();be();De();Tn=x.dirname(vn(import.meta.url)),_n=/\.(css|less|sass|scss|styl|stylus|pcss|postcss)(\?|$)/,On=new Set(["Google Sans Flex","Google Sans Code","Outfit"]),He="/@velu-favicon",Nn=e=>String(e).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;");J=x.resolve(Tn,".."),Xe=x.resolve(J,"src","template.html"),Rn=[" _ __ __","| | / /__ / /_ __","| | / / _ \\/ / / / /","| |/ / __/ / /_/ /","|___/\\___/_/\\__,_/"],Mn="const F=['\u280B','\u2819','\u2839','\u2838','\u283C','\u2834','\u2826','\u2827','\u2807','\u280F'],M=['Starting Velu','Setting up your preview','Preparing your docs','Warming things up','Almost ready'];let f=0,m=0;const t=Date.now();const C='\\x1b[38;2;220;20;60m',R='\\x1b[0m',D='\\x1b[2m';const d=()=>{const s=(Date.now()-t)/1000|0;process.stdout.write('\\x1b[2K\\r '+C+F[f]+R+' '+M[m]+'\\u2026 '+D+s+'s'+R);f=(f+1)%F.length;};d();setInterval(d,90);setInterval(()=>{if(m<M.length-1)m++;},4000);"});var nt={};ce(nt,{default:()=>Jn,runValidate:()=>tt});import Vn from"node:fs";import Bn from"node:path";function Gn(e){try{let t=JSON.parse(Vn.readFileSync(Bn.join(e,"velu.json"),"utf-8"));if(!t.navigation||typeof t.navigation!="object")return[];let o=B(t.navigation);return G(o,e).pageEntries}catch{return[]}}async function tt(e){let t=ie(e),o=Gn(e),n=await Ge(e,o),r=[...t,...n],s=!!process.stdout.isTTY;return console.log(oe(r,{color:s})),r.some(a=>a.severity!=="warning"&&a.severity!=="info")?1:0}var Jn,ot=k(()=>{W();ee();xe();be();F();Jn=tt});import ke from"node:path";import N from"node:process";import{readFileSync as qn}from"node:fs";var[,,rt,ae]=N.argv;function Dn(){try{return JSON.parse(qn(new URL("../package.json",import.meta.url),"utf8")).version}catch{return"unknown"}}async function Yn(){switch(rt){case"--version":case"-v":case"version":{console.log(Dn());break}case"init":{ae||(console.error("Usage: velu init <project-name>"),N.exit(1));let e=ae,t=ke.resolve(N.cwd(),e),{initProject:o}=await Promise.resolve().then(()=>(Ae(),je));await o(t,e);break}case"dev":{let e=ke.resolve(N.cwd(),ae??"."),{startDevServer:t}=await Promise.resolve().then(()=>(et(),Qe));await t(e);break}case"validate":{let e=ke.resolve(N.cwd(),ae??"."),{runValidate:t}=await Promise.resolve().then(()=>(ot(),nt)),o=await t(e);N.exit(o);break}default:console.log("Usage:"),console.log(" velu init <project-name> scaffold a new docs project"),console.log(" velu dev [dir] start the dev preview server"),console.log(" velu validate [dir] check velu.json + content for problems"),N.exit(rt?1:0)}}Yn().catch(e=>{console.error(N.env.VELU_DEBUG?e:`Error: ${e.message}`),N.exit(1)});
87
+ `),process.env.VELU_DEBUG&&t&&console.error(t)}function Bn(){try{return JSON.parse(M.readFileSync(w.join(J,"package.json"),"utf8")).version||"unknown"}catch{return"unknown"}}function Jn(e,t){let o=w.resolve(e,".velu"),n=w.resolve(o,".velu-version"),r=null;try{r=M.readFileSync(n,"utf8").trim()}catch{}if(r!==t){if(r!==null)try{M.rmSync(w.join(o,"vite-cache"),{recursive:!0,force:!0}),console.log("[velu] new version detected \u2014 refreshing the preview cache\u2026")}catch{}try{M.mkdirSync(o,{recursive:!0}),M.writeFileSync(n,t)}catch{}}}async function ot(e){try{return await Re(e)}catch{return xe({},w.basename(e))}}async function rt(e,t){let o=await e.moduleGraph.getModuleByUrl(t,!0),n=new Map,r=new Set;async function i(a){if(!(!a||r.has(a.url))){if(r.add(a.url),a.id&&Un.test(a.id)){let s=a.url.includes("?")?"&":"?",l=await e.transformRequest(`${a.url}${s}direct`);l?.code&&n.set(a.url,l.code)}for(let s of a.importedModules)await i(s)}}return await i(o),[...n.values()].join(`
88
+ `)}function Dn(e,t){let o=()=>{process.stdout.isTTY&&!process.env.VELU_NO_BELL&&process.stdout.write("\x07")},n=process.stdout.isTTY,r=f=>n&&process.stdout.write(`\x1B]${f}\x1B\\`),i=()=>{r(`0;\u23F3 ${t}`),r("9;4;3;0")},a=()=>{r(`0;${t}`),r("9;4;0;0")};if(n&&process.once("exit",()=>r("9;4;0;0")),i(),!e)return console.log("Starting Velu\u2026"),{ready:f=>{console.log(`Velu ready \u2192 ${f}`),a(),o()}};let s="\x1B[38;2;220;20;60m",l="\x1B[0m";console.log("");for(let f of qn)console.log(`${s}${f}${l}`);console.log("");let u=null;try{u=$n(process.execPath,["-e",Hn],{stdio:["ignore","inherit","ignore"]}),u.on("error",()=>{})}catch{}let c=()=>{if(u){try{u.kill()}catch{}u=null}};return process.once("exit",c),{ready:f=>{c(),process.stdout.write("\x1B[2K\r"),console.log(` \x1B[32m\u2713${l} Velu ready \u2192 \x1B[36m${f}${l}`),a(),o()}}}async function Yn(e){let t=!!process.env.VELU_PROFILE,o=performance.now(),n=(x,m)=>t&&console.log(`[velu-profile] ${x}: ${(performance.now()-m).toFixed(0)}ms`),r=Dn(!!process.stdout.isTTY&&!t,w.basename(e)),i=await ot(e),a=s();function s(){let m=ue(e).filter(h=>h.severity==="error"&&(h.category==="config-json"||h.category==="config-schema"));return m.length&&console.error(`
89
+ `+ae(m,{color:!!process.stderr.isTTY})+`
90
+ `),m}let l=e;try{l=M.realpathSync(e)}catch{}let u=0,c=x=>{let m=performance.now();if(m-u<500)return;u=m;let h=X(x,{projectDir:l,knownComponents:I});nt(h,x)};process.on("unhandledRejection",c),process.on("uncaughtException",c);let A=An(import.meta.url).resolve("@mdx-js/react"),p=w.resolve(J,"runtime","velu-ui"),L=M.existsSync(p)?p:w.resolve(J,"..","velu-ui","src"),d=[{find:"@mdx-js/react",replacement:A},{find:/^velu-ui$/,replacement:w.join(L,"index.js")},{find:/^velu-ui\//,replacement:L+"/"}],N=Number(process.env.PORT)||8358,C=await Oe();Jn(e,Bn());let S=On(),q=Sn.createServer(S),y=await In({root:J,appType:"custom",logLevel:t?"info":"warn",plugins:[He(e),Cn({remarkPlugins:[Tn,En,Nn,D,Xe(C,e)],rehypePlugins:[Fn],providerImportSource:"@mdx-js/react"}),Pn(),_n()],cacheDir:w.resolve(e,".velu","vite-cache"),optimizeDeps:{include:["react","react-dom","react-dom/client","react-router-dom","@mdx-js/react","lucide-react","prism-react-renderer","iso-639-1","github-slugger"],exclude:["tailwindcss","@tailwindcss/oxide","playwright","playwright-core"],esbuildOptions:{loader:{".mdx":"jsx"}}},resolve:{alias:d,dedupe:["react","react-dom","@mdx-js/react"]},server:{middlewareMode:!0,fs:{allow:[Rn(J),e,l]},hmr:{server:q}}});n("vite server created",o),S.use(y.middlewares),S.get(tt,async(x,m,h)=>{if(!i.favicon)return m.status(404).end();let g=w.resolve(e,i.favicon.replace(/^\//,""));m.sendFile(g,O=>{O&&m.status(404).end()})});let Ce=x=>(m,h)=>{let g=i.logo?.[x];if(!g)return h.status(404).end();h.sendFile(w.resolve(e,g.replace(/^\//,"")),O=>{O&&h.status(404).end()})};S.get(ne,Ce("light")),S.get(oe,Ce("dark")),S.use("*",async(x,m,h)=>{let g=x.originalUrl,O=performance.now();if(a.length)return m.status(200).set({"Content-Type":"text/html"}).end($e(a,{title:"Fix velu.json to continue"}));try{let b=performance.now(),_=await et.readFile(it,"utf-8");_=await y.transformIndexHtml(g,_),n(`transformIndexHtml ${g}`,b);let U="/src/runtime/server-entry.jsx";b=performance.now();let{render:te}=await y.ssrLoadModule(U);n(`ssrLoadModule ${g} (1st req includes dep pre-bundle)`,b),b=performance.now();let $=await te(g);n(`render ${g}`,b),b=performance.now();let H=await rt(y,U);n(`collectSsrCss ${g}`,b);let me=H?`<style data-velu-ssr>${H}</style>`:"",pt=`<style data-velu-config>
91
+ ${Me(i)}
92
+ </style>`,mt=zn.has(i.font.family)?"":Vn(i.font.family),dt=i.favicon?`<link rel="icon" href="${tt}" />`:"",gt=[me,pt,mt,dt].filter(Boolean).join(`
93
+ `),ht=_.replace("<!--app-title-->",Gn(i.name)).replace("<!--app-head-->",gt).replace("<!--app-html-->",$);m.status(200).set({"Content-Type":"text/html"}).end(ht),n(`TOTAL ${g}`,O)}catch(b){y.ssrFixStacktrace(b);let _=X(b,{projectDir:l,knownComponents:I});nt(_,b),m.status(200).set({"Content-Type":"text/html"}).end($e([_]))}}),Ln.watch(e,{ignoreInitial:!0,ignored:/(^|[\\/])(node_modules|\.git|\.velu)([\\/]|$)/,awaitWriteFinish:{stabilityThreshold:100,pollInterval:20}}).on("all",async(x,m)=>{console.log(`[velu-cli] content ${x}: ${w.relative(e,m)}`);let h=w.basename(m)==="velu.json",g=m.endsWith(".mdx"),O=h||g&&(x==="add"||x==="unlink");if(h&&(i=await ot(e),a=s()),O)y.moduleGraph.invalidateAll();else if(g){let b=m.split(w.sep).join("/"),_=y.moduleGraph.getModulesByFile(b);if(_)for(let U of _)y.moduleGraph.invalidateModule(U)}y.ws.send({type:"full-reload"})});async function Te(x){let m=new Set,h=12,g=0,O=[],b=()=>g<h?(g+=1,Promise.resolve()):new Promise($=>O.push($)).then(()=>{g+=1}),_=()=>{g-=1,O.shift()?.()},U=$=>/[\\/]\.velu[\\/]vite-cache[\\/]|[\\/]deps[\\/]/.test($)||/[\\/]node_modules[\\/]/.test($)&&!/velu-ui/.test($);async function te($){if(!(!$||m.has($)||U($))){m.add($);try{await b();try{await y.transformRequest($,{ssr:!1})}finally{_()}let H=await y.moduleGraph.getModuleByUrl($,!1);H?.importedModules?.size&&await Promise.all([...H.importedModules].map(me=>te(me.url)))}catch{}}}return await te(x),m.size}q.listen(N,async()=>{let x=performance.now(),m=0;try{let{render:h}=await y.ssrLoadModule("/src/runtime/server-entry.jsx");await Te("/src/runtime/client-entry.jsx"),typeof y.waitForRequestsIdle=="function"&&await y.waitForRequestsIdle();let[g]=await Promise.allSettled([Te("/src/runtime/client-entry.jsx"),h("/"),y.transformIndexHtml("/",await et.readFile(it,"utf-8")),rt(y,"/src/runtime/server-entry.jsx")]).then(O=>O.map(b=>b.status==="fulfilled"?b.value:0));m=g}catch(h){t&&console.log("[velu-profile] warmup error:",h?.message)}n(`cold-start warm-up (${m} client modules)`,x),r.ready(`http://localhost:${N}`)})}var Mn,Un,zn,tt,Gn,J,it,qn,Hn,lt=k(()=>{ge();re();De();Se();Ke();F();je();Le();_e();Qe();Mn=w.dirname(jn(import.meta.url)),Un=/\.(css|less|sass|scss|styl|stylus|pcss|postcss)(\?|$)/,zn=new Set(["Google Sans Flex","Google Sans Code","Outfit"]),tt="/@velu-favicon",Gn=e=>String(e).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;");J=w.resolve(Mn,".."),it=w.resolve(J,"src","template.html"),qn=[" _ __ __","| | / /__ / /_ __","| | / / _ \\/ / / / /","| |/ / __/ / /_/ /","|___/\\___/_/\\__,_/"],Hn="const F=['\u280B','\u2819','\u2839','\u2838','\u283C','\u2834','\u2826','\u2827','\u2807','\u280F'],M=['Starting Velu','Setting up your preview','Preparing your docs','Warming things up','Almost ready'];let f=0,m=0;const t=Date.now();const C='\\x1b[38;2;220;20;60m',R='\\x1b[0m',D='\\x1b[2m';const d=()=>{const s=(Date.now()-t)/1000|0;process.stdout.write('\\x1b[2K\\r '+C+F[f]+R+' '+M[m]+'\\u2026 '+D+s+'s'+R);f=(f+1)%F.length;};d();setInterval(d,90);setInterval(()=>{if(m<M.length-1)m++;},4000);"});var ct={};de(ct,{default:()=>Xn,runValidate:()=>at});import Kn from"node:fs";import Wn from"node:path";function Zn(e){try{let t=JSON.parse(Kn.readFileSync(Wn.join(e,"velu.json"),"utf-8"));if(!t.navigation||typeof t.navigation!="object")return[];let o=V(t.navigation);return B(o,e).pageEntries}catch{return[]}}async function at(e){let t=ue(e),o=Zn(e),n=await We(e,o),r=[...t,...n],i=!!process.stdout.isTTY;return console.log(ae(r,{color:i})),r.some(s=>s.severity!=="warning"&&s.severity!=="info")?1:0}var Xn,ut=k(()=>{W();se();Le();_e();F();Xn=at});import Pe from"node:path";import E from"node:process";import{readFileSync as Qn}from"node:fs";var[,,ft,pe]=E.argv;function eo(){try{return JSON.parse(Qn(new URL("../package.json",import.meta.url),"utf8")).version}catch{return"unknown"}}async function to(){switch(ft){case"--version":case"-v":case"version":{console.log(eo());break}case"init":{pe||(console.error("Usage: velu init <project-name>"),E.exit(1));let e=pe,t=Pe.resolve(E.cwd(),e),{initProject:o}=await Promise.resolve().then(()=>(Fe(),Ne));await o(t,e);break}case"dev":{let e=Pe.resolve(E.cwd(),pe??"."),{startDevServer:t}=await Promise.resolve().then(()=>(lt(),st));await t(e);break}case"validate":{let e=Pe.resolve(E.cwd(),pe??"."),{runValidate:t}=await Promise.resolve().then(()=>(ut(),ct)),o=await t(e);E.exit(o);break}default:console.log("Usage:"),console.log(" velu init <project-name> scaffold a new docs project"),console.log(" velu dev [dir] start the dev preview server"),console.log(" velu validate [dir] check velu.json + content for problems"),E.exit(ft?1:0)}}to().catch(e=>{console.error(E.env.VELU_DEBUG?e:`Error: ${e.message}`),E.exit(1)});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@veluai/velu",
3
- "version": "0.1.14",
3
+ "version": "0.1.15",
4
4
  "type": "module",
5
5
  "bin": "./dist/cli.js",
6
6
  "publishConfig": {
@@ -0,0 +1,31 @@
1
+ import React from 'react';
2
+
3
+ /**
4
+ * Logo — the configured site logo image(s), shown in the header (and mobile
5
+ * drawer) in place of the name wordmark.
6
+ *
7
+ * `logo` is `{ light, dark, mono }` where light/dark are URLs the dev server
8
+ * serves from the project's logo files. When `mono`, a single image covers
9
+ * both themes; otherwise both render and CSS shows the right one per
10
+ * `[data-theme]` (see page-header.css `.velu-logo--light/--dark`).
11
+ *
12
+ * SSR-safe: identical markup on server + client (the theme picks the visible
13
+ * image via CSS, not JS), so there's no hydration mismatch or flash.
14
+ */
15
+ export default function Logo({ logo, alt = '' }) {
16
+ if (!logo) return null;
17
+ if (logo.mono) {
18
+ return <img className="velu-logo" src={logo.light} alt={alt} />;
19
+ }
20
+ return (
21
+ <>
22
+ <img className="velu-logo velu-logo--light" src={logo.light} alt={alt} />
23
+ <img
24
+ className="velu-logo velu-logo--dark"
25
+ src={logo.dark}
26
+ alt={alt}
27
+ aria-hidden="true"
28
+ />
29
+ </>
30
+ );
31
+ }
@@ -1,6 +1,7 @@
1
1
  import React from 'react';
2
2
  import Stack from '../primitives/Stack.jsx';
3
3
  import Cluster from '../primitives/Cluster.jsx';
4
+ import SocialLinks from './SocialLinks.jsx';
4
5
 
5
6
  /**
6
7
  * PageFooter — the docs site's footer.
@@ -65,54 +66,6 @@ function VeluMark() {
65
66
  );
66
67
  }
67
68
 
68
- /* ── Social glyphs — inline SVGs with fill: currentColor so they
69
- inherit color (themed) and flip with light/dark. ─────────────────── */
70
- function GithubIcon() {
71
- return (
72
- <svg viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
73
- <path
74
- fillRule="evenodd"
75
- clipRule="evenodd"
76
- d="M8 0C3.58 0 0 3.58 0 8C0 11.54 2.29 14.53 5.47 15.59C5.87 15.66 6.02 15.42 6.02 15.21C6.02 15.02 6.01 14.39 6.01 13.72C4 14.09 3.48 13.23 3.32 12.78C3.23 12.55 2.84 11.84 2.5 11.65C2.22 11.5 1.82 11.13 2.49 11.12C3.12 11.11 3.57 11.7 3.72 11.94C4.44 13.15 5.59 12.81 6.05 12.6C6.12 12.08 6.33 11.73 6.56 11.53C4.78 11.33 2.92 10.64 2.92 7.58C2.92 6.71 3.23 5.99 3.74 5.43C3.66 5.23 3.38 4.41 3.82 3.31C3.82 3.31 4.49 3.1 6.02 4.13C6.66 3.95 7.34 3.86 8.02 3.86C8.7 3.86 9.38 3.95 10.02 4.13C11.55 3.09 12.22 3.31 12.22 3.31C12.66 4.41 12.38 5.23 12.3 5.43C12.81 5.99 13.12 6.7 13.12 7.58C13.12 10.65 11.25 11.33 9.47 11.53C9.76 11.78 10.01 12.26 10.01 13.01C10.01 14.08 10 14.94 10 15.21C10 15.42 10.15 15.67 10.55 15.59C13.71 14.53 16 11.53 16 8C16 3.58 12.42 0 8 0Z"
77
- />
78
- </svg>
79
- );
80
- }
81
- function XIcon() {
82
- return (
83
- <svg viewBox="0 0 1200 1227" fill="currentColor" aria-hidden="true">
84
- <path d="M714.163 519.284 1160.89 0h-105.86L667.137 450.887 357.328 0H0l468.492 681.821L0 1226.37h105.866l409.625-476.152 327.181 476.152H1200L714.137 519.284h.026ZM569.165 687.828l-47.468-67.894-377.686-540.24h162.604l304.797 435.991 47.468 67.894 396.2 566.721H892.476L569.165 687.854v-.026Z" />
85
- </svg>
86
- );
87
- }
88
- function YoutubeIcon() {
89
- return (
90
- <svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
91
- <path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z" />
92
- </svg>
93
- );
94
- }
95
- function LinkedinIcon() {
96
- return (
97
- <svg viewBox="0 0 256 256" fill="currentColor" aria-hidden="true">
98
- <path d="M218.123 218.127h-37.931v-59.403c0-14.165-.253-32.4-19.728-32.4-19.756 0-22.779 15.434-22.779 31.369v60.43h-37.93V95.967h36.413v16.694h.51a39.907 39.907 0 0 1 35.928-19.733c38.445 0 45.533 25.288 45.533 58.186l-.016 67.013ZM56.955 79.27c-12.157.002-22.014-9.852-22.016-22.009-.002-12.157 9.851-22.014 22.008-22.016 12.157-.003 22.014 9.851 22.016 22.008A22.013 22.013 0 0 1 56.955 79.27m18.966 138.858H37.95V95.967h37.97v122.16ZM237.033.018H18.89C8.58-.098.125 8.161-.001 18.471v219.053c.122 10.315 8.576 18.582 18.89 18.474h218.144c10.336.128 18.823-8.139 18.966-18.474V18.454c-.147-10.33-8.635-18.588-18.966-18.453" />
99
- </svg>
100
- );
101
- }
102
-
103
- const SOCIAL_ICONS = {
104
- github: GithubIcon,
105
- x: XIcon,
106
- youtube: YoutubeIcon,
107
- linkedin: LinkedinIcon,
108
- };
109
- const SOCIAL_LABELS = {
110
- github: 'GitHub',
111
- x: 'X',
112
- youtube: 'YouTube',
113
- linkedin: 'LinkedIn',
114
- };
115
-
116
69
  export default function PageFooter({
117
70
  brand,
118
71
  columns = [],
@@ -182,32 +135,11 @@ export default function PageFooter({
182
135
  </Stack>
183
136
 
184
137
  {/* Socials sit OUTSIDE the container, at the bottom of the footer. */}
185
- {socials.length > 0 && (
186
- <Cluster
187
- space="var(--s-2)"
188
- justify="center"
189
- align="center"
190
- className="velu-footer__socials"
191
- >
192
- {socials.map((s, i) => {
193
- const Icon = SOCIAL_ICONS[s.kind];
194
- if (!Icon) return null;
195
- return (
196
- <a
197
- key={i}
198
- href={s.href}
199
- target="_blank"
200
- rel="noreferrer"
201
- aria-label={SOCIAL_LABELS[s.kind] ?? s.kind}
202
- className="velu-footer__social"
203
- data-kind={s.kind}
204
- >
205
- <Icon />
206
- </a>
207
- );
208
- })}
209
- </Cluster>
210
- )}
138
+ <SocialLinks
139
+ socials={socials}
140
+ justify="center"
141
+ className="velu-footer__socials"
142
+ />
211
143
  </Stack>
212
144
  );
213
145
  }
@@ -3,6 +3,7 @@ import { MoreVertical, Menu, ChevronRight } from 'lucide-react';
3
3
  import resolveIcon from '../lib/resolveIcon.jsx';
4
4
  import Stack from '../primitives/Stack.jsx';
5
5
  import Cluster from '../primitives/Cluster.jsx';
6
+ import Logo from './Logo.jsx';
6
7
 
7
8
  /**
8
9
  * PageHeader — top of the docs site.
@@ -258,10 +259,17 @@ export default function PageHeader({
258
259
  return () => window.removeEventListener('scroll', onScroll);
259
260
  }, []);
260
261
 
262
+ // A configured logo replaces the mark + wordmark entirely (Mintlify-style).
261
263
  const brandBlock = (
262
264
  <Cluster space="var(--s-3)" align="center" className="velu-header__brand">
263
- <VeluMark />
264
- <span className="velu-header__wordmark">{brandLabel}</span>
265
+ {brand?.logo ? (
266
+ <Logo logo={brand.logo} alt={brandLabel} />
267
+ ) : (
268
+ <>
269
+ <VeluMark />
270
+ <span className="velu-header__wordmark">{brandLabel}</span>
271
+ </>
272
+ )}
265
273
  </Cluster>
266
274
  );
267
275
 
@@ -0,0 +1,90 @@
1
+ import React from 'react';
2
+ import Cluster from '../primitives/Cluster.jsx';
3
+
4
+ /**
5
+ * SocialLinks — a row of social-platform icon links. Shared by the full
6
+ * PageFooter (centered at its base) and the compact content-foot row (next to
7
+ * "Powered by Velu" when there are no footer link columns).
8
+ *
9
+ * `socials` is `[{ kind, href }]`. Unknown kinds (no icon) are skipped.
10
+ *
11
+ * @param {{ socials?: {kind: string, href: string}[],
12
+ * justify?: string, className?: string }} props
13
+ */
14
+
15
+ /* Inline SVGs, fill: currentColor so they inherit the themed text color. */
16
+ function GithubIcon() {
17
+ return (
18
+ <svg viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
19
+ <path
20
+ fillRule="evenodd"
21
+ clipRule="evenodd"
22
+ d="M8 0C3.58 0 0 3.58 0 8C0 11.54 2.29 14.53 5.47 15.59C5.87 15.66 6.02 15.42 6.02 15.21C6.02 15.02 6.01 14.39 6.01 13.72C4 14.09 3.48 13.23 3.32 12.78C3.23 12.55 2.84 11.84 2.5 11.65C2.22 11.5 1.82 11.13 2.49 11.12C3.12 11.11 3.57 11.7 3.72 11.94C4.44 13.15 5.59 12.81 6.05 12.6C6.12 12.08 6.33 11.73 6.56 11.53C4.78 11.33 2.92 10.64 2.92 7.58C2.92 6.71 3.23 5.99 3.74 5.43C3.66 5.23 3.38 4.41 3.82 3.31C3.82 3.31 4.49 3.1 6.02 4.13C6.66 3.95 7.34 3.86 8.02 3.86C8.7 3.86 9.38 3.95 10.02 4.13C11.55 3.09 12.22 3.31 12.22 3.31C12.66 4.41 12.38 5.23 12.3 5.43C12.81 5.99 13.12 6.7 13.12 7.58C13.12 10.65 11.25 11.33 9.47 11.53C9.76 11.78 10.01 12.26 10.01 13.01C10.01 14.08 10 14.94 10 15.21C10 15.42 10.15 15.67 10.55 15.59C13.71 14.53 16 11.53 16 8C16 3.58 12.42 0 8 0Z"
23
+ />
24
+ </svg>
25
+ );
26
+ }
27
+ function XIcon() {
28
+ return (
29
+ <svg viewBox="0 0 1200 1227" fill="currentColor" aria-hidden="true">
30
+ <path d="M714.163 519.284 1160.89 0h-105.86L667.137 450.887 357.328 0H0l468.492 681.821L0 1226.37h105.866l409.625-476.152 327.181 476.152H1200L714.137 519.284h.026ZM569.165 687.828l-47.468-67.894-377.686-540.24h162.604l304.797 435.991 47.468 67.894 396.2 566.721H892.476L569.165 687.854v-.026Z" />
31
+ </svg>
32
+ );
33
+ }
34
+ function YoutubeIcon() {
35
+ return (
36
+ <svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
37
+ <path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z" />
38
+ </svg>
39
+ );
40
+ }
41
+ function LinkedinIcon() {
42
+ return (
43
+ <svg viewBox="0 0 256 256" fill="currentColor" aria-hidden="true">
44
+ <path d="M218.123 218.127h-37.931v-59.403c0-14.165-.253-32.4-19.728-32.4-19.756 0-22.779 15.434-22.779 31.369v60.43h-37.93V95.967h36.413v16.694h.51a39.907 39.907 0 0 1 35.928-19.733c38.445 0 45.533 25.288 45.533 58.186l-.016 67.013ZM56.955 79.27c-12.157.002-22.014-9.852-22.016-22.009-.002-12.157 9.851-22.014 22.008-22.016 12.157-.003 22.014 9.851 22.016 22.008A22.013 22.013 0 0 1 56.955 79.27m18.966 138.858H37.95V95.967h37.97v122.16ZM237.033.018H18.89C8.58-.098.125 8.161-.001 18.471v219.053c.122 10.315 8.576 18.582 18.89 18.474h218.144c10.336.128 18.823-8.139 18.966-18.474V18.454c-.147-10.33-8.635-18.588-18.966-18.453" />
45
+ </svg>
46
+ );
47
+ }
48
+
49
+ export const SOCIAL_ICONS = {
50
+ github: GithubIcon,
51
+ x: XIcon,
52
+ youtube: YoutubeIcon,
53
+ linkedin: LinkedinIcon,
54
+ };
55
+ const SOCIAL_LABELS = {
56
+ github: 'GitHub',
57
+ x: 'X',
58
+ youtube: 'YouTube',
59
+ linkedin: 'LinkedIn',
60
+ };
61
+
62
+ export default function SocialLinks({ socials = [], justify, className = '' }) {
63
+ const known = socials.filter((s) => s && SOCIAL_ICONS[s.kind]);
64
+ if (!known.length) return null;
65
+ return (
66
+ <Cluster
67
+ space="var(--s-2)"
68
+ align="center"
69
+ justify={justify}
70
+ className={`velu-socials ${className}`.trim()}
71
+ >
72
+ {known.map((s, i) => {
73
+ const Icon = SOCIAL_ICONS[s.kind];
74
+ return (
75
+ <a
76
+ key={i}
77
+ href={s.href}
78
+ target="_blank"
79
+ rel="noreferrer"
80
+ aria-label={SOCIAL_LABELS[s.kind] ?? s.kind}
81
+ className="velu-social"
82
+ data-kind={s.kind}
83
+ >
84
+ <Icon />
85
+ </a>
86
+ );
87
+ })}
88
+ </Cluster>
89
+ );
90
+ }
@@ -100,7 +100,7 @@
100
100
  little breathing room remains around it. Each kind gets its OWN
101
101
  brand color (set via the data-kind attribute below). Monochrome
102
102
  brands (github, x) inherit --text-color so they theme correctly. */
103
- .velu-footer__social {
103
+ .velu-social {
104
104
  display: inline-flex;
105
105
  align-items: center;
106
106
  justify-content: center;
@@ -112,19 +112,19 @@
112
112
  text-decoration: none;
113
113
  transition: opacity 0.12s ease;
114
114
  }
115
- .velu-footer__social:hover {
115
+ .velu-social:hover {
116
116
  opacity: 0.78;
117
117
  }
118
- .velu-footer__social svg {
118
+ .velu-social svg {
119
119
  inline-size: var(--icon-size-lg);
120
120
  block-size: var(--icon-size-lg);
121
121
  }
122
122
  /* Brand colors — YouTube red, LinkedIn blue. GitHub + X stay
123
123
  --text-color (their canonical brand is monochrome and follows the
124
124
  surrounding theme). */
125
- .velu-footer__social[data-kind='youtube'] {
125
+ .velu-social[data-kind='youtube'] {
126
126
  color: #ff0000;
127
127
  }
128
- .velu-footer__social[data-kind='linkedin'] {
128
+ .velu-social[data-kind='linkedin'] {
129
129
  color: #0a66c2;
130
130
  }
@@ -100,6 +100,28 @@
100
100
  color: var(--text-color);
101
101
  }
102
102
 
103
+ /* Configured site logo (replaces the mark + wordmark). Height-constrained to
104
+ the brand row; width scales by aspect ratio up to a sane cap. */
105
+ .velu-logo {
106
+ display: block;
107
+ block-size: 2rem;
108
+ inline-size: auto;
109
+ max-inline-size: 12rem;
110
+ object-fit: contain;
111
+ }
112
+ /* Per-theme swap: light image by default, dark image under [data-theme=dark].
113
+ `display` (not opacity) so the inactive one leaves the a11y tree too. The
114
+ `mono` single-image case has neither modifier and always shows. */
115
+ .velu-logo--dark {
116
+ display: none;
117
+ }
118
+ [data-theme='dark'] .velu-logo--light {
119
+ display: none;
120
+ }
121
+ [data-theme='dark'] .velu-logo--dark {
122
+ display: block;
123
+ }
124
+
103
125
  /* ── Action buttons ─────────────────────────────────────────────────── */
104
126
  .velu-header__action {
105
127
  display: inline-flex;
@@ -136,6 +158,21 @@
136
158
  .velu-header__action--primary:hover {
137
159
  background: color-mix(in srgb, var(--accent-color) 88%, #000);
138
160
  }
161
+ /* Plain text link (navbar.links) — no fill or border, just text that goes
162
+ accent on hover. Transparent border keeps it the same height as the
163
+ buttons beside it; tighter inline padding reads as a link, not a button. */
164
+ .velu-header__action--link {
165
+ background: transparent;
166
+ border: var(--border-width) solid transparent;
167
+ color: var(--text-color);
168
+ padding-inline: var(--s-5);
169
+ }
170
+ .velu-header__action--link:hover {
171
+ color: var(--accent-color);
172
+ }
173
+ .velu-header__action--link:hover .velu-header__action-icon {
174
+ color: var(--accent-color);
175
+ }
139
176
  .velu-header__action-icon {
140
177
  display: inline-flex;
141
178
  flex: none;
@@ -1,6 +1,26 @@
1
1
  /* PoweredBy — "Powered by Velu" badge, right-aligned. Sits above the
2
2
  site footer. All values are tokens; light/dark via [data-theme]. */
3
3
 
4
+ /* Content foot — social icons (left) + the "Powered by" badge (right) in one
5
+ row. Shown at the base of the article column when the footer has no link
6
+ columns (so there's no full footer section). The row owns the vertical
7
+ rhythm; the nested badge drops its own standalone margins. */
8
+ .velu-content-foot {
9
+ display: flex;
10
+ align-items: center;
11
+ justify-content: space-between;
12
+ gap: var(--s2);
13
+ margin-block: 2rem;
14
+ padding-inline: var(--s3);
15
+ }
16
+ .velu-content-foot .velu-powered-by {
17
+ margin: 0;
18
+ padding: 0;
19
+ }
20
+ .velu-content-foot .velu-social {
21
+ color: var(--muted-color);
22
+ }
23
+
4
24
  .velu-powered-by {
5
25
  display: flex;
6
26
  justify-content: flex-end;
@@ -8,6 +8,8 @@ export { default as Toc } from './components/Toc.jsx';
8
8
  export { default as TocBar } from './components/TocBar.jsx';
9
9
  export { default as Callout } from './components/Callout.jsx';
10
10
  export { default as ErrorCard } from './components/ErrorCard.jsx';
11
+ export { default as Logo } from './components/Logo.jsx';
12
+ export { default as SocialLinks } from './components/SocialLinks.jsx';
11
13
  export {
12
14
  default as Accordion,
13
15
  AccordionGroup,
@@ -40,6 +40,120 @@
40
40
  "type": "string",
41
41
  "description": "Path to the favicon, relative to the project root."
42
42
  },
43
+ "logo": {
44
+ "description": "Site logo shown in the header, replacing the name wordmark. A single path used in both themes, or per-theme light/dark images with an optional click-through href. Paths are relative to the project root.",
45
+ "oneOf": [
46
+ {
47
+ "type": "string",
48
+ "minLength": 1,
49
+ "description": "Logo path used in both light and dark mode."
50
+ },
51
+ {
52
+ "type": "object",
53
+ "additionalProperties": false,
54
+ "required": ["light", "dark"],
55
+ "properties": {
56
+ "light": {
57
+ "type": "string",
58
+ "minLength": 1,
59
+ "description": "Logo shown in light mode."
60
+ },
61
+ "dark": {
62
+ "type": "string",
63
+ "minLength": 1,
64
+ "description": "Logo shown in dark mode."
65
+ },
66
+ "href": {
67
+ "type": "string",
68
+ "description": "Where clicking the logo links. Defaults to the site root."
69
+ }
70
+ }
71
+ }
72
+ ]
73
+ },
74
+ "navbar": {
75
+ "type": "object",
76
+ "additionalProperties": false,
77
+ "description": "The header's top-right area: secondary links plus a primary CTA button.",
78
+ "properties": {
79
+ "links": {
80
+ "type": "array",
81
+ "description": "Secondary header links (to the left of the primary CTA).",
82
+ "items": {
83
+ "type": "object",
84
+ "additionalProperties": false,
85
+ "required": ["href"],
86
+ "properties": {
87
+ "label": { "type": "string", "description": "Link text." },
88
+ "href": { "type": "string", "description": "Link URL." },
89
+ "icon": { "type": "string", "description": "lucide icon id." }
90
+ }
91
+ }
92
+ },
93
+ "primary": {
94
+ "description": "The prominent CTA button at the far right.",
95
+ "oneOf": [
96
+ {
97
+ "type": "object",
98
+ "additionalProperties": false,
99
+ "required": ["type", "label", "href"],
100
+ "properties": {
101
+ "type": { "const": "button" },
102
+ "label": { "type": "string", "description": "Button text." },
103
+ "href": { "type": "string", "description": "Button URL." }
104
+ }
105
+ },
106
+ {
107
+ "type": "object",
108
+ "additionalProperties": false,
109
+ "required": ["type", "href"],
110
+ "properties": {
111
+ "type": { "const": "github" },
112
+ "href": { "type": "string", "description": "GitHub repository URL." },
113
+ "label": { "type": "string", "description": "Button text (default \"GitHub\")." }
114
+ }
115
+ }
116
+ ]
117
+ }
118
+ }
119
+ },
120
+ "footer": {
121
+ "type": "object",
122
+ "additionalProperties": false,
123
+ "description": "Site footer. With `links`, a full footer section renders; with only `socials`, the icons sit next to the \"Powered by\" line instead.",
124
+ "properties": {
125
+ "socials": {
126
+ "type": "object",
127
+ "description": "Social links as platform → URL. Recognized icons: github, x (or twitter), youtube, linkedin.",
128
+ "additionalProperties": { "type": "string" }
129
+ },
130
+ "links": {
131
+ "type": "array",
132
+ "description": "Footer link columns (max 4).",
133
+ "maxItems": 4,
134
+ "items": {
135
+ "type": "object",
136
+ "additionalProperties": false,
137
+ "required": ["header", "items"],
138
+ "properties": {
139
+ "header": { "type": "string", "description": "Column heading." },
140
+ "items": {
141
+ "type": "array",
142
+ "items": {
143
+ "type": "object",
144
+ "additionalProperties": false,
145
+ "required": ["label", "href"],
146
+ "properties": {
147
+ "label": { "type": "string", "description": "Link text." },
148
+ "href": { "type": "string", "description": "Link URL." }
149
+ }
150
+ }
151
+ }
152
+ }
153
+ }
154
+ }
155
+ }
156
+ },
43
157
  "font": {
44
158
  "type": "object",
45
159
  "description": "Typography. Maps onto the --font-sans token in base.css.",
@@ -4,7 +4,7 @@ import { MDXProvider } from '@mdx-js/react';
4
4
  // The project's pages + navigation, generated from velu.json by
5
5
  // vite-plugin-velu-site (see src/vite-plugin-velu-site.js). `pages` is
6
6
  // url → { Component, frontmatter, toc } (or { missing:true }).
7
- import { pages, navigation } from 'virtual:velu-site';
7
+ import { pages, navigation, site } from 'virtual:velu-site';
8
8
  import { resolve, normalizeUrl } from '../navigation.js';
9
9
  import {
10
10
  Stack,
@@ -38,6 +38,8 @@ import {
38
38
  defaultMdxComponents,
39
39
  resolveIcon,
40
40
  Search,
41
+ Logo,
42
+ SocialLinks,
41
43
  Tree,
42
44
  Folder,
43
45
  File,
@@ -944,6 +946,35 @@ function DocsPage() {
944
946
  return () => scroller.removeEventListener('scroll', update);
945
947
  }, [pathname, chatOpen]);
946
948
 
949
+ // Footer config (from velu.json via virtual:velu-site). Link columns →
950
+ // full footer section; socials with no columns → inline next to PoweredBy.
951
+ const footerSocials = site.footer?.socials ?? [];
952
+ const footerHasLinks = (site.footer?.links?.length ?? 0) > 0;
953
+
954
+ // Header top-right actions, built from velu.json's `navbar`: secondary
955
+ // `links` (plain text links) then the `primary` CTA (a filled button, or an
956
+ // outlined GitHub button). Empty when unconfigured — no placeholder buttons.
957
+ const navActions = [];
958
+ for (const l of site.navbar?.links ?? []) {
959
+ navActions.push({
960
+ label: l.label,
961
+ href: l.href,
962
+ icon: l.icon || undefined,
963
+ kind: 'link',
964
+ external: l.external,
965
+ });
966
+ }
967
+ if (site.navbar?.primary) {
968
+ const p = site.navbar.primary;
969
+ navActions.push({
970
+ label: p.label,
971
+ href: p.href,
972
+ kind: p.kind === 'github' ? 'outlined' : 'primary',
973
+ icon: p.kind === 'github' ? 'github' : undefined,
974
+ external: p.external,
975
+ });
976
+ }
977
+
947
978
  const footerRef = React.useRef(null);
948
979
  const [footerOverlap, setFooterOverlap] = React.useState(0);
949
980
  React.useEffect(() => {
@@ -1043,7 +1074,11 @@ function DocsPage() {
1043
1074
  tabs row. Configurable: pass any number of actions / tabs. */}
1044
1075
  <PageHeader
1045
1076
  linkComponent={RouterLink}
1046
- brand={{ label: 'Velu', href: '/' }}
1077
+ brand={{
1078
+ label: site.name,
1079
+ href: site.logo?.href || '/',
1080
+ logo: site.logo,
1081
+ }}
1047
1082
  brandTrailing={
1048
1083
  versionSwitcher && (
1049
1084
  <span className="velu-hide-on-mobile">{versionSwitcher}</span>
@@ -1069,13 +1104,7 @@ function DocsPage() {
1069
1104
  )}
1070
1105
  </Cluster>
1071
1106
  }
1072
- actions={[
1073
- {
1074
- label: 'Book Demo',
1075
- kind: 'primary',
1076
- href: '#',
1077
- },
1078
- ]}
1107
+ actions={navActions}
1079
1108
  trailing={<ThemeToggle />}
1080
1109
  onMenuClick={() => setDrawerOpen((v) => !v)}
1081
1110
  breadcrumb={nav?.breadcrumb ?? []}
@@ -1111,8 +1140,14 @@ function DocsPage() {
1111
1140
  href="/"
1112
1141
  className="velu-docs-layout__drawer-brand"
1113
1142
  >
1114
- <VeluMark />
1115
- <span className="velu-header__wordmark">Velu</span>
1143
+ {site.logo ? (
1144
+ <Logo logo={site.logo} alt={site.name} />
1145
+ ) : (
1146
+ <>
1147
+ <VeluMark />
1148
+ <span className="velu-header__wordmark">{site.name}</span>
1149
+ </>
1150
+ )}
1116
1151
  </RouterLink>
1117
1152
  <ThemeToggle />
1118
1153
  <button
@@ -1417,59 +1452,40 @@ function DocsPage() {
1417
1452
  }}
1418
1453
  />
1419
1454
  )}
1420
- {/* "Powered by Velu" attribution bottom-right of the
1421
- article column, just under the AskBar. Muted so it reads
1422
- as a footer-of-content note, not a brand statement. */}
1423
- <PoweredBy />
1455
+ {/* Content foot — "Powered by Velu" at the bottom-right of the
1456
+ article column. When the footer has no link columns, the
1457
+ configured social icons sit on the LEFT of this same row (no
1458
+ full footer section); with link columns they live in the full
1459
+ footer below instead. */}
1460
+ {footerHasLinks ? (
1461
+ <PoweredBy />
1462
+ ) : (
1463
+ <div className="velu-content-foot">
1464
+ <SocialLinks socials={footerSocials} />
1465
+ <PoweredBy />
1466
+ </div>
1467
+ )}
1424
1468
  </div>
1425
1469
  </main>
1426
1470
  </div>
1427
1471
 
1428
- {/* Site footer — spans full width below the article. Its raised
1429
- z-index ensures it visually eclipses the bottoms of the fixed
1430
- left sidebar and right TOC as the page scrolls into it. The
1431
- ref is watched so the asides can pad their bottom by the same
1432
- overlap amount, keeping every item scrollable into view. */}
1433
- <div
1434
- ref={footerRef}
1435
- data-velu-footer
1436
- style={{ position: 'relative', zIndex: 20 }}
1437
- >
1438
- <PageFooter
1439
- brand={{ href: '#' }}
1440
- columns={[
1441
- {
1442
- title: 'Resources',
1443
- items: [
1444
- { label: 'Showcase', href: '#' },
1445
- { label: 'Enterprise', href: '#' },
1446
- { label: 'Status', href: '#' },
1447
- ],
1448
- },
1449
- {
1450
- title: 'Company',
1451
- items: [
1452
- { label: 'Careers', href: '#' },
1453
- { label: 'Blog', href: '#' },
1454
- { label: 'Community', href: '#' },
1455
- ],
1456
- },
1457
- {
1458
- title: 'Policies',
1459
- items: [
1460
- { label: 'Subprocessors', href: '#' },
1461
- { label: 'Terms of Service', href: '#' },
1462
- ],
1463
- },
1464
- ]}
1465
- socials={[
1466
- { kind: 'github', href: 'https://github.com/aravindc26/velu-cli' },
1467
- { kind: 'x', href: 'https://x.com/' },
1468
- { kind: 'youtube', href: 'https://youtube.com/' },
1469
- { kind: 'linkedin', href: 'https://linkedin.com/' },
1470
- ]}
1471
- />
1472
- </div>
1472
+ {/* Full site footer — only when the config provides link columns. Spans
1473
+ full width below the article; its raised z-index eclipses the bottoms
1474
+ of the fixed sidebar and TOC as the page scrolls into it, and the ref
1475
+ is watched so the asides can pad their bottom by the overlap. */}
1476
+ {footerHasLinks && (
1477
+ <div
1478
+ ref={footerRef}
1479
+ data-velu-footer
1480
+ style={{ position: 'relative', zIndex: 20 }}
1481
+ >
1482
+ <PageFooter
1483
+ brand={{ href: site.logo?.href || '/' }}
1484
+ columns={site.footer.links}
1485
+ socials={footerSocials}
1486
+ />
1487
+ </div>
1488
+ )}
1473
1489
 
1474
1490
  {/* Ask-AI chatbot — slides in from the inline-end edge. */}
1475
1491
  <Chatbot