sommark 5.0.3 → 5.0.5

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,3 +1,5 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
+
1
3
  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)})();
2
4
  return __lib__.default || __lib__; };
3
5
  let _match;
@@ -1786,7 +1788,9 @@ function parseValue(tokens, i, placeholders = {}, variables = {}, allowLogic = t
1786
1788
  }
1787
1789
  variables.__consumed__.add(vKey);
1788
1790
  } else {
1789
- val = vFallback !== undefined ? vFallback : getPrefixValue('v', vKey);
1791
+ // Encode fallback in the envelope key so resolveAstVariables can apply it
1792
+ // at instantiation time instead of baking it in now.
1793
+ val = getPrefixValue('v', vFallback !== undefined ? `${vKey}|${vFallback}` : vKey);
1790
1794
  }
1791
1795
  return [val, i, false];
1792
1796
  } else if (current_token(tokens, i).type === TOKEN_TYPES.PREFIX_P) {
@@ -2180,7 +2184,8 @@ function parseText(tokens, i, placeholders = {}, variables = {}, depth = 0, opti
2180
2184
  }
2181
2185
  variables.__consumed__.add(tvKey);
2182
2186
  } else {
2183
- textNode.text += tvFallback !== undefined ? tvFallback : getPrefixValue('v', tvKey);
2187
+ // Encode fallback in envelope so resolveAstVariables can apply it later.
2188
+ textNode.text += getPrefixValue('v', tvFallback !== undefined ? `${tvKey}|${tvFallback}` : tvKey);
2184
2189
  }
2185
2190
  } else {
2186
2191
  break;
@@ -8622,7 +8627,7 @@ function registerHostSettings(settings) {
8622
8627
  hostSettings = settings || {};
8623
8628
  }
8624
8629
 
8625
- const version = "5.0.3";
8630
+ const version = "5.0.5";
8626
8631
 
8627
8632
  const SomMark$1 = {
8628
8633
  version,
@@ -8673,6 +8678,17 @@ const SomMark$1 = {
8673
8678
  // Freeze the entire Standard Library to make it completely immutable and tamper-proof
8674
8679
  Object.freeze(SomMark$1);
8675
8680
 
8681
+ // Each transpile() call gets its own isolated EvaluatorState stack via async context.
8682
+ const evaluatorStorage = new AsyncLocalStorage();
8683
+
8684
+ /**
8685
+ * Runs fn inside an isolated evaluator context.
8686
+ * Concurrent transpile() calls each get their own stack — no cross-contamination.
8687
+ */
8688
+ function withEvaluator(fn) {
8689
+ return evaluatorStorage.run([], fn);
8690
+ }
8691
+
8676
8692
  // Global tracker to ensure deep recursive Smark compilation never exceeds safe boundaries
8677
8693
  let globalCompilationDepth = 0;
8678
8694
 
@@ -9399,8 +9415,10 @@ class EvaluatorState {
9399
9415
  this.expose(vars);
9400
9416
  }
9401
9417
 
9402
- async execute(code) {
9418
+ async execute(code, baseDir = null) {
9403
9419
  if (!this.context) throw new Error("Evaluator not initialized");
9420
+ const prevBaseDir = this.baseDir;
9421
+ if (baseDir) this.baseDir = baseDir;
9404
9422
 
9405
9423
  const timeout = this.security?.timeout ?? 5000;
9406
9424
  this.deadline = Date.now() + timeout;
@@ -9634,6 +9652,7 @@ class EvaluatorState {
9634
9652
  } finally {
9635
9653
  this.deadline = 0;
9636
9654
  clearInterval(interval);
9655
+ if (baseDir) this.baseDir = prevBaseDir;
9637
9656
  }
9638
9657
  }
9639
9658
 
@@ -9670,30 +9689,42 @@ class EvaluatorState {
9670
9689
 
9671
9690
  class Evaluator {
9672
9691
  constructor() {
9673
- this.instances = [];
9692
+ // Fallback stack for callers that use init() outside withEvaluator() (e.g. tests).
9693
+ this._fallbackStack = [];
9674
9694
  }
9675
9695
 
9696
+ _getStack() {
9697
+ return evaluatorStorage.getStore() ?? this._fallbackStack;
9698
+ }
9699
+
9700
+ // Expose the active stack so tests can check .instances.length
9701
+ get instances() { return this._getStack(); }
9702
+
9676
9703
  setDefaultFs(fs) {
9677
9704
  defaultFs$1 = fs;
9678
9705
  }
9679
9706
 
9680
9707
  get active() {
9681
- if (this.instances.length === 0) {
9708
+ const stack = this._getStack();
9709
+ if (stack.length === 0) {
9682
9710
  throw new Error("No active EvaluatorState instance. Did you call init()?");
9683
9711
  }
9684
- return this.instances[this.instances.length - 1];
9712
+ return stack[stack.length - 1];
9685
9713
  }
9686
9714
 
9715
+ // Forward .runtime to the active state so tests can assert on it
9716
+ get runtime() { return this.active?.runtime ?? null; }
9717
+
9687
9718
  async init(baseDir = null, security = {}, settings = {}, mapperFile = null) {
9688
9719
  const state = new EvaluatorState();
9689
9720
  await state.init(baseDir, security, settings, mapperFile);
9690
- this.instances.push(state);
9721
+ this._getStack().push(state);
9691
9722
  }
9692
9723
 
9693
9724
  destroy() {
9694
- if (this.instances.length > 0) {
9695
- const state = this.instances.pop();
9696
- state.destroy();
9725
+ const stack = this._getStack();
9726
+ if (stack.length > 0) {
9727
+ stack.pop().destroy();
9697
9728
  }
9698
9729
  }
9699
9730
 
@@ -9709,8 +9740,8 @@ class Evaluator {
9709
9740
  this.active.inject(vars);
9710
9741
  }
9711
9742
 
9712
- async execute(code) {
9713
- return await this.active.execute(code);
9743
+ async execute(code, baseDir = null) {
9744
+ return await this.active.execute(code, baseDir);
9714
9745
  }
9715
9746
 
9716
9747
  hasDynamicTag(id) {
@@ -10075,7 +10106,7 @@ async function generateOutput(ast, i, format, mapper_file, security = {}, parent
10075
10106
 
10076
10107
  if (node.type === STATIC_LOGIC) {
10077
10108
  try {
10078
- const result = await Evaluator$1.execute(node.code);
10109
+ const result = await Evaluator$1.execute(node.code, node.baseDir || null);
10079
10110
  if (generateRuntimeOutput) return "";
10080
10111
  if (result && typeof result === "object" && result.__raw !== undefined) {
10081
10112
  if (security?.allowRaw === false) {
@@ -10281,7 +10312,7 @@ async function generateOutput(ast, i, format, mapper_file, security = {}, parent
10281
10312
  }
10282
10313
  } else if (child.type === STATIC_LOGIC) {
10283
10314
  try {
10284
- const val = await Evaluator$1.execute(child.code);
10315
+ const val = await Evaluator$1.execute(child.code, child.baseDir || null);
10285
10316
  if (val !== undefined && typeof val !== "object") richText += String(val);
10286
10317
  } catch (err) {
10287
10318
  transpilerError([
@@ -10398,7 +10429,7 @@ async function generateOutput(ast, i, format, mapper_file, security = {}, parent
10398
10429
 
10399
10430
  case STATIC_LOGIC:
10400
10431
  try {
10401
- const result = await Evaluator$1.execute(body_node.code);
10432
+ const result = await Evaluator$1.execute(body_node.code, body_node.baseDir || null);
10402
10433
  if (result && typeof result === "object" && result.__raw !== undefined) {
10403
10434
  if (security?.allowRaw === false) {
10404
10435
  bodyOutput = mapper_file ? mapper_file.text(String(result.__raw)) : String(result.__raw);
@@ -10512,109 +10543,108 @@ async function transpiler(optionsOrAst, format, mapperFile) {
10512
10543
  })();
10513
10544
 
10514
10545
  const dualOutput = optionsOrAst?.dualOutput || false;
10515
-
10516
- // Initialize Logic Sandbox
10517
- await Evaluator$1.init(fileBaseDir, security, settings, targetMapper);
10518
- // Inject global data
10519
10546
  const placeholders = optionsOrAst?.placeholders || settings?.placeholders || {};
10520
10547
  const variables = optionsOrAst?.variables || settings?.variables || {};
10521
10548
  warnDroppedVariables(variables);
10522
- Evaluator$1.inject(placeholders);
10523
- Evaluator$1.inject(variables);
10524
10549
 
10525
- let output = "";
10526
- let prev_body_node = null;
10527
- let prev_was_silent = false;
10550
+ return withEvaluator(async () => {
10551
+ // Initialize Logic Sandbox inside isolated async context
10552
+ await Evaluator$1.init(fileBaseDir, security, settings, targetMapper);
10553
+ Evaluator$1.inject(placeholders);
10554
+ Evaluator$1.inject(variables);
10528
10555
 
10529
- if (dualOutput) {
10530
- const idState = { mode: 'record', ids: [], idx: 0 };
10556
+ let output = "";
10557
+ let prev_body_node = null;
10558
+ let prev_was_silent = false;
10531
10559
 
10532
- // HTML pass — generate HTML, record element IDs for runtime blocks
10533
- let htmlOutput = "";
10534
- try {
10535
- for (let i = 0; i < body.length; i++) {
10536
- const node = body[i];
10537
- const blockOutput = await generateOutput(body, i, targetFormat, targetMapper, security, null, false, true, instance, idState);
10538
- let finalBlockOutput = blockOutput;
10539
- if (prev_was_silent && node.type === TEXT$1) finalBlockOutput = finalBlockOutput.replace(/^\n/, "");
10540
- if (finalBlockOutput) {
10541
- htmlOutput += finalBlockOutput;
10542
- prev_was_silent = false;
10543
- } else {
10544
- prev_was_silent = true;
10545
- if ((node.type === COMMENT || node.type === COMMENT_BLOCK) && targetMapper?.options?.removeComments) {
10546
- const nextNode = body[i + 1];
10547
- if (nextNode && nextNode.type === TEXT$1 && (nextNode.text === "\n" || nextNode.text === "\r\n")) i++;
10560
+ if (dualOutput) {
10561
+ const idState = { mode: 'record', ids: [], idx: 0 };
10562
+
10563
+ // HTML pass generate HTML, record element IDs for runtime blocks
10564
+ let htmlOutput = "";
10565
+ try {
10566
+ for (let i = 0; i < body.length; i++) {
10567
+ const node = body[i];
10568
+ const blockOutput = await generateOutput(body, i, targetFormat, targetMapper, security, null, false, true, instance, idState);
10569
+ let finalBlockOutput = blockOutput;
10570
+ if (prev_was_silent && node.type === TEXT$1) finalBlockOutput = finalBlockOutput.replace(/^\n/, "");
10571
+ if (finalBlockOutput) {
10572
+ htmlOutput += finalBlockOutput;
10573
+ prev_was_silent = false;
10574
+ } else {
10575
+ prev_was_silent = true;
10576
+ if ((node.type === COMMENT || node.type === COMMENT_BLOCK) && targetMapper?.options?.removeComments) {
10577
+ const nextNode = body[i + 1];
10578
+ if (nextNode && nextNode.type === TEXT$1 && (nextNode.text === "\n" || nextNode.text === "\r\n")) i++;
10579
+ }
10548
10580
  }
10549
10581
  }
10582
+ } finally {
10583
+ Evaluator$1.destroy();
10550
10584
  }
10551
- } finally {
10552
- Evaluator$1.destroy();
10553
- }
10554
10585
 
10555
- // JS pass — replay the same IDs so querySelector targets match HTML
10556
- idState.mode = 'replay';
10557
- idState.idx = 0;
10558
- prev_was_silent = false;
10586
+ // JS pass — replay the same IDs so querySelector targets match HTML
10587
+ idState.mode = 'replay';
10588
+ idState.idx = 0;
10589
+ prev_was_silent = false;
10559
10590
 
10560
- await Evaluator$1.init(fileBaseDir, security, settings, targetMapper);
10561
- Evaluator$1.inject(placeholders);
10562
- Evaluator$1.inject(variables);
10591
+ await Evaluator$1.init(fileBaseDir, security, settings, targetMapper);
10592
+ Evaluator$1.inject(placeholders);
10593
+ Evaluator$1.inject(variables);
10594
+
10595
+ let jsOutput = "";
10596
+ try {
10597
+ for (let i = 0; i < body.length; i++) {
10598
+ const node = body[i];
10599
+ const blockOutput = await generateOutput(body, i, targetFormat, targetMapper, security, null, true, false, instance, idState);
10600
+ let finalBlockOutput = blockOutput;
10601
+ if (prev_was_silent && node.type === TEXT$1) finalBlockOutput = finalBlockOutput.replace(/^\n/, "");
10602
+ if (finalBlockOutput) {
10603
+ jsOutput += finalBlockOutput;
10604
+ prev_was_silent = false;
10605
+ } else {
10606
+ prev_was_silent = true;
10607
+ }
10608
+ }
10609
+ } finally {
10610
+ Evaluator$1.destroy();
10611
+ }
10612
+
10613
+ return [htmlOutput.trim(), jsOutput.trim()];
10614
+ }
10563
10615
 
10564
- let jsOutput = "";
10565
10616
  try {
10566
10617
  for (let i = 0; i < body.length; i++) {
10567
10618
  const node = body[i];
10568
- const blockOutput = await generateOutput(body, i, targetFormat, targetMapper, security, null, true, false, instance, idState);
10619
+ const blockOutput = await generateOutput(body, i, targetFormat, targetMapper, security, null, false, false, instance);
10620
+
10569
10621
  let finalBlockOutput = blockOutput;
10570
- if (prev_was_silent && node.type === TEXT$1) finalBlockOutput = finalBlockOutput.replace(/^\n/, "");
10622
+ if (prev_was_silent && node.type === TEXT$1) {
10623
+ finalBlockOutput = finalBlockOutput.replace(/^\n/, "");
10624
+ }
10625
+
10571
10626
  if (finalBlockOutput) {
10572
- jsOutput += finalBlockOutput;
10627
+ output += finalBlockOutput;
10573
10628
  prev_was_silent = false;
10629
+ if (node.type !== TEXT$1 || node.text.trim().length > 0) {
10630
+ prev_body_node = node;
10631
+ }
10574
10632
  } else {
10575
10633
  prev_was_silent = true;
10634
+ if ((node.type === COMMENT || node.type === COMMENT_BLOCK) && targetMapper?.options?.removeComments) {
10635
+ const nextNode = body[i + 1];
10636
+ if (nextNode && nextNode.type === TEXT$1 && (nextNode.text === "\n" || nextNode.text === "\r\n")) {
10637
+ i++;
10638
+ }
10639
+ }
10576
10640
  }
10577
10641
  }
10578
10642
  } finally {
10579
10643
  Evaluator$1.destroy();
10580
10644
  }
10581
10645
 
10582
- return [htmlOutput.trim(), jsOutput.trim()];
10583
- }
10584
-
10585
- try {
10586
- for (let i = 0; i < body.length; i++) {
10587
- const node = body[i];
10588
- const blockOutput = await generateOutput(body, i, targetFormat, targetMapper, security, null, false, false, instance);
10589
-
10590
- let finalBlockOutput = blockOutput;
10591
- if (prev_was_silent && node.type === TEXT$1) {
10592
- finalBlockOutput = finalBlockOutput.replace(/^\n/, "");
10593
- }
10594
-
10595
- if (finalBlockOutput) {
10596
- output += finalBlockOutput;
10597
- prev_was_silent = false;
10598
- if (node.type !== TEXT$1 || node.text.trim().length > 0) {
10599
- prev_body_node = node;
10600
- }
10601
- } else {
10602
- prev_was_silent = true;
10603
- if ((node.type === COMMENT || node.type === COMMENT_BLOCK) && targetMapper?.options?.removeComments) {
10604
- // If a comment is removed, check the next node.
10605
- // If it's just a blank line, skip it so we don't have extra gaps in the output.
10606
- const nextNode = body[i + 1];
10607
- if (nextNode && nextNode.type === TEXT$1 && (nextNode.text === "\n" || nextNode.text === "\r\n")) {
10608
- i++; // Skip the next newline node
10609
- }
10610
- }
10611
- }
10612
- }
10613
- } finally {
10614
- Evaluator$1.destroy();
10615
- }
10616
-
10617
- return output.trim();
10646
+ return output.trim();
10647
+ });
10618
10648
  }
10619
10649
 
10620
10650
  /**
@@ -10637,7 +10667,7 @@ async function transpileArgs(props) {
10637
10667
  result[key] = "";
10638
10668
  } else if (value.type === STATIC_LOGIC) {
10639
10669
  try {
10640
- result[key] = await Evaluator$1.execute(value.code);
10670
+ result[key] = await Evaluator$1.execute(value.code, value.baseDir || null);
10641
10671
  } catch (err) {
10642
10672
  transpilerError([
10643
10673
  `<$red:Logic Error (Argument):$> ${err.message}{line}`,
@@ -13275,7 +13305,10 @@ const resolveAstVariables = (nodes, variables) => {
13275
13305
  for (const node of nodes) {
13276
13306
  if (node.type === TEXT$1) {
13277
13307
  if (node.text.includes(VAR_PREFIX)) {
13278
- node.text = node.text.replace(VAR_PATTERN, (match, key) => {
13308
+ node.text = node.text.replace(VAR_PATTERN, (match, keyAndFallback) => {
13309
+ const pipeIdx = keyAndFallback.indexOf('|');
13310
+ const key = pipeIdx >= 0 ? keyAndFallback.slice(0, pipeIdx) : keyAndFallback;
13311
+ const fallback = pipeIdx >= 0 ? keyAndFallback.slice(pipeIdx + 1) : undefined;
13279
13312
  if (variables[key] !== undefined) {
13280
13313
  if (!variables.__consumed__) {
13281
13314
  Object.defineProperty(variables, "__consumed__", {
@@ -13288,14 +13321,21 @@ const resolveAstVariables = (nodes, variables) => {
13288
13321
  variables.__consumed__.add(key);
13289
13322
  return String(variables[key]);
13290
13323
  }
13324
+ if (fallback !== undefined) return fallback;
13291
13325
  return match;
13292
13326
  });
13293
13327
  }
13294
13328
  } else if (node.type === BLOCK) {
13295
13329
  // Resolve any unresolved variables in block arguments
13296
13330
  for (const [argKey, argVal] of Object.entries(node.props)) {
13297
- if (typeof argVal === "string" && argVal.startsWith(VAR_PREFIX) && argVal.endsWith(VAR_SUFFIX)) {
13298
- const varKey = argVal.slice(VAR_PREFIX.length, -VAR_SUFFIX.length);
13331
+ if (typeof argVal !== "string" || !argVal.includes(VAR_PREFIX)) continue;
13332
+
13333
+ if (argVal.startsWith(VAR_PREFIX) && argVal.endsWith(VAR_SUFFIX)) {
13334
+ // Entire value is an envelope — resolve to scalar or fallback
13335
+ const keyAndFallback = argVal.slice(VAR_PREFIX.length, -VAR_SUFFIX.length);
13336
+ const pipeIdx = keyAndFallback.indexOf('|');
13337
+ const varKey = pipeIdx >= 0 ? keyAndFallback.slice(0, pipeIdx) : keyAndFallback;
13338
+ const fallback = pipeIdx >= 0 ? keyAndFallback.slice(pipeIdx + 1) : undefined;
13299
13339
  if (variables[varKey] !== undefined) {
13300
13340
  node.props[argKey] = variables[varKey];
13301
13341
  if (!variables.__consumed__) {
@@ -13307,7 +13347,31 @@ const resolveAstVariables = (nodes, variables) => {
13307
13347
  });
13308
13348
  }
13309
13349
  variables.__consumed__.add(varKey);
13350
+ } else if (fallback !== undefined) {
13351
+ node.props[argKey] = fallback;
13310
13352
  }
13353
+ } else {
13354
+ // Envelope embedded inside a larger string — replace in-place.
13355
+ // Unresolved envelopes become "" so they don't pollute class names etc.
13356
+ node.props[argKey] = argVal.replace(VAR_PATTERN, (match, keyAndFallback) => {
13357
+ const pipeIdx = keyAndFallback.indexOf('|');
13358
+ const key = pipeIdx >= 0 ? keyAndFallback.slice(0, pipeIdx) : keyAndFallback;
13359
+ const fallback = pipeIdx >= 0 ? keyAndFallback.slice(pipeIdx + 1) : undefined;
13360
+ if (variables[key] !== undefined) {
13361
+ if (!variables.__consumed__) {
13362
+ Object.defineProperty(variables, "__consumed__", {
13363
+ value: new Set(),
13364
+ writable: true,
13365
+ enumerable: false,
13366
+ configurable: true
13367
+ });
13368
+ }
13369
+ variables.__consumed__.add(key);
13370
+ return String(variables[key]);
13371
+ }
13372
+ if (fallback !== undefined) return fallback;
13373
+ return "";
13374
+ });
13311
13375
  }
13312
13376
  }
13313
13377
  if (node.body) {
@@ -13337,6 +13401,7 @@ const cloneAst = (nodes) => {
13337
13401
  if (node.id !== undefined) nodeCopy.id = node.id;
13338
13402
  if (node.code !== undefined) nodeCopy.code = node.code;
13339
13403
  if (node.isSelfClosing !== undefined) nodeCopy.isSelfClosing = node.isSelfClosing;
13404
+ if (node.baseDir !== undefined) nodeCopy.baseDir = node.baseDir;
13340
13405
  if (node.props !== undefined) {
13341
13406
  nodeCopy.props = { ...node.props };
13342
13407
  }
@@ -13348,6 +13413,20 @@ const cloneAst = (nodes) => {
13348
13413
  return copy;
13349
13414
  };
13350
13415
 
13416
+ /**
13417
+ * Tags all STATIC_LOGIC and RUNTIME_LOGIC nodes in a subtree with their
13418
+ * source module's baseDir so the evaluator can resolve imports correctly.
13419
+ */
13420
+ const tagLogicNodes = (nodes, baseDir) => {
13421
+ if (!nodes) return;
13422
+ for (const node of nodes) {
13423
+ if ((node.type === STATIC_LOGIC || node.type === RUNTIME_LOGIC) && !node.baseDir) {
13424
+ node.baseDir = baseDir;
13425
+ }
13426
+ if (node.body) tagLogicNodes(node.body, baseDir);
13427
+ }
13428
+ };
13429
+
13351
13430
  /**
13352
13431
  * Handles all [import] and [$use-module] blocks in your code.
13353
13432
  * It loads the requested files, checks for errors, and puts the content into the main document.
@@ -13521,6 +13600,7 @@ async function resolveModules(ast, context) {
13521
13600
  format: context.format,
13522
13601
  filename: mod.path,
13523
13602
  baseDir: posix.dirname(mod.localPath),
13603
+ fs: context.instance.fs,
13524
13604
  mapperFile: context.instance.mapperFile,
13525
13605
  placeholders: context.instance.placeholders,
13526
13606
  variables: {},
@@ -13536,6 +13616,7 @@ async function resolveModules(ast, context) {
13536
13616
  });
13537
13617
 
13538
13618
  const subAst = await subSmark.parse();
13619
+ tagLogicNodes(subAst, posix.dirname(mod.localPath));
13539
13620
  context.instance.moduleCache.set(mod.localPath, subAst);
13540
13621
  expandedNodes = trimAst(subAst);
13541
13622
  }
@@ -13579,6 +13660,7 @@ async function resolveModules(ast, context) {
13579
13660
  format: context.format,
13580
13661
  filename: mod.path,
13581
13662
  baseDir: posix.dirname(mod.localPath),
13663
+ fs: context.instance.fs,
13582
13664
  mapperFile: context.instance.mapperFile,
13583
13665
  placeholders: context.instance.placeholders,
13584
13666
  variables: {}, // Parse without variables to keep the cached AST pure
@@ -13594,6 +13676,7 @@ async function resolveModules(ast, context) {
13594
13676
  });
13595
13677
 
13596
13678
  subAst = await subSmark.parse();
13679
+ tagLogicNodes(subAst, posix.dirname(mod.localPath));
13597
13680
  context.instance.moduleCache.set(mod.localPath, subAst);
13598
13681
  subAst = cloneAst(subAst);
13599
13682
  }
@@ -13611,6 +13694,7 @@ async function resolveModules(ast, context) {
13611
13694
  Object.entries(node.props).filter(([key]) => {
13612
13695
  if (key === "__consumed__") return false;
13613
13696
  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
13614
13698
  return true;
13615
13699
  })
13616
13700
  );
@@ -13663,6 +13747,13 @@ async function resolveModules(ast, context) {
13663
13747
  return ast;
13664
13748
  }
13665
13749
 
13750
+ /**
13751
+ * After full transpilation of the top-level file, apply any v{} fallbacks that
13752
+ * remain unresolved. Envelopes with no fallback are kept as-is (debugging signal).
13753
+ * Must NOT be called on sub-module ASTs — only on the final top-level AST.
13754
+ */
13755
+ const applyVariableFallbacks = (ast) => resolveAstVariables(ast, {});
13756
+
13666
13757
  /**
13667
13758
  * SomMark Rules Validator
13668
13759
  *
@@ -14157,6 +14248,7 @@ class SomMark {
14157
14248
  if (this.showSpinner) startSpinner();
14158
14249
  try {
14159
14250
  const ast = this.ast || await this.parse(src);
14251
+ applyVariableFallbacks(ast);
14160
14252
  let result = await transpiler({
14161
14253
  ast,
14162
14254
  format: this.targetFormat,