sommark 5.0.5 → 5.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,3 @@
1
- import { AsyncLocalStorage } from 'node:async_hooks';
2
-
3
1
  let _lazyMatch = () => { var __lib__=(()=>{var m=Object.defineProperty,V=Object.getOwnPropertyDescriptor,G=Object.getOwnPropertyNames,T=Object.prototype.hasOwnProperty,q=(r,e)=>{for(var n in e)m(r,n,{get:e[n],enumerable:true});},H=(r,e,n,a)=>{if(e&&typeof e=="object"||typeof e=="function")for(let t of G(e))!T.call(r,t)&&t!==n&&m(r,t,{get:()=>e[t],enumerable:!(a=V(e,t))||a.enumerable});return r},J=r=>H(m({},"__esModule",{value:true}),r),w={};q(w,{default:()=>re});var A=r=>Array.isArray(r),d=r=>typeof r=="function",Q=r=>r.length===0,W=r=>typeof r=="number",K=r=>typeof r=="object"&&r!==null,X=r=>r instanceof RegExp,b=r=>typeof r=="string",h=r=>r===void 0,Y=r=>{const e=new Map;return n=>{const a=e.get(n);if(a)return a;const t=r(n);return e.set(n,t),t}},rr=(r,e,n={})=>{const a={cache:{},input:r,index:0,indexMax:0,options:n,output:[]};if(v(e)(a)&&a.index===r.length)return a.output;throw new Error(`Failed to parse at index ${a.indexMax}`)},i=(r,e)=>A(r)?er(r,e):b(r)?ar(r,e):nr(r,e),er=(r,e)=>{const n={};for(const a of r){if(a.length!==1)throw new Error(`Invalid character: "${a}"`);const t=a.charCodeAt(0);n[t]=true;}return a=>{const t=a.index,o=a.input;for(;a.index<o.length&&o.charCodeAt(a.index)in n;)a.index+=1;const u=a.index;if(u>t){if(!h(e)&&!a.options.silent){const s=a.input.slice(t,u),c=d(e)?e(s,o,String(t)):e;h(c)||a.output.push(c);}a.indexMax=Math.max(a.indexMax,a.index);}return true}},nr=(r,e)=>{const n=r.source,a=r.flags.replace(/y|$/,"y"),t=new RegExp(n,a);return g(o=>{t.lastIndex=o.index;const u=t.exec(o.input);if(u){if(!h(e)&&!o.options.silent){const s=d(e)?e(...u,o.input,String(o.index)):e;h(s)||o.output.push(s);}return o.index+=u[0].length,o.indexMax=Math.max(o.indexMax,o.index),true}else return false})},ar=(r,e)=>n=>{if(n.input.startsWith(r,n.index)){if(!h(e)&&!n.options.silent){const t=d(e)?e(r,n.input,String(n.index)):e;h(t)||n.output.push(t);}return n.index+=r.length,n.indexMax=Math.max(n.indexMax,n.index),true}else return false},C=(r,e,n,a)=>{const t=v(r);return g(_(M(o=>{let u=0;for(;u<n;){const s=o.index;if(!t(o)||(u+=1,o.index===s))break}return u>=e})))},tr=(r,e)=>C(r,0,1),f=(r,e)=>C(r,0,1/0),x=(r,e)=>{const n=r.map(v);return g(_(M(a=>{for(let t=0,o=n.length;t<o;t++)if(!n[t](a))return false;return true})))},l=(r,e)=>{const n=r.map(v);return g(_(a=>{for(let t=0,o=n.length;t<o;t++)if(n[t](a))return true;return false}))},M=(r,e=false)=>{const n=v(r);return a=>{const t=a.index,o=a.output.length,u=n(a);return (!u||e)&&(a.index=t,a.output.length!==o&&(a.output.length=o)),u}},_=(r,e)=>{const n=v(r);return n},g=(()=>{let r=0;return e=>{const n=v(e),a=r+=1;return t=>{var o;if(t.options.memoization===false)return n(t);const u=t.index,s=(o=t.cache)[a]||(o[a]=new Map),c=s.get(u);if(c===false)return false;if(W(c))return t.index=c,true;if(c)return t.index=c.index,c.output?.length&&t.output.push(...c.output),true;{const Z=t.output.length;if(n(t)){const D=t.index,U=t.output.length;if(U>Z){const ee=t.output.slice(Z,U);s.set(u,{index:D,output:ee});}else s.set(u,D);return true}else return s.set(u,false),false}}}})(),E=r=>{let e;return n=>(e||(e=v(r())),e(n))},v=Y(r=>{if(d(r))return Q(r)?E(r):r;if(b(r)||X(r))return i(r);if(A(r))return x(r);if(K(r))return l(Object.values(r));throw new Error("Invalid rule")}),P="abcdefghijklmnopqrstuvwxyz",ir=r=>{let e="";for(;r>0;){const n=(r-1)%26;e=P[n]+e,r=Math.floor((r-1)/26);}return e},O=r=>{let e=0;for(let n=0,a=r.length;n<a;n++)e=e*26+P.indexOf(r[n])+1;return e},S=(r,e)=>{if(e<r)return S(e,r);const n=[];for(;r<=e;)n.push(r++);return n},or=(r,e,n)=>S(r,e).map(a=>String(a).padStart(n,"0")),R=(r,e)=>S(O(r),O(e)).map(ir),p=r=>r,z=r=>ur(e=>rr(e,r,{memoization:false}).join("")),ur=r=>{const e={};return n=>e[n]??(e[n]=r(n))},sr=i(/^\*\*\/\*$/,".*"),cr=i(/^\*\*\/(\*)?([ a-zA-Z0-9._-]+)$/,(r,e,n)=>`.*${e?"":"(?:^|/)"}${n.replaceAll(".","\\.")}`),lr=i(/^\*\*\/(\*)?([ a-zA-Z0-9._-]*)\{([ a-zA-Z0-9._-]+(?:,[ a-zA-Z0-9._-]+)*)\}$/,(r,e,n,a)=>`.*${e?"":"(?:^|/)"}${n.replaceAll(".","\\.")}(?:${a.replaceAll(",","|").replaceAll(".","\\.")})`),y=i(/\\./,p),pr=i(/[$.*+?^(){}[\]\|]/,r=>`\\${r}`),vr=i(/./,p),hr=i(/^(?:!!)*!(.*)$/,(r,e)=>`(?!^${L(e)}$).*?`),dr=i(/^(!!)+/,""),fr=l([hr,dr]),xr=i(/\/(\*\*\/)+/,"(?:/.+/|/)"),gr=i(/^(\*\*\/)+/,"(?:^|.*/)"),mr=i(/\/(\*\*)$/,"(?:/.*|$)"),_r=i(/\*\*/,".*"),j=l([xr,gr,mr,_r]),Sr=i(/\*\/(?!\*\*\/)/,"[^/]*/"),yr=i(/\*/,"[^/]*"),N=l([Sr,yr]),k=i("?","[^/]"),$r=i("[",p),wr=i("]",p),Ar=i(/[!^]/,"^/"),br=i(/[a-z]-[a-z]|[0-9]-[0-9]/i,p),Cr=i(/[$.*+?^(){}[\|]/,r=>`\\${r}`),Mr=i(/[^\]]/,p),Er=l([y,Cr,br,Mr]),B=x([$r,tr(Ar),f(Er),wr]),Pr=i("{","(?:"),Or=i("}",")"),Rr=i(/(\d+)\.\.(\d+)/,(r,e,n)=>or(+e,+n,Math.min(e.length,n.length)).join("|")),zr=i(/([a-z]+)\.\.([a-z]+)/,(r,e,n)=>R(e,n).join("|")),jr=i(/([A-Z]+)\.\.([A-Z]+)/,(r,e,n)=>R(e.toLowerCase(),n.toLowerCase()).join("|").toUpperCase()),Nr=l([Rr,zr,jr]),I=x([Pr,Nr,Or]),kr=i("{","(?:"),Br=i("}",")"),Ir=i(",","|"),Fr=i(/[$.*+?^(){[\]\|]/,r=>`\\${r}`),Lr=i(/[^}]/,p),Zr=E(()=>F),Dr=l([j,N,k,B,I,Zr,y,Fr,Ir,Lr]),F=x([kr,f(Dr),Br]),Ur=f(l([sr,cr,lr,fr,j,N,k,B,I,F,y,pr,vr])),Vr=Ur,Gr=z(Vr),L=Gr,Tr=i(/\\./,p),qr=i(/./,p),Hr=i(/\*\*\*+/,"*"),Jr=i(/([^/{[(!])\*\*/,(r,e)=>`${e}*`),Qr=i(/(^|.)\*\*(?=[^*/)\]}])/,(r,e)=>`${e}*`),Wr=f(l([Tr,Hr,Jr,Qr,qr])),Kr=Wr,Xr=z(Kr),Yr=Xr,$=(r,e)=>{const n=Array.isArray(r)?r:[r];if(!n.length)return false;const a=n.map($.compile),t=n.every(s=>/(\/(?:\*\*)?|\[\/\])$/.test(s)),o=e.replace(/[\\\/]+/g,"/").replace(/\/$/,t?"/":"");return a.some(s=>s.test(o))};$.compile=r=>new RegExp(`^${L(Yr(r))}$`,"s");var re=$;return J(w)})();
4
2
  return __lib__.default || __lib__; };
5
3
  let _match;
@@ -1476,6 +1474,7 @@ function makeBlockNode() {
1476
1474
  structure: "Block",
1477
1475
  id: "",
1478
1476
  props: {},
1477
+ directives: {},
1479
1478
  body: [],
1480
1479
  depth: 0,
1481
1480
  range: {
@@ -1961,9 +1960,11 @@ function parseBlock(tokens, i, filename = null, placeholders = {}, variables = {
1961
1960
  i = valueIndex;
1962
1961
 
1963
1962
  // Store Argument
1964
- blockNode.props[String(argIndex++)] = v;
1965
- if (k) {
1966
- blockNode.props[k] = v;
1963
+ if (k && k.startsWith("smark-")) {
1964
+ blockNode.directives[k.slice(6)] = v; // strip "smark-" prefix
1965
+ } else {
1966
+ blockNode.props[String(argIndex++)] = v;
1967
+ if (k) blockNode.props[k] = v;
1967
1968
  }
1968
1969
  k = "";
1969
1970
  v = "";
@@ -8627,7 +8628,7 @@ function registerHostSettings(settings) {
8627
8628
  hostSettings = settings || {};
8628
8629
  }
8629
8630
 
8630
- const version = "5.0.5";
8631
+ const version = "5.2.0";
8631
8632
 
8632
8633
  const SomMark$1 = {
8633
8634
  version,
@@ -8678,14 +8679,21 @@ const SomMark$1 = {
8678
8679
  // Freeze the entire Standard Library to make it completely immutable and tamper-proof
8679
8680
  Object.freeze(SomMark$1);
8680
8681
 
8681
- // Each transpile() call gets its own isolated EvaluatorState stack via async context.
8682
- const evaluatorStorage = new AsyncLocalStorage();
8682
+ const patheBundleCode = "let _lazyMatch = () => { var __lib__=(()=>{var m=Object.defineProperty,V=Object.getOwnPropertyDescriptor,G=Object.getOwnPropertyNames,T=Object.prototype.hasOwnProperty,q=(r,e)=>{for(var n in e)m(r,n,{get:e[n],enumerable:true});},H=(r,e,n,a)=>{if(e&&typeof e==\"object\"||typeof e==\"function\")for(let t of G(e))!T.call(r,t)&&t!==n&&m(r,t,{get:()=>e[t],enumerable:!(a=V(e,t))||a.enumerable});return r},J=r=>H(m({},\"__esModule\",{value:true}),r),w={};q(w,{default:()=>re});var A=r=>Array.isArray(r),d=r=>typeof r==\"function\",Q=r=>r.length===0,W=r=>typeof r==\"number\",K=r=>typeof r==\"object\"&&r!==null,X=r=>r instanceof RegExp,b=r=>typeof r==\"string\",h=r=>r===void 0,Y=r=>{const e=new Map;return n=>{const a=e.get(n);if(a)return a;const t=r(n);return e.set(n,t),t}},rr=(r,e,n={})=>{const a={cache:{},input:r,index:0,indexMax:0,options:n,output:[]};if(v(e)(a)&&a.index===r.length)return a.output;throw new Error(`Failed to parse at index ${a.indexMax}`)},i=(r,e)=>A(r)?er(r,e):b(r)?ar(r,e):nr(r,e),er=(r,e)=>{const n={};for(const a of r){if(a.length!==1)throw new Error(`Invalid character: \"${a}\"`);const t=a.charCodeAt(0);n[t]=true;}return a=>{const t=a.index,o=a.input;for(;a.index<o.length&&o.charCodeAt(a.index)in n;)a.index+=1;const u=a.index;if(u>t){if(!h(e)&&!a.options.silent){const s=a.input.slice(t,u),c=d(e)?e(s,o,String(t)):e;h(c)||a.output.push(c);}a.indexMax=Math.max(a.indexMax,a.index);}return true}},nr=(r,e)=>{const n=r.source,a=r.flags.replace(/y|$/,\"y\"),t=new RegExp(n,a);return g(o=>{t.lastIndex=o.index;const u=t.exec(o.input);if(u){if(!h(e)&&!o.options.silent){const s=d(e)?e(...u,o.input,String(o.index)):e;h(s)||o.output.push(s);}return o.index+=u[0].length,o.indexMax=Math.max(o.indexMax,o.index),true}else return false})},ar=(r,e)=>n=>{if(n.input.startsWith(r,n.index)){if(!h(e)&&!n.options.silent){const t=d(e)?e(r,n.input,String(n.index)):e;h(t)||n.output.push(t);}return n.index+=r.length,n.indexMax=Math.max(n.indexMax,n.index),true}else return false},C=(r,e,n,a)=>{const t=v(r);return g(_(M(o=>{let u=0;for(;u<n;){const s=o.index;if(!t(o)||(u+=1,o.index===s))break}return u>=e})))},tr=(r,e)=>C(r,0,1),f=(r,e)=>C(r,0,1/0),x=(r,e)=>{const n=r.map(v);return g(_(M(a=>{for(let t=0,o=n.length;t<o;t++)if(!n[t](a))return false;return true})))},l=(r,e)=>{const n=r.map(v);return g(_(a=>{for(let t=0,o=n.length;t<o;t++)if(n[t](a))return true;return false}))},M=(r,e=false)=>{const n=v(r);return a=>{const t=a.index,o=a.output.length,u=n(a);return (!u||e)&&(a.index=t,a.output.length!==o&&(a.output.length=o)),u}},_=(r,e)=>{const n=v(r);return n},g=(()=>{let r=0;return e=>{const n=v(e),a=r+=1;return t=>{var o;if(t.options.memoization===false)return n(t);const u=t.index,s=(o=t.cache)[a]||(o[a]=new Map),c=s.get(u);if(c===false)return false;if(W(c))return t.index=c,true;if(c)return t.index=c.index,c.output?.length&&t.output.push(...c.output),true;{const Z=t.output.length;if(n(t)){const D=t.index,U=t.output.length;if(U>Z){const ee=t.output.slice(Z,U);s.set(u,{index:D,output:ee});}else s.set(u,D);return true}else return s.set(u,false),false}}}})(),E=r=>{let e;return n=>(e||(e=v(r())),e(n))},v=Y(r=>{if(d(r))return Q(r)?E(r):r;if(b(r)||X(r))return i(r);if(A(r))return x(r);if(K(r))return l(Object.values(r));throw new Error(\"Invalid rule\")}),P=\"abcdefghijklmnopqrstuvwxyz\",ir=r=>{let e=\"\";for(;r>0;){const n=(r-1)%26;e=P[n]+e,r=Math.floor((r-1)/26);}return e},O=r=>{let e=0;for(let n=0,a=r.length;n<a;n++)e=e*26+P.indexOf(r[n])+1;return e},S=(r,e)=>{if(e<r)return S(e,r);const n=[];for(;r<=e;)n.push(r++);return n},or=(r,e,n)=>S(r,e).map(a=>String(a).padStart(n,\"0\")),R=(r,e)=>S(O(r),O(e)).map(ir),p=r=>r,z=r=>ur(e=>rr(e,r,{memoization:false}).join(\"\")),ur=r=>{const e={};return n=>e[n]??(e[n]=r(n))},sr=i(/^\\*\\*\\/\\*$/,\".*\"),cr=i(/^\\*\\*\\/(\\*)?([ a-zA-Z0-9._-]+)$/,(r,e,n)=>`.*${e?\"\":\"(?:^|/)\"}${n.replaceAll(\".\",\"\\\\.\")}`),lr=i(/^\\*\\*\\/(\\*)?([ a-zA-Z0-9._-]*)\\{([ a-zA-Z0-9._-]+(?:,[ a-zA-Z0-9._-]+)*)\\}$/,(r,e,n,a)=>`.*${e?\"\":\"(?:^|/)\"}${n.replaceAll(\".\",\"\\\\.\")}(?:${a.replaceAll(\",\",\"|\").replaceAll(\".\",\"\\\\.\")})`),y=i(/\\\\./,p),pr=i(/[$.*+?^(){}[\\]\\|]/,r=>`\\\\${r}`),vr=i(/./,p),hr=i(/^(?:!!)*!(.*)$/,(r,e)=>`(?!^${L(e)}$).*?`),dr=i(/^(!!)+/,\"\"),fr=l([hr,dr]),xr=i(/\\/(\\*\\*\\/)+/,\"(?:/.+/|/)\"),gr=i(/^(\\*\\*\\/)+/,\"(?:^|.*/)\"),mr=i(/\\/(\\*\\*)$/,\"(?:/.*|$)\"),_r=i(/\\*\\*/,\".*\"),j=l([xr,gr,mr,_r]),Sr=i(/\\*\\/(?!\\*\\*\\/)/,\"[^/]*/\"),yr=i(/\\*/,\"[^/]*\"),N=l([Sr,yr]),k=i(\"?\",\"[^/]\"),$r=i(\"[\",p),wr=i(\"]\",p),Ar=i(/[!^]/,\"^/\"),br=i(/[a-z]-[a-z]|[0-9]-[0-9]/i,p),Cr=i(/[$.*+?^(){}[\\|]/,r=>`\\\\${r}`),Mr=i(/[^\\]]/,p),Er=l([y,Cr,br,Mr]),B=x([$r,tr(Ar),f(Er),wr]),Pr=i(\"{\",\"(?:\"),Or=i(\"}\",\")\"),Rr=i(/(\\d+)\\.\\.(\\d+)/,(r,e,n)=>or(+e,+n,Math.min(e.length,n.length)).join(\"|\")),zr=i(/([a-z]+)\\.\\.([a-z]+)/,(r,e,n)=>R(e,n).join(\"|\")),jr=i(/([A-Z]+)\\.\\.([A-Z]+)/,(r,e,n)=>R(e.toLowerCase(),n.toLowerCase()).join(\"|\").toUpperCase()),Nr=l([Rr,zr,jr]),I=x([Pr,Nr,Or]),kr=i(\"{\",\"(?:\"),Br=i(\"}\",\")\"),Ir=i(\",\",\"|\"),Fr=i(/[$.*+?^(){[\\]\\|]/,r=>`\\\\${r}`),Lr=i(/[^}]/,p),Zr=E(()=>F),Dr=l([j,N,k,B,I,Zr,y,Fr,Ir,Lr]),F=x([kr,f(Dr),Br]),Ur=f(l([sr,cr,lr,fr,j,N,k,B,I,F,y,pr,vr])),Vr=Ur,Gr=z(Vr),L=Gr,Tr=i(/\\\\./,p),qr=i(/./,p),Hr=i(/\\*\\*\\*+/,\"*\"),Jr=i(/([^/{[(!])\\*\\*/,(r,e)=>`${e}*`),Qr=i(/(^|.)\\*\\*(?=[^*/)\\]}])/,(r,e)=>`${e}*`),Wr=f(l([Tr,Hr,Jr,Qr,qr])),Kr=Wr,Xr=z(Kr),Yr=Xr,$=(r,e)=>{const n=Array.isArray(r)?r:[r];if(!n.length)return false;const a=n.map($.compile),t=n.every(s=>/(\\/(?:\\*\\*)?|\\[\\/\\])$/.test(s)),o=e.replace(/[\\\\\\/]+/g,\"/\").replace(/\\/$/,t?\"/\":\"\");return a.some(s=>s.test(o))};$.compile=r=>new RegExp(`^${L(Yr(r))}$`,\"s\");var re=$;return J(w)})();\n return __lib__.default || __lib__; };\nlet _match;\nconst zeptomatch = (path, pattern) => {\n if (!_match) {\n _match = _lazyMatch();\n _lazyMatch = null;\n }\n return _match(path, pattern);\n};\n\nconst _DRIVE_LETTER_START_RE = /^[A-Za-z]:\\//;\nfunction normalizeWindowsPath(input = \"\") {\n if (!input) {\n return input;\n }\n return input.replace(/\\\\/g, \"/\").replace(_DRIVE_LETTER_START_RE, (r) => r.toUpperCase());\n}\n\nconst _UNC_REGEX = /^[/\\\\]{2}/;\nconst _IS_ABSOLUTE_RE = /^[/\\\\](?![/\\\\])|^[/\\\\]{2}(?!\\.)|^[A-Za-z]:[/\\\\]/;\nconst _DRIVE_LETTER_RE = /^[A-Za-z]:$/;\nconst _ROOT_FOLDER_RE = /^\\/([A-Za-z]:)?$/;\nconst _EXTNAME_RE = /.(\\.[^./]+|\\.)$/;\nconst _PATH_ROOT_RE = /^[/\\\\]|^[a-zA-Z]:[/\\\\]/;\nconst sep = \"/\";\nconst normalize = function(path) {\n if (path.length === 0) {\n return \".\";\n }\n path = normalizeWindowsPath(path);\n const isUNCPath = path.match(_UNC_REGEX);\n const isPathAbsolute = isAbsolute(path);\n const trailingSeparator = path[path.length - 1] === \"/\";\n path = normalizeString(path, !isPathAbsolute);\n if (path.length === 0) {\n if (isPathAbsolute) {\n return \"/\";\n }\n return trailingSeparator ? \"./\" : \".\";\n }\n if (trailingSeparator) {\n path += \"/\";\n }\n if (_DRIVE_LETTER_RE.test(path)) {\n path += \"/\";\n }\n if (isUNCPath) {\n if (!isPathAbsolute) {\n return `//./${path}`;\n }\n return `//${path}`;\n }\n return isPathAbsolute && !isAbsolute(path) ? `/${path}` : path;\n};\nconst join = function(...segments) {\n let path = \"\";\n for (const seg of segments) {\n if (!seg) {\n continue;\n }\n if (path.length > 0) {\n const pathTrailing = path[path.length - 1] === \"/\";\n const segLeading = seg[0] === \"/\";\n const both = pathTrailing && segLeading;\n if (both) {\n path += seg.slice(1);\n } else {\n path += pathTrailing || segLeading ? seg : `/${seg}`;\n }\n } else {\n path += seg;\n }\n }\n return normalize(path);\n};\nfunction cwd() {\n if (typeof process !== \"undefined\" && typeof process.cwd === \"function\") {\n return process.cwd().replace(/\\\\/g, \"/\");\n }\n return \"/\";\n}\nconst resolve = function(...arguments_) {\n arguments_ = arguments_.map((argument) => normalizeWindowsPath(argument));\n let resolvedPath = \"\";\n let resolvedAbsolute = false;\n for (let index = arguments_.length - 1; index >= -1 && !resolvedAbsolute; index--) {\n const path = index >= 0 ? arguments_[index] : cwd();\n if (!path || path.length === 0) {\n continue;\n }\n resolvedPath = `${path}/${resolvedPath}`;\n resolvedAbsolute = isAbsolute(path);\n }\n resolvedPath = normalizeString(resolvedPath, !resolvedAbsolute);\n if (resolvedAbsolute && !isAbsolute(resolvedPath)) {\n return `/${resolvedPath}`;\n }\n return resolvedPath.length > 0 ? resolvedPath : \".\";\n};\nfunction normalizeString(path, allowAboveRoot) {\n let res = \"\";\n let lastSegmentLength = 0;\n let lastSlash = -1;\n let dots = 0;\n let char = null;\n for (let index = 0; index <= path.length; ++index) {\n if (index < path.length) {\n char = path[index];\n } else if (char === \"/\") {\n break;\n } else {\n char = \"/\";\n }\n if (char === \"/\") {\n if (lastSlash === index - 1 || dots === 1) ; else if (dots === 2) {\n if (res.length < 2 || lastSegmentLength !== 2 || res[res.length - 1] !== \".\" || res[res.length - 2] !== \".\") {\n if (res.length > 2) {\n const lastSlashIndex = res.lastIndexOf(\"/\");\n if (lastSlashIndex === -1) {\n res = \"\";\n lastSegmentLength = 0;\n } else {\n res = res.slice(0, lastSlashIndex);\n lastSegmentLength = res.length - 1 - res.lastIndexOf(\"/\");\n }\n lastSlash = index;\n dots = 0;\n continue;\n } else if (res.length > 0) {\n res = \"\";\n lastSegmentLength = 0;\n lastSlash = index;\n dots = 0;\n continue;\n }\n }\n if (allowAboveRoot) {\n res += res.length > 0 ? \"/..\" : \"..\";\n lastSegmentLength = 2;\n }\n } else {\n if (res.length > 0) {\n res += `/${path.slice(lastSlash + 1, index)}`;\n } else {\n res = path.slice(lastSlash + 1, index);\n }\n lastSegmentLength = index - lastSlash - 1;\n }\n lastSlash = index;\n dots = 0;\n } else if (char === \".\" && dots !== -1) {\n ++dots;\n } else {\n dots = -1;\n }\n }\n return res;\n}\nconst isAbsolute = function(p) {\n return _IS_ABSOLUTE_RE.test(p);\n};\nconst toNamespacedPath = function(p) {\n return normalizeWindowsPath(p);\n};\nconst extname = function(p) {\n if (p === \"..\") return \"\";\n const match = _EXTNAME_RE.exec(normalizeWindowsPath(p));\n return match && match[1] || \"\";\n};\nconst relative = function(from, to) {\n const _from = resolve(from).replace(_ROOT_FOLDER_RE, \"$1\").split(\"/\");\n const _to = resolve(to).replace(_ROOT_FOLDER_RE, \"$1\").split(\"/\");\n if (_to[0][1] === \":\" && _from[0][1] === \":\" && _from[0] !== _to[0]) {\n return _to.join(\"/\");\n }\n const _fromCopy = [..._from];\n for (const segment of _fromCopy) {\n if (_to[0] !== segment) {\n break;\n }\n _from.shift();\n _to.shift();\n }\n return [..._from.map(() => \"..\"), ..._to].join(\"/\");\n};\nconst dirname = function(p) {\n const segments = normalizeWindowsPath(p).replace(/\\/$/, \"\").split(\"/\").slice(0, -1);\n if (segments.length === 1 && _DRIVE_LETTER_RE.test(segments[0])) {\n segments[0] += \"/\";\n }\n return segments.join(\"/\") || (isAbsolute(p) ? \"/\" : \".\");\n};\nconst format = function(p) {\n const ext = p.ext ? p.ext.startsWith(\".\") ? p.ext : `.${p.ext}` : \"\";\n const segments = [p.root, p.dir, p.base ?? (p.name ?? \"\") + ext].filter(\n Boolean\n );\n return normalizeWindowsPath(\n p.root ? resolve(...segments) : segments.join(\"/\")\n );\n};\nconst basename = function(p, extension) {\n const segments = normalizeWindowsPath(p).split(\"/\");\n let lastSegment = \"\";\n for (let i = segments.length - 1; i >= 0; i--) {\n const val = segments[i];\n if (val) {\n lastSegment = val;\n break;\n }\n }\n return extension && lastSegment.endsWith(extension) ? lastSegment.slice(0, -extension.length) : lastSegment;\n};\nconst parse = function(p) {\n const root = _PATH_ROOT_RE.exec(p)?.[0]?.replace(/\\\\/g, \"/\") || \"\";\n const base = basename(p);\n const extension = extname(base);\n return {\n root,\n dir: dirname(p),\n base,\n ext: extension,\n name: base.slice(0, base.length - extension.length)\n };\n};\nconst matchesGlob = (path, pattern) => {\n return zeptomatch(pattern, normalize(path));\n};\n\nconst _path = {\n __proto__: null,\n basename: basename,\n dirname: dirname,\n extname: extname,\n format: format,\n isAbsolute: isAbsolute,\n join: join,\n matchesGlob: matchesGlob,\n normalize: normalize,\n normalizeString: normalizeString,\n parse: parse,\n relative: relative,\n resolve: resolve,\n sep: sep,\n toNamespacedPath: toNamespacedPath\n};\n\nconst delimiter = /* @__PURE__ */ (() => globalThis.process?.platform === \"win32\" ? \";\" : \":\")();\nconst _platforms = { posix: void 0, win32: void 0 };\nconst mix = (del = delimiter) => {\n return new Proxy(_path, {\n get(_, prop) {\n if (prop === \"delimiter\") return del;\n if (prop === \"posix\") return posix;\n if (prop === \"win32\") return win32;\n return _platforms[prop] || _path[prop];\n }\n });\n};\nconst posix = /* @__PURE__ */ mix(\":\");\nconst win32 = /* @__PURE__ */ mix(\";\");\n\nvar pathe = /*#__PURE__*/Object.freeze({\n __proto__: null,\n basename: basename,\n default: posix,\n delimiter: delimiter,\n dirname: dirname,\n extname: extname,\n format: format,\n isAbsolute: isAbsolute,\n join: join,\n matchesGlob: matchesGlob,\n normalize: normalize,\n normalizeString: normalizeString,\n parse: parse,\n posix: posix,\n relative: relative,\n resolve: resolve,\n sep: sep,\n toNamespacedPath: toNamespacedPath,\n win32: win32\n});\n\nglobalThis.pathHandler = pathe;\n";
8683
+
8684
+ // Set by index.js (Node.js) or index.browser.js (shim) — never imported directly.
8685
+ let evaluatorStorage = null;
8686
+
8687
+ function setDefaultAsyncLocalStorage$1(cls) {
8688
+ evaluatorStorage = cls ? new cls() : null;
8689
+ }
8683
8690
 
8684
8691
  /**
8685
8692
  * Runs fn inside an isolated evaluator context.
8686
8693
  * Concurrent transpile() calls each get their own stack — no cross-contamination.
8687
8694
  */
8688
8695
  function withEvaluator(fn) {
8696
+ if (!evaluatorStorage) return fn();
8689
8697
  return evaluatorStorage.run([], fn);
8690
8698
  }
8691
8699
 
@@ -8814,7 +8822,7 @@ const customFetchAdapter = async (input, init, security = {}) => {
8814
8822
  };
8815
8823
  };
8816
8824
 
8817
- const customCompileAdapter = async (src, options, parentSecurity = {}) => {
8825
+ const customCompileAdapter = async (src, options, parentSecurity = {}, parentFs = null, parentBaseDir = null) => {
8818
8826
  const maxDepth = parentSecurity?.maxDepth ?? 5;
8819
8827
  if (globalCompilationDepth >= maxDepth) {
8820
8828
  throw new Error(`Recursion Guard: Maximum Smark compilation depth exceeded (limit is ${maxDepth}).`);
@@ -8830,7 +8838,9 @@ const customCompileAdapter = async (src, options, parentSecurity = {}) => {
8830
8838
  ...cleanOptions,
8831
8839
  src,
8832
8840
  format: cleanOptions.format || "html",
8833
- security: parentSecurity
8841
+ security: parentSecurity,
8842
+ fs: parentFs ?? undefined,
8843
+ baseDir: cleanOptions.baseDir || parentBaseDir || undefined,
8834
8844
  };
8835
8845
  const sm = new compilerClass(compilerOptions);
8836
8846
  return await sm.transpile();
@@ -8843,6 +8853,7 @@ const customCompileAdapter = async (src, options, parentSecurity = {}) => {
8843
8853
  registerHostCompile(customCompileAdapter);
8844
8854
 
8845
8855
  let defaultFs$1 = null;
8856
+ let defaultEnv = null;
8846
8857
  let quickJSInstance = null;
8847
8858
  async function getQuickJSModule() {
8848
8859
  if (!quickJSInstance) {
@@ -8866,6 +8877,16 @@ function objectToHandle(context, obj) {
8866
8877
  return result.unwrap();
8867
8878
  }
8868
8879
 
8880
+ function isPlainData(value, seen = new Set()) {
8881
+ if (value === null || value === undefined) return true;
8882
+ if (typeof value === "function") return false;
8883
+ if (typeof value !== "object") return true;
8884
+ if (seen.has(value)) return false;
8885
+ seen.add(value);
8886
+ if (Array.isArray(value)) return value.every(v => isPlainData(v, seen));
8887
+ return Object.values(value).every(v => isPlainData(v, seen));
8888
+ }
8889
+
8869
8890
  function expose(context, vars, pendingDeferreds) {
8870
8891
  for (const [key, value] of Object.entries(vars)) {
8871
8892
  let handle;
@@ -8960,6 +8981,7 @@ class EvaluatorState {
8960
8981
  } else {
8961
8982
  this.baseDir = "/";
8962
8983
  }
8984
+ this.rootDir = settings?.instance?.cwd || this.baseDir;
8963
8985
  this.scopes = [{}];
8964
8986
  this.dynamicTagsStack = [new Map()];
8965
8987
  this.security = security;
@@ -8986,15 +9008,39 @@ class EvaluatorState {
8986
9008
  });
8987
9009
 
8988
9010
  this.expose({
9011
+ __hostEnv: (key) => {
9012
+ if (defaultEnv === null) {
9013
+ throw new Error(
9014
+ "[SomMark] SomMark.env() is not available in browser mode.\n" +
9015
+ "Environment variables are a server-side concept.\n" +
9016
+ "Read env values at build time and pass them as placeholders instead."
9017
+ );
9018
+ }
9019
+ const allowlist = this.security?.env;
9020
+ if (!Array.isArray(allowlist) || !allowlist.includes(key)) return undefined;
9021
+ return defaultEnv[key] ?? undefined;
9022
+ },
8989
9023
  __hostSomMarkVersion: SomMark$1.version,
8990
9024
  __hostSomMarkSettings: () => {
8991
- const clean = { ...SomMark$1.settings };
8992
- delete clean.instance;
8993
- delete clean.fs;
8994
- return JSON.stringify(clean);
8995
- },
9025
+ const s = SomMark$1.settings;
9026
+ return JSON.stringify({
9027
+ format: s.format ?? null,
9028
+ dev: s.dev ?? false,
9029
+ removeComments:s.removeComments ?? false,
9030
+ allowRaw: s.allowRaw ?? true,
9031
+ dualOutput: s.dualOutput ?? false,
9032
+ webOutputs: s.webOutputs ?? false,
9033
+ });
9034
+ },
8996
9035
  __hostCompile: async (src, options) => {
8997
- return await customCompileAdapter(src, options, this.security);
9036
+ return await customCompileAdapter(src, options, this.security, this.nodeFs, this.baseDir);
9037
+ },
9038
+ __hostLexer: (src, filename) => {
9039
+ return JSON.stringify(lexer(src, filename || "anonymous"));
9040
+ },
9041
+ __hostParser: (src, filename) => {
9042
+ const tokens = lexer(src, filename || "anonymous");
9043
+ return JSON.stringify(parser(tokens, filename || "anonymous"));
8998
9044
  },
8999
9045
  __hostFetch: async (input, initStr) => {
9000
9046
  const init = initStr ? JSON.parse(initStr) : undefined;
@@ -9022,6 +9068,59 @@ class EvaluatorState {
9022
9068
  const payload = JSON.parse(payloadStr);
9023
9069
  return await target.render.call(this.mapperFile, payload);
9024
9070
  },
9071
+ __hostFileRead: async (filePath) => {
9072
+ if (!this.nodeFs) {
9073
+ throw new Error(
9074
+ "[SomMark] fileHandler is not available in browser mode.\n" +
9075
+ "File access is a server-side concept."
9076
+ );
9077
+ }
9078
+ const abs = posix.resolve(this.rootDir, filePath);
9079
+ if (!abs.startsWith(this.rootDir)) {
9080
+ throw new Error(
9081
+ `[SomMark] fileHandler.read: path traversal outside project root is not allowed.\n` +
9082
+ `Attempted path: ${abs}`
9083
+ );
9084
+ }
9085
+ return this.nodeFs.readFile(abs, "utf-8");
9086
+ },
9087
+ __hostFileExists: async (filePath) => {
9088
+ if (!this.nodeFs) return false;
9089
+ const abs = posix.resolve(this.rootDir, filePath);
9090
+ if (!abs.startsWith(this.rootDir)) return false;
9091
+ return this.nodeFs.exists(abs);
9092
+ },
9093
+ __hostFileGlob: async (pattern) => {
9094
+ if (!this.nodeFs) throw new Error("[SomMark] fileHandler.glob is not available in browser mode.\nFile access is a server-side concept.");
9095
+ if (!this.nodeFs.glob) throw new Error("[SomMark] fileHandler.glob requires Node.js 22 or later.");
9096
+ const files = await this.nodeFs.glob(pattern, { cwd: this.rootDir });
9097
+ return JSON.stringify(files);
9098
+ },
9099
+ __hostFileLastModified: async (filePath) => {
9100
+ if (!this.nodeFs) throw new Error("[SomMark] fileHandler.lastModified is not available in browser mode.");
9101
+ const abs = posix.resolve(this.rootDir, filePath);
9102
+ if (!abs.startsWith(this.rootDir)) throw new Error("[SomMark] fileHandler.lastModified: path traversal outside project root is not allowed.");
9103
+ const stat = await this.nodeFs.stat(abs);
9104
+ return stat.mtimeMs;
9105
+ },
9106
+ __hostFileStat: async (filePath) => {
9107
+ if (!this.nodeFs) throw new Error("[SomMark] fileHandler.stat is not available in browser mode.\nFile access is a server-side concept.");
9108
+ const abs = posix.resolve(this.rootDir, filePath);
9109
+ if (!abs.startsWith(this.rootDir)) throw new Error(`[SomMark] fileHandler.stat: path traversal outside project root is not allowed.\nAttempted path: ${abs}`);
9110
+ try {
9111
+ const s = await this.nodeFs.stat(abs);
9112
+ return JSON.stringify({
9113
+ size: s.size,
9114
+ mtime: s.mtimeMs,
9115
+ ctime: s.ctimeMs,
9116
+ atime: s.atimeMs,
9117
+ isFile: s.isFile(),
9118
+ isDirectory: s.isDirectory(),
9119
+ });
9120
+ } catch {
9121
+ return null;
9122
+ }
9123
+ },
9025
9124
  __allowRaw: this.security.allowRaw !== false
9026
9125
  });
9027
9126
 
@@ -9174,6 +9273,18 @@ class EvaluatorState {
9174
9273
  }
9175
9274
  return await __hostCompile(src, options);
9176
9275
  },
9276
+ lexer: (src, filename) => {
9277
+ if (typeof src !== "string") {
9278
+ throw new Error("SomMark.lexer Error: Source must be a string.");
9279
+ }
9280
+ return JSON.parse(__hostLexer(src, filename));
9281
+ },
9282
+ parser: (src, filename) => {
9283
+ if (typeof src !== "string") {
9284
+ throw new Error("SomMark.parser Error: Source must be a string.");
9285
+ }
9286
+ return JSON.parse(__hostParser(src, filename));
9287
+ },
9177
9288
  raw: (html) => {
9178
9289
  if (typeof __allowRaw !== "undefined" && !__allowRaw) {
9179
9290
  throw new Error("Security Error: SomMark.raw is disabled in this environment.");
@@ -9197,6 +9308,12 @@ class EvaluatorState {
9197
9308
  throw new Error("SomMark.static Error: Argument must be a string.");
9198
9309
  }
9199
9310
  return globalThis.eval(expr);
9311
+ },
9312
+ env: (key) => {
9313
+ if (typeof key !== "string" || !key) {
9314
+ throw new Error("SomMark.env Error: Key must be a non-empty string.");
9315
+ }
9316
+ return __hostEnv(key);
9200
9317
  }
9201
9318
  };
9202
9319
 
@@ -9208,6 +9325,20 @@ class EvaluatorState {
9208
9325
  configurable: false
9209
9326
  });
9210
9327
 
9328
+ Object.defineProperty(globalThis, "Smark", {
9329
+ value: SomMark,
9330
+ writable: false,
9331
+ configurable: false
9332
+ });
9333
+
9334
+ globalThis.fileHandler = Object.freeze({
9335
+ read: async (path) => await __hostFileRead(path),
9336
+ exists: async (path) => await __hostFileExists(path),
9337
+ glob: async (pattern) => JSON.parse(await __hostFileGlob(pattern)),
9338
+ lastModified: async (path) => await __hostFileLastModified(path),
9339
+ stat: async (path) => { const r = await __hostFileStat(path); return r ? JSON.parse(r) : null; },
9340
+ });
9341
+
9211
9342
  delete globalThis.fetch;
9212
9343
  delete globalThis.process;
9213
9344
  `);
@@ -9219,15 +9350,27 @@ class EvaluatorState {
9219
9350
  }
9220
9351
  setupRes.value.dispose();
9221
9352
 
9222
- // Configure module loader using virtual FS implementation
9353
+ const patheRes = this.context.evalCode(patheBundleCode);
9354
+ if (patheRes.error) {
9355
+ patheRes.error.dispose();
9356
+ } else {
9357
+ patheRes.value.dispose();
9358
+ }
9359
+
9360
+ // Configure module loader using virtual FS implementation.
9361
+ // The normalizer resolves every import to an absolute path so the module
9362
+ // cache key is always absolute — <smark> (the eval module name) can never
9363
+ // be reached by any user import regardless of what the file is named.
9223
9364
  this.runtime.setModuleLoader((moduleName) => {
9224
9365
  try {
9225
9366
  const isRaw = moduleName.endsWith("?raw");
9226
9367
  const cleanModuleName = isRaw ? moduleName.slice(0, -4) : moduleName;
9227
- const resolvedPath = /^https?:\/\//.test(this.baseDir)
9228
- ? new URL(cleanModuleName, this.baseDir.endsWith("/") ? this.baseDir : this.baseDir + "/").href
9368
+ // moduleName is already an absolute path (supplied by the normalizer below),
9369
+ // so resolve() is a no-op for absolute paths and a safe fallback for URLs.
9370
+ const resolvedPath = /^https?:\/\//.test(cleanModuleName)
9371
+ ? cleanModuleName
9229
9372
  : posix.resolve(this.baseDir, cleanModuleName);
9230
-
9373
+
9231
9374
  const fsImpl = this.settings?.fs || this.settings?.instance?.fs || this.nodeFs;
9232
9375
  if (!fsImpl) {
9233
9376
  throw new Error("No filesystem implementation available.");
@@ -9260,6 +9403,22 @@ class EvaluatorState {
9260
9403
  } catch (err) {
9261
9404
  throw err;
9262
9405
  }
9406
+ }, (baseName, moduleName) => {
9407
+ // Resolve every import to an absolute path so no user import can ever
9408
+ // normalize to <smark> (or any other virtual eval module name).
9409
+ const isRaw = moduleName.endsWith("?raw");
9410
+ const clean = isRaw ? moduleName.slice(0, -4) : moduleName;
9411
+ if (/^https?:\/\//.test(clean)) return moduleName;
9412
+ const baseDir = (baseName === "<smark>" || !posix.isAbsolute(baseName))
9413
+ ? this.baseDir
9414
+ : (/^https?:\/\//.test(baseName) ? baseName : posix.dirname(baseName));
9415
+ let resolved;
9416
+ if (/^https?:\/\//.test(baseDir)) {
9417
+ resolved = new URL(clean, baseDir).href;
9418
+ } else {
9419
+ resolved = posix.resolve(baseDir, clean);
9420
+ }
9421
+ return isRaw ? resolved + "?raw" : resolved;
9263
9422
  });
9264
9423
  }
9265
9424
 
@@ -9410,9 +9569,24 @@ class EvaluatorState {
9410
9569
 
9411
9570
  inject(vars) {
9412
9571
  if (!this.context) return;
9572
+ const safe = {};
9573
+ for (const [key, value] of Object.entries(vars)) {
9574
+ if (typeof value === "function") {
9575
+ const src = value.toString();
9576
+ if (src.includes("SomMark.")) {
9577
+ console.warn(`[SomMark] variables.${key}: references 'SomMark' which bundlers may rename. Use 'Smark' instead.`);
9578
+ }
9579
+ const res = this.context.evalCode(`globalThis[${JSON.stringify(key)}] = ${src}`);
9580
+ if (res.error) res.error.dispose();
9581
+ else res.value.dispose();
9582
+ continue;
9583
+ }
9584
+ if (!isPlainData(value)) continue;
9585
+ safe[key] = value;
9586
+ }
9413
9587
  const currentScope = this.scopes[this.scopes.length - 1];
9414
- Object.assign(currentScope, vars);
9415
- this.expose(vars);
9588
+ Object.assign(currentScope, safe);
9589
+ this.expose(safe);
9416
9590
  }
9417
9591
 
9418
9592
  async execute(code, baseDir = null) {
@@ -9482,7 +9656,16 @@ class EvaluatorState {
9482
9656
  }
9483
9657
  }
9484
9658
  } catch (err) {
9485
- // Ignore parsing errors and fallback to raw code
9659
+ // Parse failed as a statement try as a parenthesised expression.
9660
+ // This handles object/array literals like {a: 1} or [1, 2] which are
9661
+ // ambiguous in statement context but valid when wrapped in parens.
9662
+ try {
9663
+ const trimmed = code.trim();
9664
+ parse$1(`(${trimmed})`, { ecmaVersion: 'latest', sourceType: 'module' });
9665
+ finalCode = `export default (${trimmed});`;
9666
+ } catch {
9667
+ // Give up — let QuickJS surface the error.
9668
+ }
9486
9669
  }
9487
9670
 
9488
9671
  if (autoExportedNames.length > 0 && !hasExplicitExports) {
@@ -9496,7 +9679,7 @@ class EvaluatorState {
9496
9679
 
9497
9680
  let result;
9498
9681
  if (isModule) {
9499
- const evalRes = this.context.evalCode(finalCode, "main.js", { type: 'module' });
9682
+ const evalRes = this.context.evalCode(finalCode, "<smark>", { type: 'module' });
9500
9683
  if (evalRes.error) {
9501
9684
  const err = this.context.dump(evalRes.error);
9502
9685
  evalRes.error.dispose();
@@ -9565,7 +9748,7 @@ class EvaluatorState {
9565
9748
  }
9566
9749
 
9567
9750
  const defaultValue = this.context.dump(resolvedDefaultHandle);
9568
-
9751
+
9569
9752
  if (isPromise) {
9570
9753
  resolvedDefaultHandle.dispose();
9571
9754
  }
@@ -9605,7 +9788,7 @@ class EvaluatorState {
9605
9788
  result = res;
9606
9789
  }
9607
9790
  } else {
9608
- const evalRes = this.context.evalCode(code, "main.js");
9791
+ const evalRes = this.context.evalCode(code, "<smark>");
9609
9792
  if (evalRes.error) {
9610
9793
  const err = this.context.dump(evalRes.error);
9611
9794
  evalRes.error.dispose();
@@ -9641,7 +9824,7 @@ class EvaluatorState {
9641
9824
  return result;
9642
9825
  } catch (error) {
9643
9826
  const stack = error.stack || "";
9644
- const match = stack.match(/main\.js:(\d+):(\d+)/) || stack.match(/:(\d+):(\d+)/);
9827
+ const match = stack.match(/__smark__\.js:(\d+):(\d+)/) || stack.match(/:(\d+):(\d+)/);
9645
9828
 
9646
9829
  const err = new Error(error.message || error);
9647
9830
  if (match) {
@@ -9704,6 +9887,14 @@ class Evaluator {
9704
9887
  defaultFs$1 = fs;
9705
9888
  }
9706
9889
 
9890
+ setDefaultEnv(env) {
9891
+ defaultEnv = env;
9892
+ }
9893
+
9894
+ setDefaultAsyncLocalStorage(cls) {
9895
+ setDefaultAsyncLocalStorage$1(cls);
9896
+ }
9897
+
9707
9898
  get active() {
9708
9899
  const stack = this._getStack();
9709
9900
  if (stack.length === 0) {
@@ -9916,8 +10107,27 @@ async function preprocessRuntimeLogic(code, filename = null, security = {}, inst
9916
10107
  if (filename && filename !== "anonymous") {
9917
10108
  baseDir = posix.dirname(posix.resolve(filename));
9918
10109
  }
10110
+
10111
+ // Block absolute paths — path.resolve would ignore baseDir entirely
10112
+ if (posix.isAbsolute(argValue)) {
10113
+ transpilerError([
10114
+ `<$red:Security Error:$> Absolute import paths are not allowed: <$magenta:${argValue}$>{line}`,
10115
+ `<$yellow:Use a path relative to the template file, e.g.$> <$green:SomMark.import("./data.json")$> <$yellow:or$> <$green:SomMark.import("../shared/data.json")$><$yellow:.$>{line}`,
10116
+ `<$yellow:Base directory:$> <$blue:${baseDir}$>{line}`
10117
+ ]);
10118
+ }
10119
+
9919
10120
  const resolvedPath = posix.resolve(baseDir, argValue);
9920
10121
 
10122
+ // Block path traversal — resolved path must stay inside baseDir
10123
+ const safeBases = baseDir.endsWith(posix.sep) ? baseDir : baseDir + posix.sep;
10124
+ if (!resolvedPath.startsWith(safeBases) && resolvedPath !== baseDir) {
10125
+ transpilerError([
10126
+ `<$red:Security Error:$> Import path escapes the project directory: <$magenta:${argValue}$>{line}`,
10127
+ `<$yellow:Resolved Path:$> <$blue:${resolvedPath}$>{line}`
10128
+ ]);
10129
+ }
10130
+
9921
10131
  const fsImpl = instance?.fs || await getNodeFs();
9922
10132
 
9923
10133
  // File presence validation
@@ -10013,7 +10223,7 @@ function warnDroppedVariables(variables) {
10013
10223
  } else if (value !== null && typeof value === "object" && !Array.isArray(value)) {
10014
10224
  for (const [nestedKey, nestedVal] of Object.entries(value)) {
10015
10225
  if (typeof nestedVal === "function") {
10016
- console.warn(`[SomMark] variables.${key}.${nestedKey} is a function nested inside an object and will be ignored. Move it to the top level: variables.${nestedKey}`);
10226
+ console.warn(`[SomMark] variables.${key}.${nestedKey}: nested functions inside objects are not supported. Define it as a top-level function instead: variables.${nestedKey}`);
10017
10227
  } else if (nestedVal === undefined) {
10018
10228
  console.warn(`[SomMark] variables.${key}.${nestedKey} is undefined and will be ignored.`);
10019
10229
  }
@@ -10030,6 +10240,7 @@ const randomBytesHex = (size) => {
10030
10240
 
10031
10241
  const BODY_PLACEHOLDER = `SOMMARKBODYPLACEHOLDER${randomBytesHex(8)}SOMMARK`;
10032
10242
 
10243
+
10033
10244
  /**
10034
10245
  * Extracts all plain text from a node and its children.
10035
10246
  *
@@ -10119,15 +10330,28 @@ async function generateOutput(ast, i, format, mapper_file, security = {}, parent
10119
10330
  const out = (result !== undefined && typeof result !== "object") ? String(result) : "";
10120
10331
  return mapper_file ? mapper_file.text(out) : out;
10121
10332
  } catch (err) {
10333
+ const line = node.range?.start?.line + 1 || 1;
10122
10334
  transpilerError([
10123
10335
  `<$red:Logic Error:$> ${err.message}{line}`,
10124
- `<$yellow:Code:$> <$blue:${node.code}$>{line}`
10336
+ `<$yellow:Code:$> <$blue:${node.code}$>{line}`,
10337
+ `at line <$yellow:${line}$>{line}`
10125
10338
  ]);
10126
10339
  }
10127
10340
  }
10128
10341
 
10129
10342
  if (node.type === FOR_EACH) {
10130
10343
  const transpiledArgs = await transpileArgs(node.props);
10344
+
10345
+ if (!node.props || (node.props[0] === undefined && node.props["items"] === undefined)) {
10346
+ const line = node.range?.start?.line + 1 || 1;
10347
+ transpilerError([
10348
+ `<$red:Missing Prop Error in [for-each]:$>{line}`,
10349
+ `[for-each] requires an array as its first prop, e.g. [for-each = \${ array }\$]{line}`,
10350
+ `at line <$yellow:${line}$>{line}`
10351
+ ]);
10352
+ return "";
10353
+ }
10354
+
10131
10355
  const items = mapper_file ? mapper_file.safeArg({ props: transpiledArgs, index: 0, key: "items", fallBack: [] }) : [];
10132
10356
 
10133
10357
  if (!Array.isArray(items)) {
@@ -10141,11 +10365,11 @@ async function generateOutput(ast, i, format, mapper_file, security = {}, parent
10141
10365
  }
10142
10366
 
10143
10367
  const asVar = transpiledArgs.as || "value";
10144
- if (asVar === "i") {
10368
+ if (asVar === "i" || asVar === "length") {
10145
10369
  const line = node.range?.start?.line + 1 || 1;
10146
10370
  transpilerError([
10147
10371
  `<$red:Reserved Variable Error in [for-each]:$>{line}`,
10148
- `'i' is a reserved variable name for the loop index.{N}Use a different name for the 'as' prop, e.g. as: "item"{line}`,
10372
+ `'${asVar}' is a reserved variable name.{N}Use a different name for the 'as' prop, e.g. as: "item"{line}`,
10149
10373
  `at line <$yellow:${line}$>{line}`
10150
10374
  ]);
10151
10375
  return "";
@@ -10175,22 +10399,28 @@ async function generateOutput(ast, i, format, mapper_file, security = {}, parent
10175
10399
  }
10176
10400
  }
10177
10401
 
10178
- let output = "";
10402
+ const rawJoin = transpiledArgs.join ?? null;
10403
+ const join = rawJoin !== null ? rawJoin.replace(/\\n/g, "\n").replace(/\\t/g, "\t").replace(/\\r/g, "\r") : null;
10404
+ const parts = [];
10179
10405
  let idx = 0;
10406
+ const length = items.length;
10180
10407
  for (const item of items) {
10181
10408
  Evaluator$1.pushScope();
10182
10409
  Evaluator$1.inject({
10183
10410
  [asVar]: item,
10184
- i: idx++
10411
+ i: idx++,
10412
+ length
10185
10413
  });
10186
10414
 
10415
+ let iterOutput = "";
10187
10416
  for (let j = 0; j < cleanedBody.length; j++) {
10188
- output += await generateOutput(cleanedBody, j, format, mapper_file, security, parentId, generateRuntimeOutput, hideRuntimeOutput, instance, idState, extraCtx);
10417
+ iterOutput += await generateOutput(cleanedBody, j, format, mapper_file, security, parentId, generateRuntimeOutput, hideRuntimeOutput, instance, idState, extraCtx);
10189
10418
  }
10190
10419
 
10191
10420
  await Evaluator$1.popScope();
10421
+ parts.push(iterOutput);
10192
10422
  }
10193
- return output;
10423
+ return join !== null ? parts.join(join) : parts.join("");
10194
10424
  }
10195
10425
 
10196
10426
  let secretId = null;
@@ -10218,13 +10448,12 @@ async function generateOutput(ast, i, format, mapper_file, security = {}, parent
10218
10448
  }
10219
10449
 
10220
10450
  // smark-raw block — body collected verbatim by lexer, bypasses normal body processing pipeline
10221
- if (node.type === BLOCK && (node.props?.["smark-raw"] === "true" || node.props?.["smark-raw"] === true)) {
10451
+ if (node.type === BLOCK && (node.directives?.raw === "true" || node.directives?.raw === true)) {
10222
10452
  if (generateRuntimeOutput) return "";
10223
10453
  const rawContent = node.body?.map(n => String(n.text || "")).join("") || "";
10224
- const { "smark-raw": _, ...cleanArgs } = node.props;
10225
- const transpiledArgs = await transpileArgs(cleanArgs);
10454
+ const transpiledArgs = await transpileArgs(node.props);
10226
10455
  if (Evaluator$1.active?.hasDynamicTag?.(node.id)) {
10227
- return await Evaluator$1.active.executeDynamicTag(node.id, { props: transpiledArgs, content: rawContent, textContent: rawContent });
10456
+ return await Evaluator$1.active.executeDynamicTag(node.id, { props: transpiledArgs, directives: node.directives, content: rawContent, textContent: rawContent });
10228
10457
  }
10229
10458
  let rawTarget = mapper_file ? matchedValue(mapper_file.outputs, node.id) : null;
10230
10459
  if (!rawTarget && mapper_file) rawTarget = mapper_file.getUnknownTag(node);
@@ -10232,6 +10461,7 @@ async function generateOutput(ast, i, format, mapper_file, security = {}, parent
10232
10461
  const isManualMode = !!rawTarget.options?.handleAst;
10233
10462
  return await rawTarget.render.call(mapper_file, {
10234
10463
  props: transpiledArgs,
10464
+ directives: node.directives,
10235
10465
  content: rawContent,
10236
10466
  textContent: rawContent,
10237
10467
  ast: isManualMode ? node : undefined,
@@ -10315,9 +10545,11 @@ async function generateOutput(ast, i, format, mapper_file, security = {}, parent
10315
10545
  const val = await Evaluator$1.execute(child.code, child.baseDir || null);
10316
10546
  if (val !== undefined && typeof val !== "object") richText += String(val);
10317
10547
  } catch (err) {
10548
+ const line = child.range?.start?.line + 1 || 1;
10318
10549
  transpilerError([
10319
10550
  `<$red:Logic Error:$> ${err.message}{line}`,
10320
- `<$yellow:Code:$> <$blue:${child.code}$>{line}`
10551
+ `<$yellow:Code:$> <$blue:${child.code}$>{line}`,
10552
+ `at line <$yellow:${line}$>{line}`
10321
10553
  ]);
10322
10554
  }
10323
10555
  } else if (child.type === COMMENT) {
@@ -10343,6 +10575,7 @@ async function generateOutput(ast, i, format, mapper_file, security = {}, parent
10343
10575
 
10344
10576
  return await target.render.call(mapper_file, {
10345
10577
  props: transpiledArgs,
10578
+ directives: node.directives,
10346
10579
  content: "",
10347
10580
  textContent: richText || textContent,
10348
10581
  ast: cleanAst,
@@ -10361,6 +10594,7 @@ async function generateOutput(ast, i, format, mapper_file, security = {}, parent
10361
10594
  }
10362
10595
  result += await target.render.call(mapper_file, {
10363
10596
  props: transpiledArgs,
10597
+ directives: node.directives,
10364
10598
  content,
10365
10599
  textContent,
10366
10600
  ast: new Proxy({}, {
@@ -10442,9 +10676,11 @@ async function generateOutput(ast, i, format, mapper_file, security = {}, parent
10442
10676
  bodyOutput = mapper_file ? mapper_file.text(out, { ...target?.options, escape: parentEscape }) : out;
10443
10677
  }
10444
10678
  } catch (err) {
10679
+ const line = body_node.range?.start?.line + 1 || 1;
10445
10680
  transpilerError([
10446
10681
  `<$red:Logic Error:$> ${err.message}{line}`,
10447
- `<$yellow:Code:$> <$blue:${body_node.code}$>{line}`
10682
+ `<$yellow:Code:$> <$blue:${body_node.code}$>{line}`,
10683
+ `at line <$yellow:${line}$>{line}`
10448
10684
  ]);
10449
10685
  }
10450
10686
  break;
@@ -10543,6 +10779,10 @@ async function transpiler(optionsOrAst, format, mapperFile) {
10543
10779
  })();
10544
10780
 
10545
10781
  const dualOutput = optionsOrAst?.dualOutput || false;
10782
+ const webOutputs = optionsOrAst?.webOutputs || false;
10783
+ if (webOutputs && dualOutput) {
10784
+ throw new Error("[SomMark] Cannot use both 'webOutputs' and 'dualOutput' at the same time. Use 'webOutputs' (returns [html, css, js]) or 'dualOutput' (returns [html, js]).");
10785
+ }
10546
10786
  const placeholders = optionsOrAst?.placeholders || settings?.placeholders || {};
10547
10787
  const variables = optionsOrAst?.variables || settings?.variables || {};
10548
10788
  warnDroppedVariables(variables);
@@ -10557,6 +10797,89 @@ async function transpiler(optionsOrAst, format, mapperFile) {
10557
10797
  let prev_body_node = null;
10558
10798
  let prev_was_silent = false;
10559
10799
 
10800
+ if (webOutputs) {
10801
+ // Use unique markers so [style] content is extracted precisely —
10802
+ // no <style> regex on the final HTML, works with static logic inside [style].
10803
+ const CSS_OPEN = `SOMMARKCSSOPEN${randomBytesHex(8)}SOMMARK`;
10804
+ const CSS_CLOSE = `SOMMARKCSSCLOSE${randomBytesHex(8)}SOMMARK`;
10805
+
10806
+ const webMapper = targetMapper.clone();
10807
+ webMapper.register("style", function ({ content }) {
10808
+ return `${CSS_OPEN}${content}${CSS_CLOSE}`;
10809
+ }, { escape: false });
10810
+ // [head] injects CSS variables as a raw <style> string via this.cssVariables —
10811
+ // override it so those variables go through markers too.
10812
+ webMapper.register("head", function ({ content }) {
10813
+ const varsMarker = this.cssVariables
10814
+ ? `${CSS_OPEN}:root { ${this.cssVariables} }${CSS_CLOSE}\n`
10815
+ : "";
10816
+ return this.tag("head").body(`${varsMarker}${content}`);
10817
+ }, { escape: false });
10818
+
10819
+ const idState = { mode: 'record', ids: [], idx: 0 };
10820
+
10821
+ // HTML pass — [style] blocks emit markers instead of <style> tags
10822
+ let htmlOutput = "";
10823
+ try {
10824
+ for (let i = 0; i < body.length; i++) {
10825
+ const node = body[i];
10826
+ const blockOutput = await generateOutput(body, i, targetFormat, webMapper, security, null, false, true, instance, idState);
10827
+ let finalBlockOutput = blockOutput;
10828
+ if (prev_was_silent && node.type === TEXT$1) finalBlockOutput = finalBlockOutput.replace(/^\n/, "");
10829
+ if (finalBlockOutput) {
10830
+ htmlOutput += finalBlockOutput;
10831
+ prev_was_silent = false;
10832
+ } else {
10833
+ prev_was_silent = true;
10834
+ if ((node.type === COMMENT || node.type === COMMENT_BLOCK) && targetMapper?.options?.removeComments) {
10835
+ const nextNode = body[i + 1];
10836
+ if (nextNode && nextNode.type === TEXT$1 && (nextNode.text === "\n" || nextNode.text === "\r\n")) i++;
10837
+ }
10838
+ }
10839
+ }
10840
+ } finally {
10841
+ Evaluator$1.destroy();
10842
+ }
10843
+
10844
+ // Extract CSS from markers — exact, no HTML regex
10845
+ const cssChunks = [];
10846
+ const markerRe = new RegExp(`${CSS_OPEN}([\\s\\S]*?)${CSS_CLOSE}`, "g");
10847
+ htmlOutput = htmlOutput.replace(markerRe, (_, chunk) => {
10848
+ cssChunks.push(chunk.trim());
10849
+ return "";
10850
+ });
10851
+ const css = cssChunks.join("\n").trim();
10852
+
10853
+ // JS pass — replay IDs so querySelector targets match HTML
10854
+ idState.mode = 'replay';
10855
+ idState.idx = 0;
10856
+ prev_was_silent = false;
10857
+
10858
+ await Evaluator$1.init(fileBaseDir, security, settings, targetMapper);
10859
+ Evaluator$1.inject(placeholders);
10860
+ Evaluator$1.inject(variables);
10861
+
10862
+ let jsOutput = "";
10863
+ try {
10864
+ for (let i = 0; i < body.length; i++) {
10865
+ const node = body[i];
10866
+ const blockOutput = await generateOutput(body, i, targetFormat, targetMapper, security, null, true, false, instance, idState);
10867
+ let finalBlockOutput = blockOutput;
10868
+ if (prev_was_silent && node.type === TEXT$1) finalBlockOutput = finalBlockOutput.replace(/^\n/, "");
10869
+ if (finalBlockOutput) {
10870
+ jsOutput += finalBlockOutput;
10871
+ prev_was_silent = false;
10872
+ } else {
10873
+ prev_was_silent = true;
10874
+ }
10875
+ }
10876
+ } finally {
10877
+ Evaluator$1.destroy();
10878
+ }
10879
+
10880
+ return [htmlOutput.trim(), css, jsOutput.trim()];
10881
+ }
10882
+
10560
10883
  if (dualOutput) {
10561
10884
  const idState = { mode: 'record', ids: [], idx: 0 };
10562
10885
 
@@ -10669,9 +10992,11 @@ async function transpileArgs(props) {
10669
10992
  try {
10670
10993
  result[key] = await Evaluator$1.execute(value.code, value.baseDir || null);
10671
10994
  } catch (err) {
10995
+ const line = value.range?.start?.line + 1 || 1;
10672
10996
  transpilerError([
10673
10997
  `<$red:Logic Error (Argument):$> ${err.message}{line}`,
10674
- `<$yellow:Code:$> <$blue:${value.code}$>{line}`
10998
+ `<$yellow:Code:$> <$blue:${value.code}$>{line}`,
10999
+ `at line <$yellow:${line}$>{line}`
10675
11000
  ]);
10676
11001
  }
10677
11002
  } else {
@@ -11784,11 +12109,20 @@ class Mapper {
11784
12109
 
11785
12110
  /**
11786
12111
  * Registers universal utility blocks shared across all SomMark mappers.
11787
- * These blocks are considered "Format Agnostic."
11788
12112
  *
11789
12113
  * @param {Mapper} mapper - The mapper instance to register tags on.
11790
12114
  */
11791
12115
  function registerSharedOutputs(mapper) {
12116
+ mapper.register(
12117
+ ["raw", "Raw"],
12118
+ ({ content }) => {
12119
+ return content;
12120
+ },
12121
+ {
12122
+ escape: false, rules: {
12123
+ required_directives: ["raw"]
12124
+ } }
12125
+ );
11792
12126
  }
11793
12127
 
11794
12128
  const SVG_ELEMENTS = new Set([
@@ -11941,6 +12275,7 @@ HTML.register(
11941
12275
  return "";
11942
12276
  },
11943
12277
  );
12278
+ registerSharedOutputs(HTML);
11944
12279
 
11945
12280
  /**
11946
12281
  * The Markdown Mapper used for generating Markdown text.
@@ -11967,42 +12302,37 @@ const MARKDOWN = Mapper.define({
11967
12302
  },
11968
12303
 
11969
12304
  /**
11970
- * Provides a fallback for unknown tags by using the HTML mapper instead.
11971
- */
12305
+ * Provides a fallback for unknown tags by rendering them as HTML elements.
12306
+ * Passes child nodes to the transpiler, which handles all node types (such as ForEach).
12307
+ **/
11972
12308
  getUnknownTag(node) {
11973
- const id = node.id.toLowerCase();
11974
-
12309
+ const id = node.id;
11975
12310
  return {
11976
- render: async ({ props, ast, isSelfClosing, renderChild }) => {
12311
+ options: { trimAndWrapBlocks: true },
12312
+ render: ({ props, content, isSelfClosing }) => {
11977
12313
  const element = this.tag(id).smartAttributes(props, this.customProps, this.options);
11978
12314
  if (isSelfClosing || VOID_ELEMENTS.has(id)) return element.selfClose();
11979
-
11980
- let rawContent = "";
11981
- for (const child of (ast.body || [])) {
11982
- if (child.type === TEXT$1) rawContent += this.text(child.text);
11983
- else if (child.type === BLOCK) rawContent += await renderChild(child);
11984
- }
11985
- rawContent = rawContent.trim();
11986
-
11987
- const meaningful = (ast.body || []).filter(c => c.type !== TEXT$1 || c.text.trim());
11988
- const finalContent = meaningful.length <= 1 ? rawContent : `\n${rawContent}\n`;
11989
- return element.body(finalContent);
11990
- },
11991
- options: { handleAst: true }
12315
+ return element.body(content);
12316
+ }
11992
12317
  };
11993
12318
  }
11994
12319
  });
11995
12320
 
11996
12321
  MARKDOWN.inherit(HTML);
11997
12322
  const { md, safeArg } = MARKDOWN;
12323
+ registerSharedOutputs(MARKDOWN);
11998
12324
 
11999
12325
  /**
12000
12326
  * Quote - Renders blockquote content or GFM alerts.
12001
12327
  */
12002
- MARKDOWN.register("quote", ({ props, content }) => {
12003
- const type = safeArg({ props, index: 0, key: "type", fallBack: "" });
12004
- return md.quote(content, type);
12005
- }, { resolve: true });
12328
+ MARKDOWN.register(
12329
+ "quote",
12330
+ ({ props, content }) => {
12331
+ const type = safeArg({ props, index: 0, key: "type", fallBack: "" });
12332
+ return md.quote(content, type);
12333
+ },
12334
+ { resolve: true }
12335
+ );
12006
12336
 
12007
12337
  /**
12008
12338
  * Headings - Renders H1-H6 block headings.
@@ -12082,12 +12412,12 @@ MARKDOWN.register(
12082
12412
  "link",
12083
12413
  ({ props, content, isSelfClosing }) => {
12084
12414
  if (isSelfClosing) {
12085
- const text = safeArg({ props, index: 0, key: "text", fallBack: "" });
12086
- const src = safeArg({ props, index: 1, key: "src", fallBack: "" });
12415
+ const text = safeArg({ props, index: 0, key: "text", fallBack: "" });
12416
+ const src = safeArg({ props, index: 1, key: "src", fallBack: "" });
12087
12417
  const title = safeArg({ props, index: 2, key: "title", fallBack: "" });
12088
12418
  return md.url("link", text, src, title);
12089
12419
  }
12090
- const src = safeArg({ props, index: 0, key: "src", fallBack: "" });
12420
+ const src = safeArg({ props, index: 0, key: "src", fallBack: "" });
12091
12421
  const title = safeArg({ props, index: 1, key: "title", fallBack: "" });
12092
12422
  return md.url("link", content, src, title);
12093
12423
  },
@@ -12125,10 +12455,14 @@ MARKDOWN.register(
12125
12455
  * Escape - Escapes special Markdown characters.
12126
12456
  * Self-closing: [escape = "text" !] or [escape = text: "text" !]
12127
12457
  */
12128
- MARKDOWN.register(["escape", "e"], function ({ props, content, isSelfClosing }) {
12129
- const text = isSelfClosing ? safeArg({ props, index: 0, key: "text", fallBack: "" }) : content;
12130
- return this.md.escape(text);
12131
- }, { resolve: true });
12458
+ MARKDOWN.register(
12459
+ ["escape", "e"],
12460
+ function ({ props, content, isSelfClosing }) {
12461
+ const text = isSelfClosing ? safeArg({ props, index: 0, key: "text", fallBack: "" }) : content;
12462
+ return this.md.escape(text);
12463
+ },
12464
+ { resolve: true }
12465
+ );
12132
12466
 
12133
12467
  const ROW_SEP = "\x1E";
12134
12468
  const CELL_SEP = "\x1F";
@@ -12144,12 +12478,16 @@ MARKDOWN.register(
12144
12478
  const headers = [];
12145
12479
  const rows = [];
12146
12480
 
12147
- const extractRows = async (sectionNode) => {
12481
+ const extractRows = async sectionNode => {
12148
12482
  const sectionRows = [];
12149
- for (const child of (sectionNode.body || [])) {
12483
+ for (const child of sectionNode.body || []) {
12150
12484
  if (child.type === BLOCK && child.id?.toLowerCase() === "row") {
12151
12485
  const rendered = await renderChild(child, { inTable: true });
12152
- const cells = rendered.split(ROW_SEP)[0]?.split(CELL_SEP).filter(c => c !== "") ?? [];
12486
+ const cells =
12487
+ rendered
12488
+ .split(ROW_SEP)[0]
12489
+ ?.split(CELL_SEP)
12490
+ .filter(c => c !== "") ?? [];
12153
12491
  if (cells.length > 0) sectionRows.push(cells);
12154
12492
  } else if (child.type === FOR_EACH) {
12155
12493
  const rendered = await renderChild(child, { inTable: true });
@@ -12183,25 +12521,29 @@ MARKDOWN.register(
12183
12521
  */
12184
12522
  MARKDOWN.register(["header", "body"], ({ content }) => content);
12185
12523
 
12186
- MARKDOWN.register("row", async function ({ ast, renderChild, inTable }) {
12187
- if (!inTable) {
12188
- let result = "";
12189
- for (const child of ast.body) {
12190
- if (child.type === TEXT$1) result += this.text(child.text);
12191
- else if (child.type === BLOCK) result += await renderChild(child);
12524
+ MARKDOWN.register(
12525
+ "row",
12526
+ async function ({ ast, renderChild, inTable }) {
12527
+ if (!inTable) {
12528
+ let result = "";
12529
+ for (const child of ast.body) {
12530
+ if (child.type === TEXT$1) result += this.text(child.text);
12531
+ else if (child.type === BLOCK) result += await renderChild(child);
12532
+ }
12533
+ return result;
12192
12534
  }
12193
- return result;
12194
- }
12195
- let cells = "";
12196
- for (const child of ast.body) {
12197
- if (child.type !== BLOCK) continue;
12198
- const id = child.id?.toLowerCase();
12199
- if (id === "cell" || id === "th" || id === "td") {
12200
- cells += await renderChild(child, { inTable: true });
12535
+ let cells = "";
12536
+ for (const child of ast.body) {
12537
+ if (child.type !== BLOCK) continue;
12538
+ const id = child.id?.toLowerCase();
12539
+ if (id === "cell" || id === "th" || id === "td") {
12540
+ cells += await renderChild(child, { inTable: true });
12541
+ }
12201
12542
  }
12202
- }
12203
- return cells + ROW_SEP;
12204
- }, { handleAst: true });
12543
+ return cells + ROW_SEP;
12544
+ },
12545
+ { handleAst: true }
12546
+ );
12205
12547
 
12206
12548
  MARKDOWN.register(["cell", "th", "td"], ({ content, inTable }) => {
12207
12549
  return inTable ? content.trim() + CELL_SEP : content;
@@ -12211,34 +12553,42 @@ MARKDOWN.register(["cell", "th", "td"], ({ content, inTable }) => {
12211
12553
  * Lists - Authoritative Native AST List resolution.
12212
12554
  * Supports Ordered (Number) and Unordered (Dotlex) lists with deep nesting.
12213
12555
  */
12214
- MARKDOWN.register(["list", "List"], async function ({ ast, props, renderChild }) {
12215
- const indicator = safeArg({ props, index: 0, fallBack: "dot" });
12216
- const isOrdered = indicator === "number" || indicator === "ol";
12217
- const marker = isOrdered ? "" : (indicator === "dot" ? "-" : indicator);
12218
- const items = [];
12556
+ MARKDOWN.register(
12557
+ ["list", "List"],
12558
+ async function ({ ast, props, renderChild }) {
12559
+ const indicator = safeArg({ props, index: 0, fallBack: "dot" });
12560
+ const isOrdered = indicator === "number" || indicator === "ol";
12561
+ const marker = isOrdered ? "" : indicator === "dot" ? "-" : indicator;
12562
+ const items = [];
12219
12563
 
12220
- for (const node of ast.body) {
12221
- if (node.type !== BLOCK) continue;
12222
- const id = node.id?.toLowerCase();
12223
- if (id === "item") {
12224
- items.push((await renderChild(node)).trim());
12564
+ for (const node of ast.body) {
12565
+ if (node.type !== BLOCK) continue;
12566
+ const id = node.id?.toLowerCase();
12567
+ if (id === "item") {
12568
+ items.push((await renderChild(node)).trim());
12569
+ }
12225
12570
  }
12226
- }
12227
12571
 
12228
- return isOrdered ? md.orderedList(items, 0) : md.unorderedList(items, 0, marker);
12229
- }, { handleAst: true, trimAndWrapBlocks: false });
12572
+ return isOrdered ? md.orderedList(items, 0) : md.unorderedList(items, 0, marker);
12573
+ },
12574
+ { handleAst: true, trimAndWrapBlocks: false }
12575
+ );
12230
12576
 
12231
12577
  /**
12232
12578
  * List Helpers - Internal tags for list structural organization.
12233
12579
  */
12234
- MARKDOWN.register(["item", "Item"], async function ({ ast, renderChild }) {
12235
- let result = "";
12236
- for (const child of ast.body) {
12237
- if (child.type === TEXT$1) result += this.text(child.text);
12238
- else if (child.type === BLOCK) result += await renderChild(child);
12239
- }
12240
- return result.trim();
12241
- }, { handleAst: true, trimAndWrapBlocks: false });
12580
+ MARKDOWN.register(
12581
+ ["item", "Item"],
12582
+ async function ({ ast, renderChild }) {
12583
+ let result = "";
12584
+ for (const child of ast.body) {
12585
+ if (child.type === TEXT$1) result += this.text(child.text);
12586
+ else if (child.type === BLOCK) result += await renderChild(child);
12587
+ }
12588
+ return result.trim();
12589
+ },
12590
+ { handleAst: true, trimAndWrapBlocks: false }
12591
+ );
12242
12592
 
12243
12593
  /**
12244
12594
  * Todo - Renders task list items with status markers.
@@ -12248,19 +12598,23 @@ MARKDOWN.register(["item", "Item"], async function ({ ast, renderChild }) {
12248
12598
  * [todo = "Add feature", "x" !] positional self-closing (task, status)
12249
12599
  * [todo = "x"]Add feature[end] status in prop, task in body
12250
12600
  */
12251
- MARKDOWN.register("todo", ({ props, content, isSelfClosing }) => {
12252
- let status, task;
12601
+ MARKDOWN.register(
12602
+ "todo",
12603
+ ({ props, content, isSelfClosing }) => {
12604
+ let status, task;
12253
12605
 
12254
- if (isSelfClosing) {
12255
- task = safeArg({ props, index: 0, key: "task", fallBack: "" });
12256
- status = safeArg({ props, index: 1, key: "status", fallBack: "" });
12257
- } else {
12258
- status = safeArg({ props, index: 0, fallBack: "" });
12259
- task = content;
12260
- }
12606
+ if (isSelfClosing) {
12607
+ task = safeArg({ props, index: 0, key: "task", fallBack: "" });
12608
+ status = safeArg({ props, index: 1, key: "status", fallBack: "" });
12609
+ } else {
12610
+ status = safeArg({ props, index: 0, fallBack: "" });
12611
+ task = content;
12612
+ }
12261
12613
 
12262
- return md.todo(status, task);
12263
- }, { trimAndWrapBlocks: false });
12614
+ return md.todo(status, task);
12615
+ },
12616
+ { trimAndWrapBlocks: false }
12617
+ );
12264
12618
 
12265
12619
  /**
12266
12620
  * The MDX Mapper used for generating Markdown with JSX.
@@ -12470,100 +12824,115 @@ Jsonc.register(["Array", "array"], async function ({ props, ast, depth = 0, inAr
12470
12824
  /**
12471
12825
  * Renders a standard XML tag based on the provided identifier and arguments.
12472
12826
  * Ensures strict attribute quoting and handles self-closing tags for empty bodies.
12473
- *
12827
+ *
12474
12828
  * @param {string} id - The XML tag identifier (case-sensitive).
12475
12829
  * @param {Object} props - Key-value pairs to be rendered as XML attributes.
12476
12830
  * @param {string} content - The rendered inner content of the tag.
12477
12831
  * @returns {string} The fully rendered XML tag string.
12478
12832
  */
12479
12833
  const renderXmlTag = function (id, props, content, isSelfClosing) {
12480
- // XML is case-sensitive, so we use the exact id provided
12481
- const element = this.tag(id);
12482
-
12483
- // Filter out positional indices (numeric keys) for XML attributes
12484
- const namedArgs = {};
12485
- Object.keys(props).forEach(key => {
12486
- if (isNaN(parseInt(key))) {
12487
- namedArgs[key] = props[key];
12488
- }
12489
- });
12834
+ // XML is case-sensitive, so we use the exact id provided
12835
+ const element = this.tag(id);
12490
12836
 
12491
- // In XML, attributes must always have values (strict = true)
12492
- element.attributes(namedArgs, true);
12837
+ // Filter out positional indices (numeric keys) for XML attributes
12838
+ const namedArgs = {};
12839
+ Object.keys(props).forEach(key => {
12840
+ if (isNaN(parseInt(key))) {
12841
+ namedArgs[key] = props[key];
12842
+ }
12843
+ });
12493
12844
 
12494
- const hasBody = typeof content === "string" && content.trim().length > 0;
12845
+ // In XML, attributes must always have values (strict = true)
12846
+ element.attributes(namedArgs, true);
12495
12847
 
12496
- if (isSelfClosing || !hasBody) {
12497
- return element.selfClose();
12498
- }
12848
+ const hasBody = typeof content === "string" && content.trim().length > 0;
12849
+
12850
+ if (isSelfClosing || !hasBody) {
12851
+ return element.selfClose();
12852
+ }
12499
12853
 
12500
- return element.body(content);
12854
+ return element.body(content);
12501
12855
  };
12502
12856
 
12503
12857
  /**
12504
12858
  * The XML Mapper used for creating XML pages.
12505
12859
  */
12506
12860
  const XML = Mapper.define({
12507
- /**
12508
- * Renders a comment in XML format.
12509
- * @param {string} text - The comment content.
12510
- * @returns {string}
12511
- */
12512
- comment(text) {
12513
- return `<!-- ${text} -->`;
12514
- },
12861
+ /**
12862
+ * Renders a comment in XML format.
12863
+ * @param {string} text - The comment content.
12864
+ * @returns {string}
12865
+ */
12866
+ comment(text) {
12867
+ return `<!-- ${text} -->`;
12868
+ },
12515
12869
 
12516
- /**
12517
- * Resolves unknown tags by preserving their original case and applying XML rules.
12518
- * @param {Object} node - The AST node representing the unknown tag.
12519
- * @returns {Object} Renderer definition for the tag.
12520
- */
12521
- getUnknownTag(node) {
12522
- const id = node.id;
12523
- return {
12524
- render: ({ props, content, isSelfClosing }) => renderXmlTag.call(this, id, props, content, isSelfClosing),
12525
- options: {}
12526
- };
12527
- }
12870
+ /**
12871
+ * Resolves unknown tags by preserving their original case and applying XML rules.
12872
+ * @param {Object} node - The AST node representing the unknown tag.
12873
+ * @returns {Object} Renderer definition for the tag.
12874
+ */
12875
+ getUnknownTag(node) {
12876
+ const id = node.id;
12877
+ return {
12878
+ render: ({ props, content, isSelfClosing }) => renderXmlTag.call(this, id, props, content, isSelfClosing),
12879
+ options: {}
12880
+ };
12881
+ },
12882
+ options: {
12883
+ trimAndWrapBlocks: true
12884
+ }
12528
12885
  });
12529
12886
 
12530
12887
  /**
12531
12888
  * Registers the XML declaration as a self-closing block.
12532
12889
  * Usage: [xml = version: "1.0", encoding: "UTF-8"]
12533
12890
  */
12534
- XML.register("xml", ({ props }) => {
12535
- const version = props.version || "1.0";
12536
- const encoding = props.encoding || "UTF-8";
12537
- return `<?xml version="${version}" encoding="${encoding}"?>`;
12538
- }, { rules: { is_empty_body: true } });
12891
+ XML.register(
12892
+ "xml",
12893
+ ({ props }) => {
12894
+ const version = props.version || "1.0";
12895
+ const encoding = props.encoding || "UTF-8";
12896
+ return `<?xml version="${version}" encoding="${encoding}"?>`;
12897
+ },
12898
+ { rules: { is_empty_body: true } }
12899
+ );
12539
12900
 
12540
12901
  /**
12541
12902
  * Registers the DOCTYPE declaration.
12542
12903
  * Usage: [doctype = root: "note", system: "note.dtd"]
12543
12904
  */
12544
- XML.register(["DOCTYPE", "doctype"], ({ props }) => {
12545
- const root = props.root || "root";
12546
- const system = props.system;
12547
- const pub = props.public || props.fpi;
12905
+ XML.register(
12906
+ ["DOCTYPE", "doctype"],
12907
+ ({ props }) => {
12908
+ const root = props.root || "root";
12909
+ const system = props.system;
12910
+ const pub = props.public || props.fpi;
12548
12911
 
12549
- if (pub && system) {
12550
- return `<!DOCTYPE ${root} PUBLIC "${pub}" "${system}">`;
12551
- } else if (system) {
12552
- return `<!DOCTYPE ${root} SYSTEM "${system}">`;
12553
- }
12554
- return `<!DOCTYPE ${root}>`;
12555
- }, { rules: { is_empty_body: true } });
12912
+ if (pub && system) {
12913
+ return `<!DOCTYPE ${root} PUBLIC "${pub}" "${system}">`;
12914
+ } else if (system) {
12915
+ return `<!DOCTYPE ${root} SYSTEM "${system}">`;
12916
+ }
12917
+ return `<!DOCTYPE ${root}>`;
12918
+ },
12919
+ { rules: { is_empty_body: true } }
12920
+ );
12556
12921
 
12557
12922
  /**
12558
12923
  * Registers the XML stylesheet processing instruction.
12559
12924
  * Usage: [xml-stylesheet = href: "style.xsl"]
12560
12925
  */
12561
- XML.register("xml-stylesheet", ({ props }) => {
12562
- const type = props.type || "text/xsl";
12563
- const href = props.href;
12564
- if (!href) return "";
12565
- return `<?xml-stylesheet type="${type}" href="${href}"?>`;
12566
- }, { rules: { is_empty_body: true } });
12926
+ XML.register(
12927
+ "xml-stylesheet",
12928
+ ({ props }) => {
12929
+ const type = props.type || "text/xsl";
12930
+ const href = props.href;
12931
+ if (!href) return "";
12932
+ return `<?xml-stylesheet type="${type}" href="${href}"?>`;
12933
+ },
12934
+ { rules: { is_empty_body: true } }
12935
+ );
12567
12936
 
12568
12937
  /**
12569
12938
  * Registers CDATA sections.
@@ -12571,10 +12940,12 @@ XML.register("xml-stylesheet", ({ props }) => {
12571
12940
  * Self-closing: [cdata = "raw content" !] or [cdata = text: "raw content" !]
12572
12941
  */
12573
12942
  XML.register("cdata", ({ props, content, isSelfClosing }) => {
12574
- const text = isSelfClosing ? (props[0] ?? props.text ?? "") : content;
12575
- return `<![CDATA[${text}]]>`;
12943
+ const text = isSelfClosing ? (props[0] ?? props.text ?? "") : content;
12944
+ return `<![CDATA[${text}]]>`;
12576
12945
  });
12577
12946
 
12947
+ registerSharedOutputs(XML);
12948
+
12578
12949
  const csvEscape = (value) => {
12579
12950
  const str = String(value ?? "").trim();
12580
12951
  if (str.includes(",") || str.includes('"') || str.includes("\n") || str.includes("\r")) {
@@ -12631,6 +13002,8 @@ CSV.register(["col", "cell", "td"], ({ textContent }) => {
12631
13002
  return csvEscape(textContent);
12632
13003
  }, { handleAst: true, trimAndWrapBlocks: false });
12633
13004
 
13005
+ registerSharedOutputs(CSV);
13006
+
12634
13007
  const isValidInt$1 = (v) => v !== "" && !isNaN(Number(v)) && !v.includes(".");
12635
13008
  const isValidFloat$1 = (v) => v !== "" && !isNaN(Number(v)) && v.includes(".");
12636
13009
  const isValidNumber$1 = (v) => v !== "" && !isNaN(Number(v));
@@ -12854,6 +13227,8 @@ TOML.register("array", async ({ props, ast, renderChild }) => {
12854
13227
  return `${tomlKey(key)} = [${vals.join(", ")}]\n`;
12855
13228
  }, { handleAst: true });
12856
13229
 
13230
+ registerSharedOutputs(TOML);
13231
+
12857
13232
  const isValidInt = (v) => v !== "" && !isNaN(Number(v)) && !v.includes(".");
12858
13233
  const isValidFloat = (v) => v !== "" && !isNaN(Number(v)) && v.includes(".");
12859
13234
  const isValidNumber = (v) => v !== "" && !isNaN(Number(v));
@@ -13169,6 +13544,8 @@ YAML.register("doc-start", () => "---\n", { rules: { is_empty_body: true } });
13169
13544
  */
13170
13545
  YAML.register("doc-end", () => "...\n", { rules: { is_empty_body: true } });
13171
13546
 
13547
+ registerSharedOutputs(YAML);
13548
+
13172
13549
  /**
13173
13550
  * The Text Mapper used for plain-text extraction.
13174
13551
  */
@@ -13445,34 +13822,48 @@ async function resolveModules(ast, context) {
13445
13822
  const baseDir = context.instance.baseDir || ((filename === "anonymous") ? absFilename : posix.dirname(absFilename));
13446
13823
 
13447
13824
  // 1. Helper: Trim AST to remove file-boundary whitespace and "ghost" newlines
13448
- const trimAst = (nodes) => {
13825
+ const trimAst = (nodes, trimBoundaries = true) => {
13449
13826
  if (!nodes) return [];
13450
13827
 
13451
- // 1. Filter out internal whitespace-only nodes that are adjacent to non-rendering nodes
13452
- // (Comments, Imports, etc. shouldn't leave "ghost" newlines)
13828
+ // 1. Filter out whitespace-only text nodes adjacent (directly or through other whitespace)
13829
+ // to non-rendering nodes (Comments, Imports, USE_MODULE).
13453
13830
  const nonRenderingTypes = [COMMENT, IMPORT, USE_MODULE];
13454
13831
  let res = nodes.filter((node, idx) => {
13455
13832
  if (node.type !== TEXT$1 || node.text.trim() !== "") return true;
13456
13833
 
13457
- const prev = nodes[idx - 1];
13458
- const next = nodes[idx + 1];
13459
- const isAdjacentToNonRendering =
13460
- (prev && nonRenderingTypes.includes(prev.type)) ||
13461
- (next && nonRenderingTypes.includes(next.type));
13834
+ // Walk backwards through consecutive whitespace nodes to find prev non-whitespace
13835
+ let prevIsNonRendering = false;
13836
+ for (let j = idx - 1; j >= 0; j--) {
13837
+ if (nodes[j].type === TEXT$1 && nodes[j].text.trim() === "") continue;
13838
+ prevIsNonRendering = nonRenderingTypes.includes(nodes[j].type);
13839
+ break;
13840
+ }
13462
13841
 
13463
- return !isAdjacentToNonRendering;
13842
+ // Walk forwards through consecutive whitespace nodes to find next non-whitespace
13843
+ let nextIsNonRendering = false;
13844
+ for (let j = idx + 1; j < nodes.length; j++) {
13845
+ if (nodes[j].type === TEXT$1 && nodes[j].text.trim() === "") continue;
13846
+ nextIsNonRendering = nonRenderingTypes.includes(nodes[j].type);
13847
+ break;
13848
+ }
13849
+
13850
+ return !(prevIsNonRendering || nextIsNonRendering);
13464
13851
  });
13465
13852
 
13466
- // 2. Final pass: trim leading/trailing newlines from the remaining boundary text nodes
13467
- if (res.length > 0 && res[0].type === TEXT$1) {
13468
- res[0].text = res[0].text.replace(/^[\r\n]+/, "");
13469
- }
13470
- if (res.length > 0 && res[res.length - 1].type === TEXT$1) {
13471
- res[res.length - 1].text = res[res.length - 1].text.replace(/[\r\n]+\s*$/, "");
13853
+ if (trimBoundaries) {
13854
+ // 2. Final pass: trim leading/trailing newlines from the remaining boundary text nodes
13855
+ if (res.length > 0 && res[0].type === TEXT$1) {
13856
+ res[0].text = res[0].text.replace(/^[\r\n]+/, "");
13857
+ }
13858
+ if (res.length > 0 && res[res.length - 1].type === TEXT$1) {
13859
+ res[res.length - 1].text = res[res.length - 1].text.replace(/[\r\n]+\s*$/, "");
13860
+ }
13861
+
13862
+ // 3. Remove any nodes that became purely empty after trimming
13863
+ res = res.filter(node => node.type !== TEXT$1 || node.text !== "");
13472
13864
  }
13473
13865
 
13474
- // 3. Remove any nodes that became purely empty after trimming
13475
- return res.filter(node => node.type !== TEXT$1 || node.text !== "");
13866
+ return res;
13476
13867
  };
13477
13868
 
13478
13869
  // 2. Helper: Inject Slots with Indentation Propagation
@@ -13535,13 +13926,38 @@ async function resolveModules(ast, context) {
13535
13926
  let resolvedPath = filePath;
13536
13927
  for (const [prefix, replacement] of Object.entries(importAliases)) {
13537
13928
  if (filePath.startsWith(prefix)) {
13538
- resolvedPath = posix.resolve(context.instance.cwd || "/", filePath.replace(prefix, replacement));
13929
+ const replaced = filePath.replace(prefix, replacement);
13930
+ // Preserve scheme prefixes (pkg:, http:, etc.) — don't path.resolve them
13931
+ resolvedPath = replaced.startsWith("pkg:") || replaced.startsWith("http://") || replaced.startsWith("https://")
13932
+ ? replaced
13933
+ : posix.resolve(context.instance.cwd || "/", replaced);
13539
13934
  break;
13540
13935
  }
13541
13936
  }
13542
13937
 
13543
- // 1b. Resolve relative to current base (FS)
13544
- const absolutePath = resolveModulePath(resolvedPath, currentBaseDir);
13938
+ // 1b. pkg: resolve from node_modules at project root
13939
+ let absolutePath;
13940
+ if (resolvedPath.startsWith("pkg:")) {
13941
+ if (!context.instance.fs?.__isNodeFs) {
13942
+ runtimeError([`<$red:Module Error:$> <$cyan:pkg:$> imports are not supported in browser or virtual filesystem mode at line <$yellow:${node.range.start.line + 1}$>`]);
13943
+ }
13944
+ const pkgPath = resolvedPath.slice(4);
13945
+ if (!pkgPath || pkgPath.trim() === "") {
13946
+ runtimeError([`<$red:Module Error:$> <$cyan:pkg:$> path cannot be empty at line <$yellow:${node.range.start.line + 1}$>`]);
13947
+ }
13948
+ const nodeModulesRoot = posix.resolve(context.instance.cwd || "/", "node_modules");
13949
+ absolutePath = posix.resolve(nodeModulesRoot, pkgPath);
13950
+ if (!absolutePath.startsWith(nodeModulesRoot + posix.sep) && absolutePath !== nodeModulesRoot) {
13951
+ runtimeError([`<$red:Module Security Error:$> <$cyan:pkg:${pkgPath}$> resolves outside node_modules — path traversal is not allowed at line <$yellow:${node.range.start.line + 1}$>`]);
13952
+ }
13953
+ } else {
13954
+ // 1c. Resolve relative to current base (FS)
13955
+ absolutePath = resolveModulePath(resolvedPath, currentBaseDir);
13956
+ }
13957
+
13958
+ if (!context.instance.fs) {
13959
+ runtimeError([`<$red:Module Error:$> Cannot import <$magenta:${filePath}$> — no filesystem is available.{N}In browser mode, pass a URL-based <$cyan:baseDir$> or a <$cyan:files$> map to enable module loading.`]);
13960
+ }
13545
13961
 
13546
13962
  // Local Path Resolution with Auto-Extension
13547
13963
  let localPath = absolutePath;
@@ -13694,7 +14110,6 @@ async function resolveModules(ast, context) {
13694
14110
  Object.entries(node.props).filter(([key]) => {
13695
14111
  if (key === "__consumed__") return false;
13696
14112
  if (consumed.has(key)) return false; // THE FIX: Filter if hit by v{}
13697
- if (key === "smark-raw") return false; // directive — must not leak onto root element
13698
14113
  return true;
13699
14114
  })
13700
14115
  );
@@ -13809,10 +14224,7 @@ const runValidations = (node, target, instance) => {
13809
14224
  const isStructural = node.type === "Block";
13810
14225
  if (isStructural && rules.required_args && Array.isArray(rules.required_args)) {
13811
14226
  const missingArgs = rules.required_args.filter(arg => {
13812
- // Check if the argument exists in named args or as a positional arg (if arg is a number)
13813
- if (typeof arg === "number") {
13814
- return node.props[arg] === undefined;
13815
- }
14227
+ if (typeof arg === "number") return node.props[arg] === undefined;
13816
14228
  return node.props[arg] === undefined;
13817
14229
  });
13818
14230
 
@@ -13827,6 +14239,22 @@ const runValidations = (node, target, instance) => {
13827
14239
  );
13828
14240
  }
13829
14241
  }
14242
+
14243
+ // -- Directives Validation (Required Directives) ----------------------- //
14244
+ if (isStructural && rules.required_directives && Array.isArray(rules.required_directives)) {
14245
+ const missingDirectives = rules.required_directives.filter(key => node.directives?.[key] === undefined);
14246
+
14247
+ if (missingDirectives.length > 0) {
14248
+ transpilerError(
14249
+ [
14250
+ "{N}",
14251
+ `<$yellow:Identifier$> <$blue:'${id}'$> <$yellow:is missing required directive props:$> <$red:${missingDirectives.map(k => `smark-${k}`).join(", ")}$>{N}`,
14252
+ `<$blue:Please ensure these directive props are provided in the template usage.$>`
14253
+ ],
14254
+ context
14255
+ );
14256
+ }
14257
+ }
13830
14258
  };
13831
14259
 
13832
14260
  /**
@@ -13998,6 +14426,14 @@ function setDefaultFs(fs) {
13998
14426
  Evaluator$1.setDefaultFs(fs);
13999
14427
  }
14000
14428
 
14429
+ function setDefaultEnv(env) {
14430
+ Evaluator$1.setDefaultEnv(env);
14431
+ }
14432
+
14433
+ function setDefaultAsyncLocalStorage(cls) {
14434
+ Evaluator$1.setDefaultAsyncLocalStorage(cls);
14435
+ }
14436
+
14001
14437
  function setDefaultResolvePath(fn) {
14002
14438
  defaultResolvePath = fn;
14003
14439
  }
@@ -14030,7 +14466,7 @@ class SomMark {
14030
14466
  * @param {string} [options.baseDir=null] - The base directory for resolving relative paths.
14031
14467
  */
14032
14468
  constructor(options = {}) {
14033
- const { src, ast = null, format, mapperFile = null, filename = "anonymous", removeComments = true, placeholders = {}, customProps = [], fallbackTarget = true, outputValidator = null, importAliases = {}, importStack = [], baseDir = null, moduleCache = null, showSpinner = true, security = {}, dualOutput = false, moduleIdentityToken = null } = options;
14469
+ const { src, ast = null, format, mapperFile = null, filename = "anonymous", removeComments = true, placeholders = {}, customProps = [], fallbackTarget = true, outputValidator = null, importAliases = {}, importStack = [], baseDir = null, moduleCache = null, showSpinner = true, security = {}, dualOutput = false, webOutputs = false, moduleIdentityToken = null } = options;
14034
14470
  this.rawSettings = options;
14035
14471
  this.src = src;
14036
14472
  this.ast = ast;
@@ -14041,6 +14477,7 @@ class SomMark {
14041
14477
  this.placeholders = placeholders;
14042
14478
  this.customProps = customProps;
14043
14479
  this.dualOutput = dualOutput;
14480
+ this.webOutputs = webOutputs;
14044
14481
  this.cwd = options.baseDir || (options.files ? "/" : defaultCwd);
14045
14482
  this.fs = options.fs
14046
14483
  || (options.files ? new VirtualFS(options.files) : null)
@@ -14069,7 +14506,8 @@ class SomMark {
14069
14506
  allowFetch: security?.allowFetch !== false,
14070
14507
  allowHttp: security?.allowHttp === true,
14071
14508
  allowedOrigins: Array.isArray(security?.allowedOrigins) ? security.allowedOrigins.map(o => o.toLowerCase()) : null,
14072
- allowedExtensions: Array.isArray(security?.allowedExtensions) ? security.allowedExtensions.map(e => e.toLowerCase()) : null
14509
+ allowedExtensions: Array.isArray(security?.allowedExtensions) ? security.allowedExtensions.map(e => e.toLowerCase()) : null,
14510
+ env: Array.isArray(security?.env) ? security.env : []
14073
14511
  };
14074
14512
  this.warnings = [];
14075
14513
  this._prepared = false;
@@ -14256,6 +14694,7 @@ class SomMark {
14256
14694
  security: this.security,
14257
14695
  settings: this.rawSettings,
14258
14696
  dualOutput: this.dualOutput,
14697
+ webOutputs: this.webOutputs,
14259
14698
  instance: this
14260
14699
  });
14261
14700
 
@@ -14381,8 +14820,24 @@ async function findAndLoadConfig(targetPath) {
14381
14820
  }
14382
14821
  setCompilerClass(SomMark);
14383
14822
 
14823
+ class AsyncLocalStorage {
14824
+ #store = undefined;
14825
+ run(store, fn) {
14826
+ const prev = this.#store;
14827
+ this.#store = store;
14828
+ try { return fn(); }
14829
+ finally { this.#store = prev; }
14830
+ }
14831
+ getStore() { return this.#store; }
14832
+ exit(fn) { return fn(); }
14833
+ enterWith(store) { this.#store = store; }
14834
+ disable() {}
14835
+ }
14836
+
14384
14837
  setDefaultFs(null);
14385
14838
  setDefaultCwd("/");
14839
+ setDefaultEnv(null);
14840
+ setDefaultAsyncLocalStorage(AsyncLocalStorage);
14386
14841
 
14387
14842
  /**
14388
14843
  * Resolves a relative path into a full URL using the current document location.
@@ -14464,4 +14919,4 @@ function renderCompiledHTML(container, html) {
14464
14919
  }
14465
14920
  }
14466
14921
 
14467
- export { CSV, Evaluator$1 as Evaluator, formats as FORMATS, HTML, Json, Jsonc, MARKDOWN, MDX, Mapper, TOKEN_TYPES, TOML, XML, YAML, SomMark as default, enableColor, findAndLoadConfig, labels, lex, lexSync, parse, parseSync, preprocessRuntimeLogic, registerSharedOutputs, renderCompiledHTML, resolveBaseDir, safeArg$1 as safeArg, setDefaultCwd, setDefaultFindAndLoadConfig, setDefaultFs, setDefaultResolvePath, transpile };
14922
+ export { CSV, Evaluator$1 as Evaluator, formats as FORMATS, HTML, Json, Jsonc, MARKDOWN, MDX, Mapper, TOKEN_TYPES, TOML, XML, YAML, SomMark as default, enableColor, findAndLoadConfig, labels, lex, lexSync, parse, parseSync, preprocessRuntimeLogic, registerSharedOutputs, renderCompiledHTML, resolveBaseDir, safeArg$1 as safeArg, setDefaultAsyncLocalStorage, setDefaultCwd, setDefaultEnv, setDefaultFindAndLoadConfig, setDefaultFs, setDefaultResolvePath, transpile };