tw-framework 2.0.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/tw.mjs CHANGED
@@ -6915,20 +6915,39 @@ function sha12(data) {
6915
6915
  async function md52(data) {
6916
6916
  return hashWithWebCrypto("md5", data);
6917
6917
  }
6918
+ function webcryptoHashName(algorithm) {
6919
+ const key2 = String(algorithm).toLowerCase().replace(/[-_]/g, "");
6920
+ const map2 = {
6921
+ md5: "MD5",
6922
+ sha1: "SHA-1",
6923
+ sha224: "SHA-224",
6924
+ sha256: "SHA-256",
6925
+ sha384: "SHA-384",
6926
+ sha512: "SHA-512"
6927
+ };
6928
+ return map2[key2] ?? algorithm;
6929
+ }
6918
6930
  async function hmac2(data, key2, algorithm = "sha256") {
6919
6931
  const encoder2 = new TextEncoder();
6920
6932
  const keyData = encoder2.encode(key2);
6921
6933
  const messageData = encoder2.encode(data);
6922
- const cryptoKey = await crypto.subtle.importKey("raw", keyData, { name: "HMAC", hash: algorithm }, false, ["sign"]);
6934
+ const cryptoKey = await crypto.subtle.importKey("raw", keyData, { name: "HMAC", hash: webcryptoHashName(algorithm) }, false, ["sign"]);
6923
6935
  const signature = await crypto.subtle.sign("HMAC", cryptoKey, messageData);
6924
6936
  const hashArray = Array.from(new Uint8Array(signature));
6925
6937
  return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
6926
6938
  }
6939
+ async function verifyHmac(data, key2, signature, algorithm = "sha256") {
6940
+ const expected = await hmac2(data, key2, algorithm);
6941
+ if (expected.length !== signature.length) return false;
6942
+ let diff2 = 0;
6943
+ for (let i = 0; i < expected.length; i++) diff2 |= expected.charCodeAt(i) ^ signature.charCodeAt(i);
6944
+ return diff2 === 0;
6945
+ }
6927
6946
  async function pbkdf2(password, salt, iterations = 1e5, keyLength = 256) {
6928
6947
  const encoder2 = new TextEncoder();
6929
6948
  const keyMaterial = await crypto.subtle.importKey("raw", encoder2.encode(password), "PBKDF2", false, ["deriveBits"]);
6930
6949
  const derivedBits = await crypto.subtle.deriveBits(
6931
- { name: "PBKDF2", salt: encoder2.encode(salt), iterations, hash: "sha256" },
6950
+ { name: "PBKDF2", salt: encoder2.encode(salt), iterations, hash: "SHA-256" },
6932
6951
  keyMaterial,
6933
6952
  keyLength
6934
6953
  );
@@ -6973,9 +6992,16 @@ async function decryptString(encryptedData, password, salt) {
6973
6992
  const key2 = await deriveKey(password, salt);
6974
6993
  return decryptAES(encrypted, key2, iv);
6975
6994
  }
6995
+ async function generateRSASigningKeyPair(bits = 2048) {
6996
+ return crypto.subtle.generateKey(
6997
+ { name: "RSASSA-PKCS1-v1_5", modulusLength: bits, publicExponent: new Uint8Array([1, 0, 1]), hash: "SHA-256" },
6998
+ true,
6999
+ ["sign", "verify"]
7000
+ );
7001
+ }
6976
7002
  async function generateRSAKeyPair(bits = 2048) {
6977
7003
  return crypto.subtle.generateKey(
6978
- { name: "RSA-OAEP", modulusLength: bits, publicExponent: new Uint8Array([1, 0, 1]), hash: "sha256" },
7004
+ { name: "RSA-OAEP", modulusLength: bits, publicExponent: new Uint8Array([1, 0, 1]), hash: "SHA-256" },
6979
7005
  true,
6980
7006
  ["encrypt", "decrypt"]
6981
7007
  );
@@ -8423,36 +8449,6 @@ var init_etag = __esm({
8423
8449
  });
8424
8450
 
8425
8451
  // ../../packages/shared/tw/hashing/hmac.ts
8426
- function bufferToHex2(buffer) {
8427
- const bytes = new Uint8Array(buffer);
8428
- return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
8429
- }
8430
- function timingSafeEqual2(a, b) {
8431
- if (a.length !== b.length) return false;
8432
- let result = 0;
8433
- for (let i = 0; i < a.length; i++) {
8434
- result |= a.charCodeAt(i) ^ b.charCodeAt(i);
8435
- }
8436
- return result === 0;
8437
- }
8438
- async function hmac3(key2, message, algorithm = "SHA-256") {
8439
- const encoder2 = new TextEncoder();
8440
- const keyData = encoder2.encode(key2);
8441
- const messageData = encoder2.encode(message);
8442
- const cryptoKey = await crypto.subtle.importKey(
8443
- "raw",
8444
- keyData,
8445
- { name: "HMAC", hash: algorithm },
8446
- false,
8447
- ["sign"]
8448
- );
8449
- const signature = await crypto.subtle.sign("HMAC", cryptoKey, messageData);
8450
- return bufferToHex2(signature);
8451
- }
8452
- async function verifyHmac(key2, message, signature, algorithm = "SHA-256") {
8453
- const computed6 = await hmac3(key2, message, algorithm);
8454
- return timingSafeEqual2(computed6, signature);
8455
- }
8456
8452
  var init_hmac = __esm({
8457
8453
  "../../packages/shared/tw/hashing/hmac.ts"() {
8458
8454
  }
@@ -9117,6 +9113,7 @@ __export(tw_exports, {
9117
9113
  generatePIN: () => generatePIN,
9118
9114
  generateProofOfKnowledge: () => generateProofOfKnowledge,
9119
9115
  generateRSAKeyPair: () => generateRSAKeyPair,
9116
+ generateRSASigningKeyPair: () => generateRSASigningKeyPair,
9120
9117
  generateSecret: () => generateSecret,
9121
9118
  generateTOTP: () => generateTOTP,
9122
9119
  geolocation: () => geolocation,
@@ -9622,6 +9619,7 @@ __export(tw_exports, {
9622
9619
  zip: () => zip,
9623
9620
  zipWith: () => zipWith
9624
9621
  });
9622
+ import { createHmac } from "node:crypto";
9625
9623
  function generateETag(data) {
9626
9624
  let hash3 = 0;
9627
9625
  for (let i = 0; i < data.length; i++) {
@@ -9647,19 +9645,14 @@ function merkleRoot(hashes) {
9647
9645
  return level[0];
9648
9646
  }
9649
9647
  function hmacSign(data, key2) {
9650
- let h1 = 0, h22 = 0;
9651
- for (let i = 0; i < data.length; i++) {
9652
- h1 = (h1 << 5) - h1 + data.charCodeAt(i);
9653
- h1 |= 0;
9654
- }
9655
- for (let i = 0; i < key2.length; i++) {
9656
- h22 = (h22 << 5) - h22 + key2.charCodeAt(i);
9657
- h22 |= 0;
9658
- }
9659
- return (h1 ^ h22).toString(16);
9648
+ return createHmac("sha256", key2).update(data).digest("hex");
9660
9649
  }
9661
9650
  function hmacVerify(data, key2, signature) {
9662
- return hmacSign(data, key2) === signature;
9651
+ const expected = hmacSign(data, key2);
9652
+ if (expected.length !== signature.length) return false;
9653
+ let diff2 = 0;
9654
+ for (let i = 0; i < expected.length; i++) diff2 |= expected.charCodeAt(i) ^ signature.charCodeAt(i);
9655
+ return diff2 === 0;
9663
9656
  }
9664
9657
  var HTML_TAGS;
9665
9658
  var init_tw = __esm({
@@ -227257,6 +227250,10 @@ function parseTWSelector(cursor, errors, state, parseBody) {
227257
227250
  while (!cursor.done) {
227258
227251
  const ct = cursor.peek();
227259
227252
  if (!ct) break;
227253
+ if (ct.type === "IDENT" && ct.value === "on") {
227254
+ const after2 = cursor.peek(1);
227255
+ if (after2 && after2.type === "COLON") break;
227256
+ }
227260
227257
  if (ct.type === "IDENT" && !nextPrevMinus && nextClass.length > 0 && (CLASS_BOUNDARY_BOOLEAN_ATTRS.has(ct.value) || inlineAttrStartsAt(cursor))) {
227261
227258
  break;
227262
227259
  }
@@ -227547,7 +227544,9 @@ function parseTWSelector(cursor, errors, state, parseBody) {
227547
227544
  const first = cursor.peek();
227548
227545
  const second = cursor.peek(1);
227549
227546
  const CONTROL_FLOW = /* @__PURE__ */ new Set(["for", "if", "while", "switch", "else", "try", "catch"]);
227550
- const isAttrBlock = first && (first.type === "IDENT" || first.type === "KEYWORD") && !CONTROL_FLOW.has(first.value) && HTML_ATTRS_ONLY.has(first.value) && second && (second.type === "STRING" || second.type === "NUMBER" || second.type === "IDENT" || second.type === "KEYWORD");
227547
+ const third = cursor.peek(2);
227548
+ const isPropBlock = second && second.type === "ASSIGN" && third && third.type === "LBRACE";
227549
+ const isAttrBlock = first && (first.type === "IDENT" || first.type === "KEYWORD") && !CONTROL_FLOW.has(first.value) && (HTML_ATTRS_ONLY.has(first.value) || isPropBlock) && second && (second.type === "STRING" || second.type === "NUMBER" || second.type === "IDENT" || second.type === "KEYWORD" || second.type === "ASSIGN");
227551
227550
  if (isAttrBlock) {
227552
227551
  while (!cursor.done) {
227553
227552
  const t3 = cursor.peek();
@@ -227555,6 +227554,34 @@ function parseTWSelector(cursor, errors, state, parseBody) {
227555
227554
  if (t3.type === "IDENT" || t3.type === "KEYWORD") {
227556
227555
  const attrName = t3.value;
227557
227556
  cursor.advance();
227557
+ if (cursor.peek() && (cursor.peek().type === "ASSIGN" || cursor.peek().type === "EQUALS")) {
227558
+ cursor.advance();
227559
+ cursor.skipWhitespace();
227560
+ if (cursor.peek() && cursor.peek().type === "LBRACE") {
227561
+ cursor.advance();
227562
+ let expr = "";
227563
+ let depth = 1;
227564
+ let prevWS = false;
227565
+ while (!cursor.done) {
227566
+ const tk = cursor.peek();
227567
+ if (!tk || tk.type === "EOF") break;
227568
+ if (tk.type === "LBRACE") depth++;
227569
+ else if (tk.type === "RBRACE") {
227570
+ depth--;
227571
+ if (depth === 0) {
227572
+ cursor.advance();
227573
+ break;
227574
+ }
227575
+ }
227576
+ if (expr && prevWS) expr += " ";
227577
+ expr += tk.raw ?? tk.value;
227578
+ prevWS = !!tk.followedByWhitespace;
227579
+ cursor.advance();
227580
+ }
227581
+ el.attrs.push(createAttribute(attrName, expr.trim(), t3.pos.line, t3.pos.col, true));
227582
+ continue;
227583
+ }
227584
+ }
227558
227585
  const vToken = cursor.peek();
227559
227586
  if (vToken && (vToken.type === "STRING" || vToken.type === "NUMBER")) {
227560
227587
  cursor.advance();
@@ -236300,7 +236327,7 @@ var init_types4 = __esm({
236300
236327
  var TW_VERSION, TW_GENERATOR_META;
236301
236328
  var init_version = __esm({
236302
236329
  "../../packages/compiler/tw/codegen/version.ts"() {
236303
- TW_VERSION = "2.0.0";
236330
+ TW_VERSION = "2.0.1";
236304
236331
  TW_GENERATOR_META = `<meta name="generator" content="TW Framework ${TW_VERSION}">`;
236305
236332
  }
236306
236333
  });
@@ -236324,6 +236351,9 @@ function optimizedImageUrl(src, opts = {}) {
236324
236351
  if (opts.quality) p.set("q", String(opts.quality));
236325
236352
  p.set("f", opts.format ?? "auto");
236326
236353
  const qs = p.toString();
236354
+ if (/^https?:\/\//i.test(src)) {
236355
+ return `/_tw/img/_remote?src=${encodeURIComponent(src)}&${qs}`;
236356
+ }
236327
236357
  return `/_tw/img${src.startsWith("/") ? src : "/" + src}?${qs}`;
236328
236358
  }
236329
236359
  function imageAttributesFor(opts, config = DEFAULT_IMAGE_CONFIG) {
@@ -236331,7 +236361,7 @@ function imageAttributesFor(opts, config = DEFAULT_IMAGE_CONFIG) {
236331
236361
  const quality = opts.quality ?? config.quality;
236332
236362
  const format = config.formats[0] ?? "webp";
236333
236363
  const isRemote = /^https?:\/\//i.test(opts.src);
236334
- const src = isRemote ? `/_tw/img/_remote?src=${encodeURIComponent(opts.src)}&w=${opts.width ?? ""}&h=${opts.height ?? ""}&q=${quality}&f=auto` : optimizedImageUrl(opts.src, { width: opts.width, height: opts.height, quality });
236364
+ const src = isRemote ? `/_tw/img/_remote?src=${encodeURIComponent(opts.src)}${opts.width ? `&w=${opts.width}` : ""}${opts.height ? `&h=${opts.height}` : ""}&q=${quality}&f=auto` : optimizedImageUrl(opts.src, { width: opts.width, height: opts.height, quality });
236335
236365
  const widths = config.breakpoints.filter((bp) => width === 0 || bp <= width * 2);
236336
236366
  const srcset = widths.map(
236337
236367
  (bp) => isRemote ? `/_tw/img/_remote?src=${encodeURIComponent(opts.src)}&w=${bp}&q=${quality}&f=auto ${bp}w` : `${optimizedImageUrl(opts.src, { width: bp, quality })} ${bp}w`
@@ -236511,7 +236541,7 @@ function createImageHandler(options) {
236511
236541
  }
236512
236542
  if (!warnedNoSharp) {
236513
236543
  warnedNoSharp = true;
236514
- console.warn(" [tw/image] sharp not installed \u2014 serving original images (bun add sharp to enable resizing/WebP/AVIF)");
236544
+ console.warn(" [tw/image] sharp not installed \u2014 using the built-in converter (WebP/AVIF); install sharp for faster resizing");
236515
236545
  }
236516
236546
  return { data, contentType: "", viaSharp: false };
236517
236547
  }
@@ -236626,7 +236656,11 @@ var init_tw2 = __esm({
236626
236656
  });
236627
236657
 
236628
236658
  // ../../packages/link/tw/index.ts
236659
+ function isDangerousHref(href) {
236660
+ return DANGEROUS_SCHEME.test(href.replace(/[\u0000-\u0020]/g, ""));
236661
+ }
236629
236662
  function isExternalHref(href) {
236663
+ if (isDangerousHref(href)) return false;
236630
236664
  return /^[a-z][a-z0-9+.-]*:/i.test(href) || // any scheme: http(s), mailto, tel, ...
236631
236665
  href.startsWith("//");
236632
236666
  }
@@ -236635,7 +236669,8 @@ function isWebExternal(href) {
236635
236669
  return true;
236636
236670
  }
236637
236671
  function linkAttributesFor(opts) {
236638
- const href = (opts.href ?? "").trim();
236672
+ const rawHref = (opts.href ?? "").trim();
236673
+ const href = isDangerousHref(rawHref) ? "#" : rawHref;
236639
236674
  const external = opts.external === true || isExternalHref(href);
236640
236675
  const attrs = {};
236641
236676
  if (external) {
@@ -236664,8 +236699,10 @@ function linkAttributesFor(opts) {
236664
236699
  if (opts.id) attrs["id"] = opts.id;
236665
236700
  return { href, external, attrs };
236666
236701
  }
236702
+ var DANGEROUS_SCHEME;
236667
236703
  var init_tw3 = __esm({
236668
236704
  "../../packages/link/tw/index.ts"() {
236705
+ DANGEROUS_SCHEME = /^(javascript|data|vbscript):/i;
236669
236706
  }
236670
236707
  });
236671
236708
 
@@ -237178,12 +237215,31 @@ var init_interpolate = __esm({
237178
237215
  });
237179
237216
 
237180
237217
  // ../../packages/compiler/tw/eval/evaluate.ts
237218
+ function setEvalBindings(bindings) {
237219
+ extraBindings = bindings ?? {};
237220
+ }
237181
237221
  function evaluate(expr, vars, sandbox) {
237182
237222
  if (!expr) return "";
237183
237223
  const sb = sandbox ?? defaultSandbox;
237184
237224
  if (vars[expr] !== void 0) {
237185
237225
  return vars[expr];
237186
237226
  }
237227
+ if (expr.startsWith("(") && expr.endsWith(")")) {
237228
+ let d = 0;
237229
+ let wraps = true;
237230
+ for (let i = 0; i < expr.length; i++) {
237231
+ const ch = expr[i];
237232
+ if (ch === "(") d++;
237233
+ else if (ch === ")") {
237234
+ d--;
237235
+ if (d === 0 && i < expr.length - 1) {
237236
+ wraps = false;
237237
+ break;
237238
+ }
237239
+ }
237240
+ }
237241
+ if (wraps && d === 0) return evaluate(expr.slice(1, -1).trim(), vars, sb);
237242
+ }
237187
237243
  if (/^-?\d+(\.\d+)?$/.test(expr)) return expr;
237188
237244
  if (expr.startsWith('"') && expr.endsWith('"') || expr.startsWith("'") && expr.endsWith("'")) {
237189
237245
  return expr.slice(1, -1);
@@ -237191,11 +237247,54 @@ function evaluate(expr, vars, sandbox) {
237191
237247
  if (expr === "true") return "true";
237192
237248
  if (expr === "false") return "false";
237193
237249
  if (expr === "null" || expr === "undefined") return "";
237194
- const ternMatch = expr.match(/^(.+?)\s*\?\s*(.+?)\s*:\s*(.+)$/);
237195
- if (ternMatch) {
237196
- const cond = isTruthy(resolve3(ternMatch[1].trim(), vars, sb));
237197
- return cond ? resolve3(ternMatch[2].trim(), vars, sb) : resolve3(ternMatch[3].trim(), vars, sb);
237250
+ {
237251
+ let depth = 0;
237252
+ let pending2 = 0;
237253
+ let q = -1;
237254
+ let quote = "";
237255
+ for (let i = 0; i < expr.length; i++) {
237256
+ const ch = expr[i];
237257
+ if (quote) {
237258
+ if (ch === "\\") {
237259
+ i++;
237260
+ continue;
237261
+ }
237262
+ if (ch === quote) quote = "";
237263
+ continue;
237264
+ }
237265
+ if (ch === "'" || ch === '"' || ch === "`") {
237266
+ quote = ch;
237267
+ continue;
237268
+ }
237269
+ if (ch === "(" || ch === "[" || ch === "{") {
237270
+ depth++;
237271
+ continue;
237272
+ }
237273
+ if (ch === ")" || ch === "]" || ch === "}") {
237274
+ depth--;
237275
+ continue;
237276
+ }
237277
+ if (depth !== 0) continue;
237278
+ if (ch === "?") {
237279
+ if (pending2 === 0) q = i;
237280
+ pending2++;
237281
+ continue;
237282
+ }
237283
+ if (ch === ":" && pending2 > 0) {
237284
+ pending2--;
237285
+ if (pending2 > 0) continue;
237286
+ const cond = expr.slice(0, q).trim();
237287
+ const cons = expr.slice(q + 1, i).trim();
237288
+ const alt = expr.slice(i + 1).trim();
237289
+ if (cond && cons && alt) {
237290
+ return isTruthy(resolve3(cond, vars, sb)) ? resolve3(cons, vars, sb) : resolve3(alt, vars, sb);
237291
+ }
237292
+ break;
237293
+ }
237294
+ }
237198
237295
  }
237296
+ const dyn = evalWithVars(expr, vars);
237297
+ if (dyn !== null) return dyn;
237199
237298
  const orParts = splitLogical(expr, "||");
237200
237299
  if (orParts.length > 1) {
237201
237300
  for (const part of orParts) {
@@ -237272,7 +237371,7 @@ function evaluate(expr, vars, sandbox) {
237272
237371
  return current2 !== void 0 && current2 !== null ? String(current2) : "";
237273
237372
  }
237274
237373
  const fnMatch = expr.match(/^(\w+)\((.*)\)$/);
237275
- if (fnMatch) {
237374
+ if (fnMatch && isBuiltinName(fnMatch[1])) {
237276
237375
  const fn = fnMatch[1];
237277
237376
  const arg = resolve3(fnMatch[2].trim(), vars, sb);
237278
237377
  return callBuiltin(fn, arg, sb);
@@ -237291,6 +237390,38 @@ function evaluate(expr, vars, sandbox) {
237291
237390
  }
237292
237391
  return expr;
237293
237392
  }
237393
+ function evalWithVars(expr, vars) {
237394
+ if (!expr || /[`\\]/.test(expr)) return null;
237395
+ if (!/^[\w\s.$[\]{}()'"`,+\-*/%?:<>=!&|]*$/.test(expr)) return null;
237396
+ for (const m of expr.matchAll(/\.([A-Za-z_$][\w$]*)\s*\(/g)) {
237397
+ if (!SAFE_MEMBERS.has(m[1])) return null;
237398
+ }
237399
+ const names = [];
237400
+ const vals = [];
237401
+ for (const k of Object.keys(vars)) {
237402
+ if (!/^[A-Za-z_$][\w$]*$/.test(k)) continue;
237403
+ if (k in extraBindings) continue;
237404
+ names.push(k);
237405
+ try {
237406
+ vals.push(JSON.parse(vars[k]));
237407
+ } catch {
237408
+ vals.push(vars[k]);
237409
+ }
237410
+ }
237411
+ for (const k of Object.keys(extraBindings)) {
237412
+ if (!/^[A-Za-z_$][\w$]*$/.test(k)) continue;
237413
+ names.push(k);
237414
+ vals.push(extraBindings[k]);
237415
+ }
237416
+ try {
237417
+ const fn = new Function(...names, `"use strict"; return (${expr});`);
237418
+ const out = fn(...vals);
237419
+ if (out === void 0 || out === null) return null;
237420
+ return typeof out === "object" ? JSON.stringify(out) : String(out);
237421
+ } catch {
237422
+ return null;
237423
+ }
237424
+ }
237294
237425
  function resolve3(token, vars, sandbox) {
237295
237426
  const trimmed = token.trim();
237296
237427
  if (!trimmed) return "";
@@ -237459,7 +237590,7 @@ function coerce(value, targetType) {
237459
237590
  return value;
237460
237591
  }
237461
237592
  }
237462
- var defaultSandbox;
237593
+ var defaultSandbox, extraBindings, SAFE_MEMBERS;
237463
237594
  var init_evaluate = __esm({
237464
237595
  "../../packages/compiler/tw/eval/evaluate.ts"() {
237465
237596
  init_sandbox();
@@ -237467,6 +237598,64 @@ var init_evaluate = __esm({
237467
237598
  init_builtins();
237468
237599
  init_interpolate();
237469
237600
  defaultSandbox = new Sandbox();
237601
+ extraBindings = {};
237602
+ SAFE_MEMBERS = /* @__PURE__ */ new Set([
237603
+ "length",
237604
+ "toUpperCase",
237605
+ "toLowerCase",
237606
+ "trim",
237607
+ "trimStart",
237608
+ "trimEnd",
237609
+ "slice",
237610
+ "substring",
237611
+ "substr",
237612
+ "charAt",
237613
+ "at",
237614
+ "includes",
237615
+ "indexOf",
237616
+ "lastIndexOf",
237617
+ "startsWith",
237618
+ "endsWith",
237619
+ "replace",
237620
+ "replaceAll",
237621
+ "split",
237622
+ "join",
237623
+ "concat",
237624
+ "repeat",
237625
+ "padStart",
237626
+ "padEnd",
237627
+ "toString",
237628
+ "toFixed",
237629
+ "map",
237630
+ "filter",
237631
+ "find",
237632
+ "some",
237633
+ "every",
237634
+ "reduce",
237635
+ "sort",
237636
+ "reverse",
237637
+ // Static helpers on the built-ins (JSON.stringify, Object.keys, Math.round).
237638
+ "stringify",
237639
+ "parse",
237640
+ "keys",
237641
+ "values",
237642
+ "entries",
237643
+ "from",
237644
+ "isArray",
237645
+ "isInteger",
237646
+ "isFinite",
237647
+ "abs",
237648
+ "ceil",
237649
+ "floor",
237650
+ "round",
237651
+ "max",
237652
+ "min",
237653
+ "pow",
237654
+ "sqrt",
237655
+ "random",
237656
+ "now",
237657
+ "toISOString"
237658
+ ]);
237470
237659
  }
237471
237660
  });
237472
237661
 
@@ -237621,9 +237810,78 @@ function callBuiltin(fn, arg, sandbox) {
237621
237810
  function applyFilter(value, filter, args, vars, sandbox) {
237622
237811
  return callBuiltin(filter, value, sandbox);
237623
237812
  }
237813
+ function isBuiltinName(fn) {
237814
+ return BUILTIN_NAMES.has(fn);
237815
+ }
237816
+ var BUILTIN_NAMES;
237624
237817
  var init_builtins = __esm({
237625
237818
  "../../packages/compiler/tw/eval/builtins.ts"() {
237626
237819
  init_evaluate();
237820
+ BUILTIN_NAMES = /* @__PURE__ */ new Set([
237821
+ "Boolean",
237822
+ "Number",
237823
+ "String",
237824
+ "abs",
237825
+ "base64decode",
237826
+ "base64encode",
237827
+ "bool",
237828
+ "camelCase",
237829
+ "capitalize",
237830
+ "ceil",
237831
+ "cos",
237832
+ "date",
237833
+ "decodeURI",
237834
+ "encodeURI",
237835
+ "endsWith",
237836
+ "exp",
237837
+ "first",
237838
+ "float",
237839
+ "floor",
237840
+ "includes",
237841
+ "indexOf",
237842
+ "int",
237843
+ "join",
237844
+ "json",
237845
+ "kebabCase",
237846
+ "last",
237847
+ "len",
237848
+ "length",
237849
+ "log",
237850
+ "lower",
237851
+ "lowercase",
237852
+ "match",
237853
+ "max",
237854
+ "min",
237855
+ "now",
237856
+ "num",
237857
+ "padEnd",
237858
+ "padStart",
237859
+ "parseFloat",
237860
+ "parseInt",
237861
+ "parseJSON",
237862
+ "pow",
237863
+ "random",
237864
+ "repeat",
237865
+ "replace",
237866
+ "reverse",
237867
+ "round",
237868
+ "sin",
237869
+ "size",
237870
+ "slice",
237871
+ "snakeCase",
237872
+ "split",
237873
+ "sqrt",
237874
+ "startsWith",
237875
+ "str",
237876
+ "stringify",
237877
+ "tan",
237878
+ "test",
237879
+ "time",
237880
+ "title",
237881
+ "trim",
237882
+ "upper",
237883
+ "uppercase"
237884
+ ]);
237627
237885
  }
237628
237886
  });
237629
237887
 
@@ -238672,16 +238930,61 @@ function addScope(css, scopeId) {
238672
238930
  out += prelude;
238673
238931
  return out;
238674
238932
  }
238675
- function interpolate2(expr, vars) {
238676
- const masked = expr.replace(/\\\{/g, "O").replace(/\\\}/g, "C");
238677
- const out = masked.replace(/\{([^}]+)\}/g, (_, name) => {
238678
- const key2 = name.trim();
238933
+ function interpolateBraces(input, vars) {
238934
+ let out = "";
238935
+ let i = 0;
238936
+ while (i < input.length) {
238937
+ const ch = input[i];
238938
+ if (ch !== "{") {
238939
+ out += ch;
238940
+ i++;
238941
+ continue;
238942
+ }
238943
+ let depth = 1;
238944
+ let j = i + 1;
238945
+ let quote = "";
238946
+ while (j < input.length && depth > 0) {
238947
+ const c = input[j];
238948
+ if (quote) {
238949
+ if (c === "\\") {
238950
+ j += 2;
238951
+ continue;
238952
+ }
238953
+ if (c === quote) quote = "";
238954
+ j++;
238955
+ continue;
238956
+ }
238957
+ if (c === "'" || c === '"' || c === "`") {
238958
+ quote = c;
238959
+ j++;
238960
+ continue;
238961
+ }
238962
+ if (c === "{") depth++;
238963
+ else if (c === "}") {
238964
+ depth--;
238965
+ if (depth === 0) break;
238966
+ }
238967
+ j++;
238968
+ }
238969
+ if (depth !== 0) {
238970
+ out += ch;
238971
+ i++;
238972
+ continue;
238973
+ }
238974
+ const key2 = input.slice(i + 1, j).trim();
238679
238975
  if (/^[a-zA-Z_$][\w$]*$/.test(key2)) {
238680
- return vars[key2] ?? "";
238976
+ out += vars[key2] ?? "";
238977
+ } else {
238978
+ const val = evaluate(key2, vars);
238979
+ out += val === null ? "" : val;
238681
238980
  }
238682
- const val = evaluate(key2, vars);
238683
- return val === null ? "" : val;
238684
- });
238981
+ i = j + 1;
238982
+ }
238983
+ return out;
238984
+ }
238985
+ function interpolate2(expr, vars) {
238986
+ const masked = expr.replace(/\\\{/g, "O").replace(/\\\}/g, "C");
238987
+ const out = interpolateBraces(masked, vars);
238685
238988
  return out.replace(/\u0001O/g, "{").replace(/\u0001C/g, "}");
238686
238989
  }
238687
238990
  function isTruthy2(val) {
@@ -239160,7 +239463,21 @@ function renderBindings(bindings, ctx) {
239160
239463
  if (bindings.length === 0) return "";
239161
239464
  const parts = [];
239162
239465
  for (const binding of bindings) {
239163
- parts.push(`data-tw-bind-${binding.property}="${escapeAttr3(String(binding.expression ?? ""))}"`);
239466
+ const expr = String(binding.expression ?? "");
239467
+ let initial = "";
239468
+ try {
239469
+ if (/^[A-Za-z_$][\w$]*$/.test(expr)) {
239470
+ const v = ctx.stateVars[expr];
239471
+ if (v !== void 0 && v !== null) initial = String(v);
239472
+ } else if (expr) {
239473
+ initial = interpolate2("{" + expr + "}", ctx.stateVars);
239474
+ }
239475
+ } catch {
239476
+ }
239477
+ if (initial && !/[{}]/.test(initial)) {
239478
+ parts.push(`${binding.property}="${escapeAttr3(initial)}"`);
239479
+ }
239480
+ parts.push(`data-tw-bind-${binding.property}="${escapeAttr3(expr)}"`);
239164
239481
  }
239165
239482
  return parts.join(" ");
239166
239483
  }
@@ -239275,8 +239592,13 @@ function generateComponent(comp, ctx) {
239275
239592
  const savedStateVars = { ...ctx.stateVars };
239276
239593
  for (const prop of comp.props) {
239277
239594
  if (typeof prop.value === "string") {
239278
- const resolved = interpolate2(prop.value, ctx.stateVars);
239279
- ctx.stateVars[prop.name] = resolved;
239595
+ const raw = String(prop.value);
239596
+ const head = raw.split(/[.[]/)[0];
239597
+ if (!/[{}]/.test(raw) && Object.prototype.hasOwnProperty.call(ctx.stateVars, head)) {
239598
+ ctx.stateVars[prop.name] = interpolate2("{" + raw + "}", ctx.stateVars);
239599
+ } else {
239600
+ ctx.stateVars[prop.name] = interpolate2(raw, ctx.stateVars);
239601
+ }
239280
239602
  }
239281
239603
  }
239282
239604
  const componentProgram = componentRegistry.get(comp.name);
@@ -239370,6 +239692,14 @@ function generateFor(node, ctx) {
239370
239692
  }
239371
239693
  }
239372
239694
  }
239695
+ if (items.length === 0 && !/^\s*\[/.test(iterableStr)) {
239696
+ try {
239697
+ const evaluated = evaluate(String(node.iterable ?? "").replace(/^\{/, "").replace(/\}$/, "").trim(), ctx.stateVars);
239698
+ const parsed = JSON.parse(evaluated);
239699
+ if (Array.isArray(parsed)) items = parsed;
239700
+ } catch {
239701
+ }
239702
+ }
239373
239703
  for (let i = 0; i < items.length; i++) {
239374
239704
  const childCtx = { ...ctx };
239375
239705
  childCtx.stateVars = { ...ctx.stateVars };
@@ -243176,16 +243506,22 @@ function generateWithLayoutChain(layoutPrograms, pageProgram, stateVars) {
243176
243506
  }
243177
243507
  };
243178
243508
  let pageTitle = "TW Page";
243509
+ let pageDescription = "";
243179
243510
  for (const dir of pageProgram.directives || []) {
243180
- if (dir.type === "PageDirective" && dir.key === "title") {
243181
- const t3 = dir.value;
243182
- if (t3 != null && String(t3) !== "") pageTitle = String(t3);
243511
+ if (dir.type === "PageDirective") {
243512
+ if (dir.key === "title") {
243513
+ const t3 = dir.value;
243514
+ if (t3 != null && String(t3) !== "") pageTitle = String(t3);
243515
+ } else if (dir.key === "description") {
243516
+ const d = dir.value;
243517
+ if (d != null) pageDescription = String(d);
243518
+ }
243183
243519
  }
243184
243520
  }
243185
243521
  const pageCtx = createContext("ssr");
243186
243522
  seedCtx(pageCtx);
243187
243523
  try {
243188
- pageCtx.stateVars["page"] = JSON.stringify({ title: pageTitle });
243524
+ pageCtx.stateVars["page"] = JSON.stringify({ title: pageTitle, description: pageDescription });
243189
243525
  } catch {
243190
243526
  }
243191
243527
  {
@@ -243341,6 +243677,10 @@ function generate(program, stateVars, genOpts2) {
243341
243677
  const startTime = performance.now();
243342
243678
  const ctx = createContext("ssr");
243343
243679
  if (stateVars) ctx.stateVars = stateVars;
243680
+ if (genOpts2?.imports) {
243681
+ ctx.imports = genOpts2.imports;
243682
+ setEvalBindings(genOpts2.imports);
243683
+ } else setEvalBindings({});
243344
243684
  if (genOpts2 && genOpts2.scopedStyles === false) ctx.scopedStyles = false;
243345
243685
  if (genOpts2?.scopeId) ctx.scopeId = genOpts2.scopeId;
243346
243686
  if (genOpts2?.cssPrefix) ctx.cssPrefix = genOpts2.cssPrefix;
@@ -243425,6 +243765,7 @@ var init_vdom = __esm({
243425
243765
  init_types4();
243426
243766
  init_html2();
243427
243767
  init_builtin_components();
243768
+ init_evaluate();
243428
243769
  }
243429
243770
  });
243430
243771
 
@@ -243765,6 +244106,7 @@ var init_codes = __esm({
243765
244106
  TW095: { message: "Invalid for-loop iterable -- use {varName} or a valid comma-separated literal", severity: "error", category: "syntax" },
243766
244107
  TW096: { message: "Unknown state variable in interpolation (renders empty)", severity: "warning", category: "semantic" },
243767
244108
  TW097: { message: "while with a comparison renders its body ONCE at build time (state cannot mutate during SSR) -- use for loops for repetition", severity: "warning", category: "semantic" },
244109
+ TW098: { message: "Interpolation cannot be evaluated at build time -- it renders as its source text", severity: "warning", category: "semantic" },
243768
244110
  // Server blocks (TW099-TW101)
243769
244111
  TW099: { message: "Unknown server modifier (only ':defer' is supported)", severity: "error", category: "syntax" },
243770
244112
  TW100: { message: "Expected '{' after 'server'", severity: "error", category: "syntax" },
@@ -243852,6 +244194,7 @@ function checkUnknownTags(program, filePath, diags) {
243852
244194
  forEachNode(program, (node, ctx) => {
243853
244195
  if (!isElement(node)) return;
243854
244196
  if (node.tag[0] === node.tag[0]?.toUpperCase()) return;
244197
+ if (BUILTIN_COMPONENT_TAGS.has(node.tag)) return;
243855
244198
  const parent = ctx?.parent;
243856
244199
  if (parent && (parent.type === "Element" && typeof parent.tag === "string" && parent.tag[0] === parent.tag[0]?.toUpperCase() || parent.type === "Component")) return;
243857
244200
  if (HTML_ELEMENTS2.has(node.tag.toLowerCase())) return;
@@ -244206,6 +244549,59 @@ function checkUnknownStateVars(program, filePath, diags) {
244206
244549
  "null",
244207
244550
  "undefined"
244208
244551
  ]);
244552
+ const EVALUABLE_METHODS = /* @__PURE__ */ new Set([
244553
+ "length",
244554
+ "toUpperCase",
244555
+ "toLowerCase",
244556
+ "trim",
244557
+ "trimStart",
244558
+ "trimEnd",
244559
+ "slice",
244560
+ "substring",
244561
+ "substr",
244562
+ "charAt",
244563
+ "at",
244564
+ "includes",
244565
+ "indexOf",
244566
+ "lastIndexOf",
244567
+ "startsWith",
244568
+ "endsWith",
244569
+ "replace",
244570
+ "replaceAll",
244571
+ "split",
244572
+ "join",
244573
+ "concat",
244574
+ "repeat",
244575
+ "padStart",
244576
+ "padEnd",
244577
+ "toString",
244578
+ "toFixed",
244579
+ "map",
244580
+ "filter",
244581
+ "find",
244582
+ "some",
244583
+ "every",
244584
+ "reduce",
244585
+ "sort",
244586
+ "reverse",
244587
+ "stringify",
244588
+ "parse",
244589
+ "keys",
244590
+ "values",
244591
+ "entries",
244592
+ "from",
244593
+ "isArray",
244594
+ "round",
244595
+ "floor",
244596
+ "ceil",
244597
+ "abs",
244598
+ "max",
244599
+ "min",
244600
+ "pow",
244601
+ "sqrt",
244602
+ "random",
244603
+ "now"
244604
+ ]);
244209
244605
  for (const node of texts) {
244210
244606
  const text = String(node.value ?? "");
244211
244607
  if (!text.includes("{")) continue;
@@ -244221,6 +244617,22 @@ function checkUnknownStateVars(program, filePath, diags) {
244221
244617
  [`Declare it in the state block, or check the spelling`]
244222
244618
  ));
244223
244619
  }
244620
+ for (const m of text.matchAll(/\{([^{}]+)\}/g)) {
244621
+ const expr = m[1].trim();
244622
+ if (/^[A-Za-z_$][\w$]*$/.test(expr)) continue;
244623
+ for (const c of expr.matchAll(/([A-Za-z_$][\w$]*)\s*\(/g)) {
244624
+ const fn = c[1];
244625
+ if (known.has(fn) || BUILTINS2.has(fn) || EVALUABLE_METHODS.has(fn)) continue;
244626
+ diags.push(createDiagnostic(
244627
+ "TW098",
244628
+ node.line,
244629
+ node.col,
244630
+ filePath,
244631
+ `{${expr}}`,
244632
+ [`Cannot evaluate this at build time -- it renders as its source. Check the name, or simplify the expression`]
244633
+ ));
244634
+ }
244635
+ }
244224
244636
  }
244225
244637
  }
244226
244638
  function checkWhileComparisons(program, filePath, diags) {
@@ -244253,7 +244665,7 @@ function checkDeprecatedImports(program, filePath, diags) {
244253
244665
  }
244254
244666
  }
244255
244667
  }
244256
- var HTML_ELEMENTS2, HTML_ATTRIBUTES2, GLOBAL_ATTRIBUTES2, CSS_PROPERTIES2, CSS_ALIASES2, EVENT_TYPES2, ARIA_ROLES2, ARIA_ATTRIBUTES2, DEPRECATED_IMPORTS;
244668
+ var HTML_ELEMENTS2, HTML_ATTRIBUTES2, GLOBAL_ATTRIBUTES2, CSS_PROPERTIES2, CSS_ALIASES2, EVENT_TYPES2, ARIA_ROLES2, ARIA_ATTRIBUTES2, BUILTIN_COMPONENT_TAGS, DEPRECATED_IMPORTS;
244257
244669
  var init_rules = __esm({
244258
244670
  "../../packages/compiler/tw/diagnostics/rules.ts"() {
244259
244671
  init_visitors();
@@ -245117,6 +245529,7 @@ var init_rules = __esm({
245117
245529
  "aria-valuenow",
245118
245530
  "aria-valuetext"
245119
245531
  ]);
245532
+ BUILTIN_COMPONENT_TAGS = /* @__PURE__ */ new Set(["optImage", "twImage", "twLink", "twImg"]);
245120
245533
  DEPRECATED_IMPORTS = [
245121
245534
  { source: "utils/string/transform", name: "toHex", replacement: "stringToHex", removal: "2.1.0" },
245122
245535
  { source: "utils/string/transform", name: "fromHex", replacement: "hexToString", removal: "2.1.0" },
@@ -255429,7 +255842,8 @@ function genOpts(opts, scopeId) {
255429
255842
  scopedStyles: opts?.compiler?.scopedStyles,
255430
255843
  scopeId,
255431
255844
  cssPrefix: opts?.cssPrefix,
255432
- cssImportPaths: opts?.cssImportPaths
255845
+ cssImportPaths: opts?.cssImportPaths,
255846
+ imports: opts?.imports
255433
255847
  };
255434
255848
  }
255435
255849
  function scopeIdFor(filePath) {
@@ -256881,7 +257295,8 @@ var init_tw8 = __esm({
256881
257295
  }
256882
257296
  async runHook(name, ctx, ...args) {
256883
257297
  const results = await this.runHooks(name, ctx, ...args);
256884
- return results[results.length - 1];
257298
+ for (const r of results) if (r !== void 0) return r;
257299
+ return void 0;
256885
257300
  }
256886
257301
  // Value-chaining hook: each plugin receives the previous plugin's return
256887
257302
  // value and may return a new one. The transform:* and optimize:asset hooks
@@ -257739,7 +258154,7 @@ __export(twm_rules_exports, {
257739
258154
  isRuleDsl: () => isRuleDsl,
257740
258155
  parseRules: () => parseRules
257741
258156
  });
257742
- import { createHmac, timingSafeEqual as timingSafeEqual3 } from "node:crypto";
258157
+ import { createHmac as createHmac2, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
257743
258158
  function isRuleDsl(source) {
257744
258159
  const stripped = source.split("\n").map((l) => l.replace(/\/\/.*$/, "").trim()).filter((l) => l.length > 0).join("\n");
257745
258160
  return stripped.startsWith('rule "') || stripped.startsWith("rule '");
@@ -257837,14 +258252,14 @@ function verifyJwt_hs256(token, secret) {
257837
258252
  return false;
257838
258253
  }
257839
258254
  if (header?.alg !== "HS256") return false;
257840
- const expected = createHmac("sha256", secret).update(`${h3}.${p}`).digest();
258255
+ const expected = createHmac2("sha256", secret).update(`${h3}.${p}`).digest();
257841
258256
  let got;
257842
258257
  try {
257843
258258
  got = b64urlDecode(sig);
257844
258259
  } catch {
257845
258260
  return false;
257846
258261
  }
257847
- if (expected.length !== got.length || !timingSafeEqual3(expected, got)) return false;
258262
+ if (expected.length !== got.length || !timingSafeEqual2(expected, got)) return false;
257848
258263
  if (typeof payload?.exp === "number" && payload.exp * 1e3 < Date.now()) return false;
257849
258264
  return true;
257850
258265
  }
@@ -258529,6 +258944,18 @@ function findGlobalError(rootNode2) {
258529
258944
  function findRootError(rootNode2) {
258530
258945
  return (rootNode2.files || []).find((f) => f.type === "error");
258531
258946
  }
258947
+ function findNearestNotFound(rootNode2, pathParts) {
258948
+ let found = (rootNode2.files || []).find((f) => f.type === "not-found");
258949
+ let node = rootNode2;
258950
+ for (const part of pathParts) {
258951
+ const child = (node.children || []).find((c) => c.segment === part);
258952
+ if (!child) break;
258953
+ const f = (child.files || []).find((x) => x.type === "not-found");
258954
+ if (f) found = f;
258955
+ node = child;
258956
+ }
258957
+ return found;
258958
+ }
258532
258959
  function findRootNotFound(rootNode2) {
258533
258960
  return rootNode2.files.find((f) => f.type === "not-found");
258534
258961
  }
@@ -259330,6 +259757,26 @@ var init_render_pipeline = __esm({
259330
259757
  };
259331
259758
  }
259332
259759
  console.error(`[tw] server block failed for ${pageFile}: ${err2?.message ?? err2}`);
259760
+ try {
259761
+ const errFile = ctx.error ?? findRootError(tree);
259762
+ if (errFile) {
259763
+ const compiled = this.compileFile(errFile);
259764
+ let eh = String(compiled.html || "");
259765
+ const em = eh.match(/<body[^>]*>([\s\S]*)<\/body>/i);
259766
+ eh = (em ? em[1] : eh).replace(/<\/?html[^>]*>/gi, "").replace(/<\/?body[^>]*>/gi, "");
259767
+ return {
259768
+ html: this.assembleHTML(eh, compiled.css, "", "Error"),
259769
+ css: "",
259770
+ js: "",
259771
+ status: 500,
259772
+ headers: { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "private, no-store" },
259773
+ fromCache: false,
259774
+ durationMs: 0,
259775
+ serverBlocks: true
259776
+ };
259777
+ }
259778
+ } catch {
259779
+ }
259333
259780
  return {
259334
259781
  html: "<!DOCTYPE html><html><head><title>Server Error</title></head><body><h1>Internal Server Error</h1></body></html>",
259335
259782
  css: "",
@@ -259395,7 +259842,7 @@ var init_render_pipeline = __esm({
259395
259842
  }
259396
259843
  const match = matchRoute(tree, pathname);
259397
259844
  if (!match) {
259398
- const rootNotFound = findRootNotFound(tree);
259845
+ const rootNotFound = findNearestNotFound(tree, pathname.split("/").filter(Boolean));
259399
259846
  if (rootNotFound) {
259400
259847
  const compiled = this.compileFile(rootNotFound);
259401
259848
  let nf = String(compiled.html || "");
@@ -259461,6 +259908,7 @@ var init_render_pipeline = __esm({
259461
259908
  }
259462
259909
  let bodyHTML = pageCompiled.html;
259463
259910
  let allCSS = pageCompiled.css;
259911
+ const parallelSlots = [];
259464
259912
  try {
259465
259913
  const parentNode = match?.node;
259466
259914
  if (parentNode && Array.isArray(parentNode.children)) {
@@ -259471,9 +259919,7 @@ var init_render_pipeline = __esm({
259471
259919
  const sbm = slotBody.match(/<body[^>]*>([\s\S]*)<\/body>/i);
259472
259920
  if (sbm) slotBody = sbm[1];
259473
259921
  slotBody = slotBody.replace(/<\/?html[^>]*>/gi, "").replace(/<\/?body[^>]*>/gi, "");
259474
- const comp = slotName.charAt(0).toUpperCase() + slotName.slice(1);
259475
- const slotRe = new RegExp(`<${comp}(s[^>]*)?>([sS]*?)</${comp}>|<${comp}(s[^>]*)?/>`, "gi");
259476
- bodyHTML = bodyHTML.replace(slotRe, slotBody);
259922
+ parallelSlots.push({ comp: slotName.charAt(0).toUpperCase() + slotName.slice(1), body: slotBody });
259477
259923
  if (slotCompiled.css) allCSS = slotCompiled.css + "\n" + allCSS;
259478
259924
  }
259479
259925
  }
@@ -259492,6 +259938,11 @@ var init_render_pipeline = __esm({
259492
259938
  allJS = templateCompiled.js + "\n" + allJS;
259493
259939
  bodyHTML = this.wrapInLayout(templateCompiled.html, bodyHTML);
259494
259940
  }
259941
+ for (const { comp, body } of parallelSlots) {
259942
+ const slotRe = new RegExp(`<${comp}(\\s[^>]*)?>([\\s\\S]*?)<\\/${comp}>|<${comp}(\\s[^>]*)?/>`, "gi");
259943
+ bodyHTML = bodyHTML.replace(slotRe, body);
259944
+ bodyHTML = bodyHTML.replace(new RegExp(`<!--\\s*TW: unknown component '${comp}'[\\s\\S]*?-->`, "gi"), body);
259945
+ }
259495
259946
  let layoutTitle;
259496
259947
  const liftedParts = [];
259497
259948
  {
@@ -259910,8 +260361,6 @@ ${bodyHTML}`;
259910
260361
  };
259911
260362
  let tags = "";
259912
260363
  const description = pick2("description");
259913
- if (description) tags += ` <meta name="description" content="${esc5(description)}">
259914
- `;
259915
260364
  const keywords = pick2("keywords");
259916
260365
  if (keywords) tags += ` <meta name="keywords" content="${esc5(keywords)}">
259917
260366
  `;
@@ -262093,6 +262542,7 @@ var init_static = __esm({
262093
262542
  if (!existsSync21(filePath)) {
262094
262543
  filePath = join26(this.root, safePath + ".html");
262095
262544
  }
262545
+ let notFoundFallback = false;
262096
262546
  if (!existsSync21(filePath)) {
262097
262547
  filePath = join26(this.root, safePath, this.indexFile);
262098
262548
  }
@@ -262104,6 +262554,7 @@ var init_static = __esm({
262104
262554
  }
262105
262555
  } else {
262106
262556
  filePath = join26(this.root, "404.html");
262557
+ notFoundFallback = true;
262107
262558
  if (!existsSync21(filePath)) {
262108
262559
  return new Response("Not Found", { status: 404, headers: { "Content-Type": "text/plain" } });
262109
262560
  }
@@ -262171,12 +262622,12 @@ var init_static = __esm({
262171
262622
  const gzHeaders = { ...headers, "Content-Encoding": "gzip", "Content-Length": String(gz.length), Vary: "Accept-Encoding" };
262172
262623
  delete gzHeaders["Content-Length"];
262173
262624
  if (this.lastModified) delete gzHeaders["Last-Modified"];
262174
- return new Response(gz, { headers: gzHeaders });
262625
+ return new Response(gz, { headers: gzHeaders, status: notFoundFallback ? 404 : 200 });
262175
262626
  }
262176
262627
  } catch {
262177
262628
  }
262178
262629
  }
262179
- return new Response(bytes, { headers });
262630
+ return new Response(bytes, { headers, status: notFoundFallback ? 404 : 200 });
262180
262631
  }
262181
262632
  /**
262182
262633
  * Parse a `Range: bytes=...` header against a known content length.
@@ -262826,6 +263277,12 @@ var init_xss_detector = __esm({
262826
263277
  { name: "SQL comment", regex: /--\s/g, severity: "medium", recommendation: "Check for SQL comment injection" },
262827
263278
  { name: "Stacked queries", regex: /;\s*(SELECT|INSERT|UPDATE|DELETE|DROP|UNION)/gi, severity: "high", recommendation: "Prevent stacked queries" },
262828
263279
  { name: "UNION injection", regex: /UNION\s+SELECT/gi, severity: "critical", recommendation: "Filter UNION SELECT from user input" },
263280
+ // Comment-only injections (`admin'--`, `admin'#`, `admin'/*`): a quote
263281
+ // followed by a comment truncates the query and bypasses the login
263282
+ // check, with no OR/UNION for the other patterns to catch.
263283
+ { name: "Comment-terminated injection", regex: /['"`]\s*(--|#|\/\*)/g, severity: "high", recommendation: "A quote followed by a comment truncates the query -- use parameterized queries" },
263284
+ // Parenthesised boolean injection: `') OR ('1'='1`.
263285
+ { name: "Parenthesised boolean injection", regex: /['"`]?\s*\)\s*(OR|AND)\s*\(?\s*['"`]?\s*\d/i, severity: "high", recommendation: "Use parameterized queries instead of building boolean expressions" },
262829
263286
  { name: "Boolean-based injection", regex: /OR\s+1\s*=\s*1/gi, severity: "high", recommendation: "Use parameterized queries to prevent boolean injection" },
262830
263287
  { name: "Time-based injection", regex: /WAITFOR\s+DELAY/gi, severity: "high", recommendation: "Filter WAITFOR DELAY from user input" },
262831
263288
  { name: "DROP statement", regex: /DROP\s+(TABLE|DATABASE)/gi, severity: "critical", recommendation: "Prevent DROP statements in queries" },
@@ -263356,6 +263813,7 @@ var init_logger3 = __esm({
263356
263813
  }
263357
263814
  /** Records an audit event. */
263358
263815
  log(event) {
263816
+ event = { type: "system", actor: "unknown", resource: "unknown", ...event };
263359
263817
  const fullEvent = {
263360
263818
  ...event,
263361
263819
  id: crypto.randomUUID(),
@@ -264372,6 +264830,13 @@ var FILE_SIGNATURES, DANGEROUS_EXTENSIONS, DANGEROUS_MIME_TYPES, ContentSecurity
264372
264830
  var init_protector = __esm({
264373
264831
  "../../packages/security/tw/content-security/protector.ts"() {
264374
264832
  FILE_SIGNATURES = [
264833
+ // Executables and HTML: without these, content renamed to an image extension
264834
+ // had no detected type, the signature check was skipped, and the upload was
264835
+ // accepted as risk "low" -- the exact rename bypass the check exists for.
264836
+ { ext: ["exe", "dll", "sys"], mime: "application/x-msdownload", signature: [77, 90] },
264837
+ { ext: ["elf", "so", "bin"], mime: "application/x-executable", signature: [127, 69, 76, 70] },
264838
+ { ext: ["html", "htm", "xhtml"], mime: "text/html", signature: [60, 104, 116, 109, 108] },
264839
+ { ext: ["html", "htm"], mime: "text/html", signature: [60, 33, 68, 79, 67, 84, 89, 80, 69] },
264375
264840
  { ext: ["jpg", "jpeg"], mime: "image/jpeg", signature: [255, 216, 255] },
264376
264841
  { ext: ["png"], mime: "image/png", signature: [137, 80, 78, 71, 13, 10, 26, 10] },
264377
264842
  { ext: ["gif"], mime: "image/gif", signature: [71, 73, 70, 56] },
@@ -264423,10 +264888,39 @@ var init_protector = __esm({
264423
264888
  ".htpasswd",
264424
264889
  ".svg",
264425
264890
  // Can contain JavaScript
264426
- ".xml"
264891
+ ".xml",
264427
264892
  // Can contain XXE
264893
+ // Active-content types the set used to let through while still blocking .svg
264894
+ // "because it can contain JavaScript" -- .html/.js are script-capable by
264895
+ // definition, so an accepted upload served from our own origin is stored XSS.
264896
+ ".html",
264897
+ ".htm",
264898
+ ".xhtml",
264899
+ ".shtml",
264900
+ ".js",
264901
+ ".mjs",
264902
+ ".cjs",
264903
+ ".jsx",
264904
+ ".ts",
264905
+ ".tsx",
264906
+ ".mhtml",
264907
+ ".hta",
264908
+ ".jsp",
264909
+ ".asp",
264910
+ ".aspx",
264911
+ ".cgi",
264912
+ ".pl"
264428
264913
  ]);
264429
264914
  DANGEROUS_MIME_TYPES = /* @__PURE__ */ new Set([
264915
+ // Active-content types: they can carry script (svg, xhtml, xml) or be
264916
+ // rendered as a document (html, pdf). Blocking .svg by extension while
264917
+ // reporting isContentTypeSafe("image/svg+xml") === true was inconsistent.
264918
+ "image/svg+xml",
264919
+ "text/html",
264920
+ "application/xhtml+xml",
264921
+ "application/pdf",
264922
+ "text/xml",
264923
+ "application/xml",
264430
264924
  "application/javascript",
264431
264925
  "text/javascript",
264432
264926
  "application/x-javascript",
@@ -264481,7 +264975,9 @@ var init_protector = __esm({
264481
264975
  reason: "Empty file",
264482
264976
  sanitizedFilename: sanitized,
264483
264977
  detectedType: "unknown",
264484
- risk: "low"
264978
+ // Unknown content is not low risk -- it is content whose type we could
264979
+ // not establish, which is what a rename bypass looks like.
264980
+ risk: "medium"
264485
264981
  };
264486
264982
  }
264487
264983
  if (this.blockedExtensions.has(ext)) {
@@ -265644,6 +266140,7 @@ var init_nonce_manager = __esm({
265644
266140
  validate(nonce) {
265645
266141
  const entry = this.nonces.get(nonce);
265646
266142
  if (!entry) return false;
266143
+ if (entry.used) return false;
265647
266144
  if (Date.now() > entry.expiresAt) {
265648
266145
  this.nonces.delete(nonce);
265649
266146
  return false;
@@ -266436,7 +266933,15 @@ var init_token_manager = __esm({
266436
266933
  rotateAfterUse;
266437
266934
  maxTokens;
266438
266935
  tokens = /* @__PURE__ */ new Map();
266936
+ /** Tokens this manager issued and already consumed. A rotated token is
266937
+ * deleted from `tokens`, so without this marker a replay found no entry and
266938
+ * fell through to the stateless branch, which accepted it -- defeating the
266939
+ * documented single-use guarantee of `rotateAfterUse`. */
266940
+ consumedTokens = /* @__PURE__ */ new Set();
266439
266941
  constructor(options) {
266942
+ if (!options || typeof options.secret !== "string" || options.secret.length === 0) {
266943
+ throw new Error("createCSRFManager: options.secret is required (the HMAC key used to sign tokens)");
266944
+ }
266440
266945
  this.secret = options.secret;
266441
266946
  this.tokenLength = options.tokenLength ?? 32;
266442
266947
  this.ttl = options.ttl ?? 36e5;
@@ -266536,12 +267041,16 @@ var init_token_manager = __esm({
266536
267041
  }
266537
267042
  entry.used = true;
266538
267043
  if (this.rotateAfterUse) {
267044
+ this.consumedTokens.add(headerPart);
266539
267045
  this.tokens.delete(headerPart);
266540
267046
  const rotated = await this.generate(sessionId ?? void 0);
266541
267047
  return { valid: true, rotated };
266542
267048
  }
266543
267049
  return { valid: true };
266544
267050
  }
267051
+ if (this.consumedTokens.has(headerPart)) {
267052
+ return { valid: false, reason: "Token already used" };
267053
+ }
266545
267054
  const expectedSig = await hmacSign3(
266546
267055
  this.secret,
266547
267056
  `${headerPart}.${sessionId ?? ""}`
@@ -272417,17 +272926,17 @@ async function hs256(payload, secret, maxAge) {
272417
272926
  const header = { alg: "HS256", typ: "JWT" };
272418
272927
  const body = { ...payload, iat: now, exp: now + maxAge };
272419
272928
  const data = enc(header) + "." + enc(body);
272420
- const { createHmac: createHmac2 } = await import("node:crypto");
272421
- const sig = createHmac2("sha256", secret).update(data).digest("base64url");
272929
+ const { createHmac: createHmac3 } = await import("node:crypto");
272930
+ const sig = createHmac3("sha256", secret).update(data).digest("base64url");
272422
272931
  return data + "." + sig;
272423
272932
  }
272424
272933
  async function verifyHs256(token, secret) {
272425
272934
  const parts = token.split(".");
272426
272935
  if (parts.length !== 3) return null;
272427
- const { createHmac: createHmac2, timingSafeEqual: timingSafeEqual4 } = await import("node:crypto");
272428
- const expected = createHmac2("sha256", secret).update(parts[0] + "." + parts[1]).digest("base64url");
272936
+ const { createHmac: createHmac3, timingSafeEqual: timingSafeEqual3 } = await import("node:crypto");
272937
+ const expected = createHmac3("sha256", secret).update(parts[0] + "." + parts[1]).digest("base64url");
272429
272938
  const a = Buffer.from(expected), b = Buffer.from(parts[2]);
272430
- if (a.length !== b.length || !timingSafeEqual4(a, b)) return null;
272939
+ if (a.length !== b.length || !timingSafeEqual3(a, b)) return null;
272431
272940
  try {
272432
272941
  const body = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8"));
272433
272942
  if (typeof body.exp === "number" && body.exp < Math.floor(Date.now() / 1e3)) return null;
@@ -273732,6 +274241,19 @@ ${err2.stack ?? ""}`);
273732
274241
  const isrLookupPath = pathname.length > 1 ? pathname.replace(/\/+$/, "") : pathname;
273733
274242
  if (this.staticHandler && !this.isrRoutes.has(isrLookupPath)) {
273734
274243
  let staticResponse = await this.staticHandler.serve(pathname, request);
274244
+ if (staticResponse.status !== 404) {
274245
+ const reqMethod = request.method.toUpperCase();
274246
+ const allow = "GET, HEAD, OPTIONS";
274247
+ if (reqMethod === "TRACE") {
274248
+ return new Response("Method Not Allowed", { status: 405, headers: { Allow: allow } });
274249
+ }
274250
+ if (reqMethod === "OPTIONS") {
274251
+ return new Response(null, { status: 204, headers: { Allow: allow } });
274252
+ }
274253
+ if (reqMethod !== "GET" && reqMethod !== "HEAD") {
274254
+ return new Response("Method Not Allowed", { status: 405, headers: { Allow: allow } });
274255
+ }
274256
+ }
273735
274257
  if (staticResponse && this.cspEnabled() && (staticResponse.headers.get("content-type") || "").includes("text/html")) {
273736
274258
  try {
273737
274259
  let buf = Buffer.from(await staticResponse.arrayBuffer());
@@ -273876,6 +274398,32 @@ ${err2.stack ?? ""}`);
273876
274398
  });
273877
274399
 
273878
274400
  // ../../packages/runtime/tw/dependency-graph.ts
274401
+ function flushPendingSubs() {
274402
+ if (isFlushing || !pendingSubs) return;
274403
+ isFlushing = true;
274404
+ try {
274405
+ let guard = 0;
274406
+ while (pendingSubs && pendingSubs.size > 0 && guard++ < 1e5) {
274407
+ const batchOf = pendingSubs;
274408
+ pendingSubs = /* @__PURE__ */ new Set();
274409
+ for (const sub of batchOf) {
274410
+ try {
274411
+ sub();
274412
+ } catch (e) {
274413
+ console.error("[TW Reactivity] Signal subscriber error:", e);
274414
+ }
274415
+ }
274416
+ }
274417
+ } finally {
274418
+ isFlushing = false;
274419
+ pendingSubs = null;
274420
+ }
274421
+ }
274422
+ function enqueueSubs(subs) {
274423
+ if (!pendingSubs) pendingSubs = /* @__PURE__ */ new Set();
274424
+ for (const s of subs) pendingSubs.add(s);
274425
+ if (batchDepth === 0) flushPendingSubs();
274426
+ }
273879
274427
  function startBatch() {
273880
274428
  batchDepth++;
273881
274429
  }
@@ -273885,13 +274433,7 @@ function endBatch() {
273885
274433
  batchDepth = 0;
273886
274434
  const effects = Array.from(batchedEffects);
273887
274435
  batchedEffects.clear();
273888
- for (const eff of effects) {
273889
- try {
273890
- eff();
273891
- } catch (e) {
273892
- console.error("[TW Reactivity] Batch effect error:", e);
273893
- }
273894
- }
274436
+ enqueueSubs(effects);
273895
274437
  }
273896
274438
  }
273897
274439
  function batch(fn) {
@@ -274073,12 +274615,14 @@ function isComputed(value) {
274073
274615
  function createScope3() {
274074
274616
  return new ReactiveScope();
274075
274617
  }
274076
- var currentEffect, batchDepth, batchedEffects, SignalImpl, ComputedImpl, watchEffect, currentCleanup, ReactiveScope;
274618
+ var currentEffect, batchDepth, batchedEffects, pendingSubs, isFlushing, SignalImpl, ComputedImpl, watchEffect, currentCleanup, ReactiveScope;
274077
274619
  var init_dependency_graph = __esm({
274078
274620
  "../../packages/runtime/tw/dependency-graph.ts"() {
274079
274621
  currentEffect = null;
274080
274622
  batchDepth = 0;
274081
274623
  batchedEffects = /* @__PURE__ */ new Set();
274624
+ pendingSubs = null;
274625
+ isFlushing = false;
274082
274626
  SignalImpl = class {
274083
274627
  value;
274084
274628
  subscribers = /* @__PURE__ */ new Set();
@@ -274097,19 +274641,9 @@ var init_dependency_graph = __esm({
274097
274641
  if (this.equals(this.value, value)) return;
274098
274642
  this.value = value;
274099
274643
  if (batchDepth > 0) {
274100
- for (const sub of this.subscribers) {
274101
- batchedEffects.add(sub);
274102
- }
274103
- } else {
274104
- const subs = Array.from(this.subscribers);
274105
- for (const sub of subs) {
274106
- try {
274107
- sub();
274108
- } catch (e) {
274109
- console.error("[TW Reactivity] Signal subscriber error:", e);
274110
- }
274111
- }
274644
+ for (const sub of this.subscribers) batchedEffects.add(sub);
274112
274645
  }
274646
+ enqueueSubs(this.subscribers);
274113
274647
  }
274114
274648
  update(fn) {
274115
274649
  this.set(fn(this.value));
@@ -274175,14 +274709,11 @@ var init_dependency_graph = __esm({
274175
274709
  return this.cachedValue;
274176
274710
  }
274177
274711
  notify() {
274178
- const subs = Array.from(this.subscribers);
274179
- for (const sub of subs) {
274180
- try {
274181
- sub();
274182
- } catch (e) {
274183
- console.error("[TW Reactivity] Computed subscriber error:", e);
274184
- }
274712
+ if (batchDepth > 0) {
274713
+ for (const sub of this.subscribers) batchedEffects.add(sub);
274714
+ return;
274185
274715
  }
274716
+ enqueueSubs(this.subscribers);
274186
274717
  }
274187
274718
  set(_value) {
274188
274719
  throw new Error("Cannot set a computed signal");
@@ -277367,10 +277898,12 @@ var init_diff = __esm({
277367
277898
  "../../packages/runtime/tw/diff.ts"() {
277368
277899
  init_vdom2();
277369
277900
  HTML_ESCAPE_MAP = {
277370
- "&": "&",
277371
- "<": "<",
277372
- ">": ">",
277373
- '"': '"',
277901
+ // These four mapped to THEMSELVES, so a <script> tag passed through
277902
+ // untouched -- only ' was actually escaped.
277903
+ "&": "&amp;",
277904
+ "<": "&lt;",
277905
+ ">": "&gt;",
277906
+ '"': "&quot;",
277374
277907
  "'": "&#39;"
277375
277908
  };
277376
277909
  HTML_ESCAPE_REGEX = /[&<>"']/g;
@@ -280900,8 +281433,19 @@ async function hydrateElementById(id, component, root = typeof document !== "und
280900
281433
  );
280901
281434
  }
280902
281435
  function markForHydration(element, componentId, props) {
281436
+ if (!element || typeof componentId !== "string") return;
280903
281437
  element.setAttribute(HYDRATE_ATTR, componentId);
280904
- element.setAttribute("data-tw-props", JSON.stringify(props));
281438
+ let serialised = null;
281439
+ if (props && typeof props === "object") {
281440
+ try {
281441
+ serialised = JSON.stringify(props);
281442
+ } catch {
281443
+ serialised = null;
281444
+ }
281445
+ }
281446
+ if (serialised !== null && serialised !== void 0) {
281447
+ element.setAttribute("data-tw-props", serialised);
281448
+ }
280905
281449
  }
280906
281450
  function unmarkForHydration(element) {
280907
281451
  element.removeAttribute(HYDRATE_ATTR);
@@ -284201,15 +284745,21 @@ function usePathname(opts) {
284201
284745
  function matchPathPattern(path, pattern) {
284202
284746
  const pSeg = path.split("/").filter(Boolean);
284203
284747
  const tSeg = pattern.split("/").filter(Boolean);
284204
- if (pSeg.length !== tSeg.length) return { matches: false, params: {}, score: 0 };
284748
+ const catchAllAt = tSeg.findIndex((t3) => t3.startsWith("[...") || t3.startsWith(":..."));
284749
+ if (catchAllAt === -1) {
284750
+ if (pSeg.length !== tSeg.length) return { matches: false, params: {}, score: 0 };
284751
+ } else if (pSeg.length < tSeg.length) {
284752
+ return { matches: false, params: {}, score: 0 };
284753
+ }
284205
284754
  const params = {};
284206
284755
  let literal = 0;
284207
284756
  for (let i = 0; i < tSeg.length; i++) {
284208
284757
  const t3 = tSeg[i];
284209
284758
  const isParam = t3.startsWith("[") && t3.endsWith("]") || t3.startsWith(":");
284210
284759
  if (isParam) {
284211
- const name = t3.startsWith("[") ? t3.slice(1, -1).replace(/\.\.\.$/, "") : t3.slice(1);
284212
- params[name] = decodeURIComponent(pSeg[i]);
284760
+ const name = t3.startsWith("[") ? t3.slice(1, -1).replace(/^\.\.\./, "") : t3.slice(1);
284761
+ const isCatchAll = t3.startsWith("[...") || t3.startsWith(":...");
284762
+ params[name] = isCatchAll ? pSeg.slice(i).map((seg) => decodeURIComponent(seg)).join("/") : decodeURIComponent(pSeg[i]);
284213
284763
  } else if (t3 !== pSeg[i]) {
284214
284764
  return { matches: false, params: {}, score: 0 };
284215
284765
  } else {
@@ -285024,7 +285574,11 @@ function defineStore(idOrDefinition, maybeDefinition) {
285024
285574
  const getterDeps = Object.values(reactiveState);
285025
285575
  for (const key2 of Object.keys(getters)) {
285026
285576
  const getter = getters[key2];
285027
- getterValues[key2] = computed4(getterDeps, getter);
285577
+ getterValues[key2] = computed4(getterDeps, () => {
285578
+ const snap = {};
285579
+ for (const k of Object.keys(reactiveState)) snap[k] = reactiveState[k].value;
285580
+ return getter.call(snap, snap);
285581
+ });
285028
285582
  }
285029
285583
  const subscribers = /* @__PURE__ */ new Set();
285030
285584
  const actionListeners = /* @__PURE__ */ new Set();
@@ -285067,7 +285621,7 @@ function defineStore(idOrDefinition, maybeDefinition) {
285067
285621
  const fn = actions[key2];
285068
285622
  boundActions[key2] = (...args) => {
285069
285623
  actionListeners.forEach((cb) => cb({ name: key2, args }));
285070
- return fn.call(ctx, stateProxy, ...args);
285624
+ return fn.call(stateProxy, stateProxy, ...args);
285071
285625
  };
285072
285626
  }
285073
285627
  for (const rv of Object.values(reactiveState)) {
@@ -285179,9 +285733,10 @@ var init_store = __esm({
285179
285733
  };
285180
285734
  }
285181
285735
  _notify() {
285736
+ const current2 = this.value ?? this._value;
285182
285737
  for (const l of this._listeners) {
285183
285738
  try {
285184
- l();
285739
+ l(current2);
285185
285740
  } catch {
285186
285741
  }
285187
285742
  }
@@ -286187,7 +286742,8 @@ var init_virtual_list = __esm({
286187
286742
  return this.container.scrollTop;
286188
286743
  }
286189
286744
  getFirstVisibleIndex() {
286190
- return this.visibleRange.start;
286745
+ const last = Math.max(0, this.options?.itemCount ?? 0 - 1);
286746
+ return Math.min(Math.max(this.visibleRange.start, 0), last);
286191
286747
  }
286192
286748
  getLastVisibleIndex() {
286193
286749
  return this.visibleRange.end;
@@ -289490,8 +290046,6 @@ function buildMetaTagsFromSource(source) {
289490
290046
  };
289491
290047
  let tags = "";
289492
290048
  const description = pick2("description");
289493
- if (description) tags += ` <meta name="description" content="${esc5(description)}">
289494
- `;
289495
290049
  const keywords = pick2("keywords");
289496
290050
  if (keywords) tags += ` <meta name="keywords" content="${esc5(keywords)}">
289497
290051
  `;
@@ -290108,6 +290662,7 @@ async function buildCommand() {
290108
290662
  if ((renderMode === "ssr" || renderMode === "stream") && !hasExplicitParams) {
290109
290663
  requestRenderedCount++;
290110
290664
  console.log(" \x1B[36m~\x1B[0m " + routePath + " [" + renderMode + ": rendered at request time]");
290665
+ isrRoutes.set(routePath === "" ? "/" : routePath, 0);
290111
290666
  try {
290112
290667
  const src = readFileSync18(pageFile, "utf-8");
290113
290668
  if (serverBlocksOn) {
@@ -290208,7 +290763,30 @@ async function buildCommand() {
290208
290763
  }
290209
290764
  }
290210
290765
  const scopeVars = { ...paramSet, params: paramSet, ...serverVars };
290211
- const result = _compileSync(source, { filePath: pageFile, stateVars: scopeVars, ...compilePassOpts() });
290766
+ let pageImports = {};
290767
+ try {
290768
+ const { twImportTs: twImportTs2 } = await Promise.resolve().then(() => (init_node_import(), node_import_exports));
290769
+ const { dirname: dirname17, resolve: resolvePath } = await import("node:path");
290770
+ const parseFn = _compilerMod.parseWithDetails;
290771
+ const parsed = parseFn(source, pageFile);
290772
+ const prog = parsed?.program ?? parsed?.ast;
290773
+ for (const d of prog?.directives ?? []) {
290774
+ if (d?.type !== "ImportDirective" || !d.source || d.isTypeOnly) continue;
290775
+ const spec = String(d.source);
290776
+ if (!(spec.startsWith("./") || spec.startsWith("../") || spec.startsWith("@./"))) continue;
290777
+ const abs = spec.startsWith("@./") ? resolvePath(rootDir, spec.slice(3)) : resolvePath(dirname17(pageFile), spec);
290778
+ const mod = await twImportTs2(abs);
290779
+ if (d.namespaceImport) pageImports[d.namespaceImport] = mod;
290780
+ if (d.defaultImport) pageImports[d.defaultImport] = mod?.default ?? mod;
290781
+ for (const it of d.items ?? []) {
290782
+ const name = String(it).split(/\s+as\s+/).pop()?.trim();
290783
+ if (name && mod && name in mod) pageImports[name] = mod[name];
290784
+ }
290785
+ }
290786
+ } catch (e) {
290787
+ if (process.env.TW_DEBUG === "1") console.error("[tw] page imports failed:", e?.message ?? e);
290788
+ }
290789
+ const result = _compileSync(source, { filePath: pageFile, stateVars: scopeVars, imports: pageImports, ...compilePassOpts() });
290212
290790
  {
290213
290791
  const used = /* @__PURE__ */ new Set();
290214
290792
  collectComponentUsages(result.ast, used);
@@ -293906,7 +294484,7 @@ async function createPackageJson(dir, name) {
293906
294484
  ship: "tw ship"
293907
294485
  },
293908
294486
  dependencies: {
293909
- "tw-framework": "^2.0.0"
294487
+ "tw-framework": "^2.0.1"
293910
294488
  },
293911
294489
  devDependencies: {
293912
294490
  typescript: "^5.4.0",
@@ -293993,7 +294571,7 @@ dist/
293993
294571
  async function createReadme(dir, name) {
293994
294572
  const content = `# ${name}
293995
294573
 
293996
- Built with TW Framework 2.0.0.
294574
+ Built with TW Framework 2.0.1.
293997
294575
 
293998
294576
  ## Getting Started
293999
294577
 
@@ -295685,7 +296263,31 @@ async function devCommand() {
295685
296263
  try {
295686
296264
  const source = readFileSync19(twFile, "utf-8");
295687
296265
  if (typeof beginCssRouteCapture2 === "function") beginCssRouteCapture2(path);
295688
- const result = compileSync3(source, { filePath: twFile, stateVars: resolved.params });
296266
+ let result = compileSync3(source, { filePath: twFile, stateVars: resolved.params });
296267
+ try {
296268
+ const _c = await Promise.resolve().then(() => (init_tw7(), tw_exports3));
296269
+ const blocks = _c.collectServerBlocks(result.ast);
296270
+ if (blocks.length) {
296271
+ const values = await _c.executeServerBlocks(blocks.filter((b) => !b.deferred), { label: twFile });
296272
+ if (values && Object.keys(values).length) {
296273
+ result = compileSync3(source, { filePath: twFile, stateVars: { ...resolved.params, ...values } });
296274
+ }
296275
+ }
296276
+ } catch (e) {
296277
+ console.log(" -> 500 Error (server block): " + (e?.message ?? e));
296278
+ if (devOpts?.overlay === false) {
296279
+ return new Response(
296280
+ "500 - Server Error\n\n" + (e?.message ?? String(e)),
296281
+ { status: 500, headers: { "Content-Type": "text/plain; charset=utf-8" } }
296282
+ );
296283
+ }
296284
+ return new Response(devErrorPage({
296285
+ title: "Server Error",
296286
+ message: e?.message ?? String(e),
296287
+ stack: e?.stack,
296288
+ file: twFile
296289
+ }), { status: 500, headers: { "Content-Type": "text/html; charset=utf-8" } });
296290
+ }
295689
296291
  let html = result.html;
295690
296292
  const pageDir = twFile.substring(0, twFile.lastIndexOf("/"));
295691
296293
  const chain2 = [];
@@ -296511,7 +297113,7 @@ function ensureTestingShim(rootDir) {
296511
297113
  if (existsSync32(marker)) return;
296512
297114
  const { mkdirSync: mkdirSync12, writeFileSync: writeFileSync14 } = _require("node:fs");
296513
297115
  mkdirSync12(join37(shimPkgDir, "tw"), { recursive: true });
296514
- writeFileSync14(marker, JSON.stringify({ name: "@tw/server", version: "2.0.0", private: true }, null, 2));
297116
+ writeFileSync14(marker, JSON.stringify({ name: "@tw/server", version: "2.0.1", private: true }, null, 2));
296515
297117
  let testingBundle = null;
296516
297118
  try {
296517
297119
  const here = new URL(".", import.meta.url).pathname;
@@ -297408,7 +298010,7 @@ import { join as join42 } from "node:path";
297408
298010
  function cliVersion() {
297409
298011
  try {
297410
298012
  const bd = globalThis.__TW_BUNDLE_DIR;
297411
- const pj = bd ? join42(bd, "..", "package.json") : new URL("../../package.json", import.meta.url).pathname;
298013
+ const pj = bd ? join42(bd, "..", "package.json") : new URL("../package.json", import.meta.url).pathname;
297412
298014
  return JSON.parse(readFileSync26(pj, "utf-8")).version ?? "0.0.0";
297413
298015
  } catch {
297414
298016
  return "0.0.0";
@@ -297517,7 +298119,20 @@ async function run() {
297517
298119
  console.log(` ${colors.bold("tw")} ${colors.cyan(`v${VERSION2}`)}`);
297518
298120
  return;
297519
298121
  }
297520
- const handler = commands[parsed.command];
298122
+ let handler = commands[parsed.command];
298123
+ if (!handler) {
298124
+ try {
298125
+ const { loadPlugins: loadPlugins2 } = await Promise.resolve().then(() => (init_tw8(), tw_exports4));
298126
+ const pm = await loadPlugins2(process.cwd());
298127
+ const cmd = pm?.getCommands?.()[parsed.command];
298128
+ if (typeof cmd === "function") {
298129
+ handler = async () => {
298130
+ await cmd(process.argv.slice(3));
298131
+ };
298132
+ }
298133
+ } catch {
298134
+ }
298135
+ }
297521
298136
  if (!handler) {
297522
298137
  console.error(` ${colors.red("?")} Unknown command: ${parsed.command}`);
297523
298138
  console.log("\n" + helpText);