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/CHANGELOG.md +41 -0
- package/dist/testing.cjs +702 -147
- package/dist/tw-runtime-client.mjs +74 -38
- package/dist/tw-server.mjs +684 -129
- package/dist/tw.mjs +754 -139
- package/package.json +1 -1
package/dist/testing.cjs
CHANGED
|
@@ -6906,20 +6906,39 @@ function sha12(data) {
|
|
|
6906
6906
|
async function md52(data) {
|
|
6907
6907
|
return hashWithWebCrypto("md5", data);
|
|
6908
6908
|
}
|
|
6909
|
+
function webcryptoHashName(algorithm) {
|
|
6910
|
+
const key2 = String(algorithm).toLowerCase().replace(/[-_]/g, "");
|
|
6911
|
+
const map2 = {
|
|
6912
|
+
md5: "MD5",
|
|
6913
|
+
sha1: "SHA-1",
|
|
6914
|
+
sha224: "SHA-224",
|
|
6915
|
+
sha256: "SHA-256",
|
|
6916
|
+
sha384: "SHA-384",
|
|
6917
|
+
sha512: "SHA-512"
|
|
6918
|
+
};
|
|
6919
|
+
return map2[key2] ?? algorithm;
|
|
6920
|
+
}
|
|
6909
6921
|
async function hmac2(data, key2, algorithm = "sha256") {
|
|
6910
6922
|
const encoder2 = new TextEncoder();
|
|
6911
6923
|
const keyData = encoder2.encode(key2);
|
|
6912
6924
|
const messageData = encoder2.encode(data);
|
|
6913
|
-
const cryptoKey = await crypto.subtle.importKey("raw", keyData, { name: "HMAC", hash: algorithm }, false, ["sign"]);
|
|
6925
|
+
const cryptoKey = await crypto.subtle.importKey("raw", keyData, { name: "HMAC", hash: webcryptoHashName(algorithm) }, false, ["sign"]);
|
|
6914
6926
|
const signature = await crypto.subtle.sign("HMAC", cryptoKey, messageData);
|
|
6915
6927
|
const hashArray = Array.from(new Uint8Array(signature));
|
|
6916
6928
|
return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
6917
6929
|
}
|
|
6930
|
+
async function verifyHmac(data, key2, signature, algorithm = "sha256") {
|
|
6931
|
+
const expected = await hmac2(data, key2, algorithm);
|
|
6932
|
+
if (expected.length !== signature.length) return false;
|
|
6933
|
+
let diff2 = 0;
|
|
6934
|
+
for (let i = 0; i < expected.length; i++) diff2 |= expected.charCodeAt(i) ^ signature.charCodeAt(i);
|
|
6935
|
+
return diff2 === 0;
|
|
6936
|
+
}
|
|
6918
6937
|
async function pbkdf2(password, salt, iterations = 1e5, keyLength = 256) {
|
|
6919
6938
|
const encoder2 = new TextEncoder();
|
|
6920
6939
|
const keyMaterial = await crypto.subtle.importKey("raw", encoder2.encode(password), "PBKDF2", false, ["deriveBits"]);
|
|
6921
6940
|
const derivedBits = await crypto.subtle.deriveBits(
|
|
6922
|
-
{ name: "PBKDF2", salt: encoder2.encode(salt), iterations, hash: "
|
|
6941
|
+
{ name: "PBKDF2", salt: encoder2.encode(salt), iterations, hash: "SHA-256" },
|
|
6923
6942
|
keyMaterial,
|
|
6924
6943
|
keyLength
|
|
6925
6944
|
);
|
|
@@ -6964,9 +6983,16 @@ async function decryptString(encryptedData, password, salt) {
|
|
|
6964
6983
|
const key2 = await deriveKey(password, salt);
|
|
6965
6984
|
return decryptAES(encrypted, key2, iv);
|
|
6966
6985
|
}
|
|
6986
|
+
async function generateRSASigningKeyPair(bits = 2048) {
|
|
6987
|
+
return crypto.subtle.generateKey(
|
|
6988
|
+
{ name: "RSASSA-PKCS1-v1_5", modulusLength: bits, publicExponent: new Uint8Array([1, 0, 1]), hash: "SHA-256" },
|
|
6989
|
+
true,
|
|
6990
|
+
["sign", "verify"]
|
|
6991
|
+
);
|
|
6992
|
+
}
|
|
6967
6993
|
async function generateRSAKeyPair(bits = 2048) {
|
|
6968
6994
|
return crypto.subtle.generateKey(
|
|
6969
|
-
{ name: "RSA-OAEP", modulusLength: bits, publicExponent: new Uint8Array([1, 0, 1]), hash: "
|
|
6995
|
+
{ name: "RSA-OAEP", modulusLength: bits, publicExponent: new Uint8Array([1, 0, 1]), hash: "SHA-256" },
|
|
6970
6996
|
true,
|
|
6971
6997
|
["encrypt", "decrypt"]
|
|
6972
6998
|
);
|
|
@@ -8415,36 +8441,6 @@ var init_etag = __esm({
|
|
|
8415
8441
|
});
|
|
8416
8442
|
|
|
8417
8443
|
// ../../packages/shared/tw/hashing/hmac.ts
|
|
8418
|
-
function bufferToHex2(buffer) {
|
|
8419
|
-
const bytes = new Uint8Array(buffer);
|
|
8420
|
-
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
8421
|
-
}
|
|
8422
|
-
function timingSafeEqual2(a, b) {
|
|
8423
|
-
if (a.length !== b.length) return false;
|
|
8424
|
-
let result = 0;
|
|
8425
|
-
for (let i = 0; i < a.length; i++) {
|
|
8426
|
-
result |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
8427
|
-
}
|
|
8428
|
-
return result === 0;
|
|
8429
|
-
}
|
|
8430
|
-
async function hmac3(key2, message, algorithm = "SHA-256") {
|
|
8431
|
-
const encoder2 = new TextEncoder();
|
|
8432
|
-
const keyData = encoder2.encode(key2);
|
|
8433
|
-
const messageData = encoder2.encode(message);
|
|
8434
|
-
const cryptoKey = await crypto.subtle.importKey(
|
|
8435
|
-
"raw",
|
|
8436
|
-
keyData,
|
|
8437
|
-
{ name: "HMAC", hash: algorithm },
|
|
8438
|
-
false,
|
|
8439
|
-
["sign"]
|
|
8440
|
-
);
|
|
8441
|
-
const signature = await crypto.subtle.sign("HMAC", cryptoKey, messageData);
|
|
8442
|
-
return bufferToHex2(signature);
|
|
8443
|
-
}
|
|
8444
|
-
async function verifyHmac(key2, message, signature, algorithm = "SHA-256") {
|
|
8445
|
-
const computed6 = await hmac3(key2, message, algorithm);
|
|
8446
|
-
return timingSafeEqual2(computed6, signature);
|
|
8447
|
-
}
|
|
8448
8444
|
var init_hmac = __esm({
|
|
8449
8445
|
"../../packages/shared/tw/hashing/hmac.ts"() {
|
|
8450
8446
|
}
|
|
@@ -9109,6 +9105,7 @@ __export(tw_exports, {
|
|
|
9109
9105
|
generatePIN: () => generatePIN,
|
|
9110
9106
|
generateProofOfKnowledge: () => generateProofOfKnowledge,
|
|
9111
9107
|
generateRSAKeyPair: () => generateRSAKeyPair,
|
|
9108
|
+
generateRSASigningKeyPair: () => generateRSASigningKeyPair,
|
|
9112
9109
|
generateSecret: () => generateSecret,
|
|
9113
9110
|
generateTOTP: () => generateTOTP,
|
|
9114
9111
|
geolocation: () => geolocation,
|
|
@@ -9639,23 +9636,19 @@ function merkleRoot(hashes) {
|
|
|
9639
9636
|
return level[0];
|
|
9640
9637
|
}
|
|
9641
9638
|
function hmacSign(data, key2) {
|
|
9642
|
-
|
|
9643
|
-
for (let i = 0; i < data.length; i++) {
|
|
9644
|
-
h1 = (h1 << 5) - h1 + data.charCodeAt(i);
|
|
9645
|
-
h1 |= 0;
|
|
9646
|
-
}
|
|
9647
|
-
for (let i = 0; i < key2.length; i++) {
|
|
9648
|
-
h22 = (h22 << 5) - h22 + key2.charCodeAt(i);
|
|
9649
|
-
h22 |= 0;
|
|
9650
|
-
}
|
|
9651
|
-
return (h1 ^ h22).toString(16);
|
|
9639
|
+
return (0, import_node_crypto3.createHmac)("sha256", key2).update(data).digest("hex");
|
|
9652
9640
|
}
|
|
9653
9641
|
function hmacVerify(data, key2, signature) {
|
|
9654
|
-
|
|
9642
|
+
const expected = hmacSign(data, key2);
|
|
9643
|
+
if (expected.length !== signature.length) return false;
|
|
9644
|
+
let diff2 = 0;
|
|
9645
|
+
for (let i = 0; i < expected.length; i++) diff2 |= expected.charCodeAt(i) ^ signature.charCodeAt(i);
|
|
9646
|
+
return diff2 === 0;
|
|
9655
9647
|
}
|
|
9656
|
-
var HTML_TAGS;
|
|
9648
|
+
var import_node_crypto3, HTML_TAGS;
|
|
9657
9649
|
var init_tw = __esm({
|
|
9658
9650
|
"../../packages/shared/tw/index.ts"() {
|
|
9651
|
+
import_node_crypto3 = require("node:crypto");
|
|
9659
9652
|
init_sha();
|
|
9660
9653
|
init_utils();
|
|
9661
9654
|
init_manipulate();
|
|
@@ -227101,6 +227094,10 @@ function parseTWSelector(cursor, errors, state, parseBody) {
|
|
|
227101
227094
|
while (!cursor.done) {
|
|
227102
227095
|
const ct = cursor.peek();
|
|
227103
227096
|
if (!ct) break;
|
|
227097
|
+
if (ct.type === "IDENT" && ct.value === "on") {
|
|
227098
|
+
const after2 = cursor.peek(1);
|
|
227099
|
+
if (after2 && after2.type === "COLON") break;
|
|
227100
|
+
}
|
|
227104
227101
|
if (ct.type === "IDENT" && !nextPrevMinus && nextClass.length > 0 && (CLASS_BOUNDARY_BOOLEAN_ATTRS.has(ct.value) || inlineAttrStartsAt(cursor))) {
|
|
227105
227102
|
break;
|
|
227106
227103
|
}
|
|
@@ -227391,7 +227388,9 @@ function parseTWSelector(cursor, errors, state, parseBody) {
|
|
|
227391
227388
|
const first = cursor.peek();
|
|
227392
227389
|
const second = cursor.peek(1);
|
|
227393
227390
|
const CONTROL_FLOW = /* @__PURE__ */ new Set(["for", "if", "while", "switch", "else", "try", "catch"]);
|
|
227394
|
-
const
|
|
227391
|
+
const third = cursor.peek(2);
|
|
227392
|
+
const isPropBlock = second && second.type === "ASSIGN" && third && third.type === "LBRACE";
|
|
227393
|
+
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");
|
|
227395
227394
|
if (isAttrBlock) {
|
|
227396
227395
|
while (!cursor.done) {
|
|
227397
227396
|
const t3 = cursor.peek();
|
|
@@ -227399,6 +227398,34 @@ function parseTWSelector(cursor, errors, state, parseBody) {
|
|
|
227399
227398
|
if (t3.type === "IDENT" || t3.type === "KEYWORD") {
|
|
227400
227399
|
const attrName = t3.value;
|
|
227401
227400
|
cursor.advance();
|
|
227401
|
+
if (cursor.peek() && (cursor.peek().type === "ASSIGN" || cursor.peek().type === "EQUALS")) {
|
|
227402
|
+
cursor.advance();
|
|
227403
|
+
cursor.skipWhitespace();
|
|
227404
|
+
if (cursor.peek() && cursor.peek().type === "LBRACE") {
|
|
227405
|
+
cursor.advance();
|
|
227406
|
+
let expr = "";
|
|
227407
|
+
let depth = 1;
|
|
227408
|
+
let prevWS = false;
|
|
227409
|
+
while (!cursor.done) {
|
|
227410
|
+
const tk = cursor.peek();
|
|
227411
|
+
if (!tk || tk.type === "EOF") break;
|
|
227412
|
+
if (tk.type === "LBRACE") depth++;
|
|
227413
|
+
else if (tk.type === "RBRACE") {
|
|
227414
|
+
depth--;
|
|
227415
|
+
if (depth === 0) {
|
|
227416
|
+
cursor.advance();
|
|
227417
|
+
break;
|
|
227418
|
+
}
|
|
227419
|
+
}
|
|
227420
|
+
if (expr && prevWS) expr += " ";
|
|
227421
|
+
expr += tk.raw ?? tk.value;
|
|
227422
|
+
prevWS = !!tk.followedByWhitespace;
|
|
227423
|
+
cursor.advance();
|
|
227424
|
+
}
|
|
227425
|
+
el.attrs.push(createAttribute(attrName, expr.trim(), t3.pos.line, t3.pos.col, true));
|
|
227426
|
+
continue;
|
|
227427
|
+
}
|
|
227428
|
+
}
|
|
227402
227429
|
const vToken = cursor.peek();
|
|
227403
227430
|
if (vToken && (vToken.type === "STRING" || vToken.type === "NUMBER")) {
|
|
227404
227431
|
cursor.advance();
|
|
@@ -236144,7 +236171,7 @@ var init_types4 = __esm({
|
|
|
236144
236171
|
var TW_VERSION, TW_GENERATOR_META;
|
|
236145
236172
|
var init_version = __esm({
|
|
236146
236173
|
"../../packages/compiler/tw/codegen/version.ts"() {
|
|
236147
|
-
TW_VERSION = "2.0.
|
|
236174
|
+
TW_VERSION = "2.0.1";
|
|
236148
236175
|
TW_GENERATOR_META = `<meta name="generator" content="TW Framework ${TW_VERSION}">`;
|
|
236149
236176
|
}
|
|
236150
236177
|
});
|
|
@@ -236157,6 +236184,9 @@ function optimizedImageUrl(src, opts = {}) {
|
|
|
236157
236184
|
if (opts.quality) p.set("q", String(opts.quality));
|
|
236158
236185
|
p.set("f", opts.format ?? "auto");
|
|
236159
236186
|
const qs = p.toString();
|
|
236187
|
+
if (/^https?:\/\//i.test(src)) {
|
|
236188
|
+
return `/_tw/img/_remote?src=${encodeURIComponent(src)}&${qs}`;
|
|
236189
|
+
}
|
|
236160
236190
|
return `/_tw/img${src.startsWith("/") ? src : "/" + src}?${qs}`;
|
|
236161
236191
|
}
|
|
236162
236192
|
function imageAttributesFor(opts, config = DEFAULT_IMAGE_CONFIG) {
|
|
@@ -236164,7 +236194,7 @@ function imageAttributesFor(opts, config = DEFAULT_IMAGE_CONFIG) {
|
|
|
236164
236194
|
const quality = opts.quality ?? config.quality;
|
|
236165
236195
|
const format = config.formats[0] ?? "webp";
|
|
236166
236196
|
const isRemote = /^https?:\/\//i.test(opts.src);
|
|
236167
|
-
const src = isRemote ? `/_tw/img/_remote?src=${encodeURIComponent(opts.src)}
|
|
236197
|
+
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 });
|
|
236168
236198
|
const widths = config.breakpoints.filter((bp) => width === 0 || bp <= width * 2);
|
|
236169
236199
|
const srcset = widths.map(
|
|
236170
236200
|
(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`
|
|
@@ -236317,7 +236347,7 @@ function createImageHandler(options) {
|
|
|
236317
236347
|
return { data: (0, import_node_fs5.readFileSync)(file), mtime: st.mtimeMs };
|
|
236318
236348
|
}
|
|
236319
236349
|
function cacheKey(src, w, h3, q, f, mtime) {
|
|
236320
|
-
return (0,
|
|
236350
|
+
return (0, import_node_crypto4.createHash)("sha256").update(`${src}|${mtime}|${w}|${h3}|${q}|${f}`).digest("hex").slice(0, 32);
|
|
236321
236351
|
}
|
|
236322
236352
|
async function processBuffer(data, w, h3, q, format) {
|
|
236323
236353
|
const sharp = await loadSharp();
|
|
@@ -236344,7 +236374,7 @@ function createImageHandler(options) {
|
|
|
236344
236374
|
}
|
|
236345
236375
|
if (!warnedNoSharp) {
|
|
236346
236376
|
warnedNoSharp = true;
|
|
236347
|
-
console.warn(" [tw/image] sharp not installed \u2014
|
|
236377
|
+
console.warn(" [tw/image] sharp not installed \u2014 using the built-in converter (WebP/AVIF); install sharp for faster resizing");
|
|
236348
236378
|
}
|
|
236349
236379
|
return { data, contentType: "", viaSharp: false };
|
|
236350
236380
|
}
|
|
@@ -236428,10 +236458,10 @@ function createImageHandler(options) {
|
|
|
236428
236458
|
}
|
|
236429
236459
|
return { handle, sourceDir, cacheDir, config };
|
|
236430
236460
|
}
|
|
236431
|
-
var
|
|
236461
|
+
var import_node_crypto4, import_node_fs5, import_node_path11, import_node_url, DEFAULT_IMAGE_CONFIG, IMAGE_EXTENSIONS, SHARP_FORMATS;
|
|
236432
236462
|
var init_tw2 = __esm({
|
|
236433
236463
|
"../../packages/image/tw/index.ts"() {
|
|
236434
|
-
|
|
236464
|
+
import_node_crypto4 = require("node:crypto");
|
|
236435
236465
|
import_node_fs5 = require("node:fs");
|
|
236436
236466
|
import_node_path11 = require("node:path");
|
|
236437
236467
|
import_node_url = require("node:url");
|
|
@@ -236463,7 +236493,11 @@ var init_tw2 = __esm({
|
|
|
236463
236493
|
});
|
|
236464
236494
|
|
|
236465
236495
|
// ../../packages/link/tw/index.ts
|
|
236496
|
+
function isDangerousHref(href) {
|
|
236497
|
+
return DANGEROUS_SCHEME.test(href.replace(/[\u0000-\u0020]/g, ""));
|
|
236498
|
+
}
|
|
236466
236499
|
function isExternalHref(href) {
|
|
236500
|
+
if (isDangerousHref(href)) return false;
|
|
236467
236501
|
return /^[a-z][a-z0-9+.-]*:/i.test(href) || // any scheme: http(s), mailto, tel, ...
|
|
236468
236502
|
href.startsWith("//");
|
|
236469
236503
|
}
|
|
@@ -236472,7 +236506,8 @@ function isWebExternal(href) {
|
|
|
236472
236506
|
return true;
|
|
236473
236507
|
}
|
|
236474
236508
|
function linkAttributesFor(opts) {
|
|
236475
|
-
const
|
|
236509
|
+
const rawHref = (opts.href ?? "").trim();
|
|
236510
|
+
const href = isDangerousHref(rawHref) ? "#" : rawHref;
|
|
236476
236511
|
const external = opts.external === true || isExternalHref(href);
|
|
236477
236512
|
const attrs = {};
|
|
236478
236513
|
if (external) {
|
|
@@ -236501,8 +236536,10 @@ function linkAttributesFor(opts) {
|
|
|
236501
236536
|
if (opts.id) attrs["id"] = opts.id;
|
|
236502
236537
|
return { href, external, attrs };
|
|
236503
236538
|
}
|
|
236539
|
+
var DANGEROUS_SCHEME;
|
|
236504
236540
|
var init_tw3 = __esm({
|
|
236505
236541
|
"../../packages/link/tw/index.ts"() {
|
|
236542
|
+
DANGEROUS_SCHEME = /^(javascript|data|vbscript):/i;
|
|
236506
236543
|
}
|
|
236507
236544
|
});
|
|
236508
236545
|
|
|
@@ -237015,12 +237052,31 @@ var init_interpolate = __esm({
|
|
|
237015
237052
|
});
|
|
237016
237053
|
|
|
237017
237054
|
// ../../packages/compiler/tw/eval/evaluate.ts
|
|
237055
|
+
function setEvalBindings(bindings) {
|
|
237056
|
+
extraBindings = bindings ?? {};
|
|
237057
|
+
}
|
|
237018
237058
|
function evaluate(expr, vars, sandbox) {
|
|
237019
237059
|
if (!expr) return "";
|
|
237020
237060
|
const sb = sandbox ?? defaultSandbox;
|
|
237021
237061
|
if (vars[expr] !== void 0) {
|
|
237022
237062
|
return vars[expr];
|
|
237023
237063
|
}
|
|
237064
|
+
if (expr.startsWith("(") && expr.endsWith(")")) {
|
|
237065
|
+
let d = 0;
|
|
237066
|
+
let wraps = true;
|
|
237067
|
+
for (let i = 0; i < expr.length; i++) {
|
|
237068
|
+
const ch = expr[i];
|
|
237069
|
+
if (ch === "(") d++;
|
|
237070
|
+
else if (ch === ")") {
|
|
237071
|
+
d--;
|
|
237072
|
+
if (d === 0 && i < expr.length - 1) {
|
|
237073
|
+
wraps = false;
|
|
237074
|
+
break;
|
|
237075
|
+
}
|
|
237076
|
+
}
|
|
237077
|
+
}
|
|
237078
|
+
if (wraps && d === 0) return evaluate(expr.slice(1, -1).trim(), vars, sb);
|
|
237079
|
+
}
|
|
237024
237080
|
if (/^-?\d+(\.\d+)?$/.test(expr)) return expr;
|
|
237025
237081
|
if (expr.startsWith('"') && expr.endsWith('"') || expr.startsWith("'") && expr.endsWith("'")) {
|
|
237026
237082
|
return expr.slice(1, -1);
|
|
@@ -237028,11 +237084,54 @@ function evaluate(expr, vars, sandbox) {
|
|
|
237028
237084
|
if (expr === "true") return "true";
|
|
237029
237085
|
if (expr === "false") return "false";
|
|
237030
237086
|
if (expr === "null" || expr === "undefined") return "";
|
|
237031
|
-
|
|
237032
|
-
|
|
237033
|
-
|
|
237034
|
-
|
|
237087
|
+
{
|
|
237088
|
+
let depth = 0;
|
|
237089
|
+
let pending2 = 0;
|
|
237090
|
+
let q = -1;
|
|
237091
|
+
let quote = "";
|
|
237092
|
+
for (let i = 0; i < expr.length; i++) {
|
|
237093
|
+
const ch = expr[i];
|
|
237094
|
+
if (quote) {
|
|
237095
|
+
if (ch === "\\") {
|
|
237096
|
+
i++;
|
|
237097
|
+
continue;
|
|
237098
|
+
}
|
|
237099
|
+
if (ch === quote) quote = "";
|
|
237100
|
+
continue;
|
|
237101
|
+
}
|
|
237102
|
+
if (ch === "'" || ch === '"' || ch === "`") {
|
|
237103
|
+
quote = ch;
|
|
237104
|
+
continue;
|
|
237105
|
+
}
|
|
237106
|
+
if (ch === "(" || ch === "[" || ch === "{") {
|
|
237107
|
+
depth++;
|
|
237108
|
+
continue;
|
|
237109
|
+
}
|
|
237110
|
+
if (ch === ")" || ch === "]" || ch === "}") {
|
|
237111
|
+
depth--;
|
|
237112
|
+
continue;
|
|
237113
|
+
}
|
|
237114
|
+
if (depth !== 0) continue;
|
|
237115
|
+
if (ch === "?") {
|
|
237116
|
+
if (pending2 === 0) q = i;
|
|
237117
|
+
pending2++;
|
|
237118
|
+
continue;
|
|
237119
|
+
}
|
|
237120
|
+
if (ch === ":" && pending2 > 0) {
|
|
237121
|
+
pending2--;
|
|
237122
|
+
if (pending2 > 0) continue;
|
|
237123
|
+
const cond = expr.slice(0, q).trim();
|
|
237124
|
+
const cons = expr.slice(q + 1, i).trim();
|
|
237125
|
+
const alt = expr.slice(i + 1).trim();
|
|
237126
|
+
if (cond && cons && alt) {
|
|
237127
|
+
return isTruthy(resolve2(cond, vars, sb)) ? resolve2(cons, vars, sb) : resolve2(alt, vars, sb);
|
|
237128
|
+
}
|
|
237129
|
+
break;
|
|
237130
|
+
}
|
|
237131
|
+
}
|
|
237035
237132
|
}
|
|
237133
|
+
const dyn = evalWithVars(expr, vars);
|
|
237134
|
+
if (dyn !== null) return dyn;
|
|
237036
237135
|
const orParts = splitLogical(expr, "||");
|
|
237037
237136
|
if (orParts.length > 1) {
|
|
237038
237137
|
for (const part of orParts) {
|
|
@@ -237109,7 +237208,7 @@ function evaluate(expr, vars, sandbox) {
|
|
|
237109
237208
|
return current2 !== void 0 && current2 !== null ? String(current2) : "";
|
|
237110
237209
|
}
|
|
237111
237210
|
const fnMatch = expr.match(/^(\w+)\((.*)\)$/);
|
|
237112
|
-
if (fnMatch) {
|
|
237211
|
+
if (fnMatch && isBuiltinName(fnMatch[1])) {
|
|
237113
237212
|
const fn = fnMatch[1];
|
|
237114
237213
|
const arg = resolve2(fnMatch[2].trim(), vars, sb);
|
|
237115
237214
|
return callBuiltin(fn, arg, sb);
|
|
@@ -237128,6 +237227,38 @@ function evaluate(expr, vars, sandbox) {
|
|
|
237128
237227
|
}
|
|
237129
237228
|
return expr;
|
|
237130
237229
|
}
|
|
237230
|
+
function evalWithVars(expr, vars) {
|
|
237231
|
+
if (!expr || /[`\\]/.test(expr)) return null;
|
|
237232
|
+
if (!/^[\w\s.$[\]{}()'"`,+\-*/%?:<>=!&|]*$/.test(expr)) return null;
|
|
237233
|
+
for (const m of expr.matchAll(/\.([A-Za-z_$][\w$]*)\s*\(/g)) {
|
|
237234
|
+
if (!SAFE_MEMBERS.has(m[1])) return null;
|
|
237235
|
+
}
|
|
237236
|
+
const names = [];
|
|
237237
|
+
const vals = [];
|
|
237238
|
+
for (const k of Object.keys(vars)) {
|
|
237239
|
+
if (!/^[A-Za-z_$][\w$]*$/.test(k)) continue;
|
|
237240
|
+
if (k in extraBindings) continue;
|
|
237241
|
+
names.push(k);
|
|
237242
|
+
try {
|
|
237243
|
+
vals.push(JSON.parse(vars[k]));
|
|
237244
|
+
} catch {
|
|
237245
|
+
vals.push(vars[k]);
|
|
237246
|
+
}
|
|
237247
|
+
}
|
|
237248
|
+
for (const k of Object.keys(extraBindings)) {
|
|
237249
|
+
if (!/^[A-Za-z_$][\w$]*$/.test(k)) continue;
|
|
237250
|
+
names.push(k);
|
|
237251
|
+
vals.push(extraBindings[k]);
|
|
237252
|
+
}
|
|
237253
|
+
try {
|
|
237254
|
+
const fn = new Function(...names, `"use strict"; return (${expr});`);
|
|
237255
|
+
const out = fn(...vals);
|
|
237256
|
+
if (out === void 0 || out === null) return null;
|
|
237257
|
+
return typeof out === "object" ? JSON.stringify(out) : String(out);
|
|
237258
|
+
} catch {
|
|
237259
|
+
return null;
|
|
237260
|
+
}
|
|
237261
|
+
}
|
|
237131
237262
|
function resolve2(token, vars, sandbox) {
|
|
237132
237263
|
const trimmed = token.trim();
|
|
237133
237264
|
if (!trimmed) return "";
|
|
@@ -237296,7 +237427,7 @@ function coerce(value, targetType) {
|
|
|
237296
237427
|
return value;
|
|
237297
237428
|
}
|
|
237298
237429
|
}
|
|
237299
|
-
var defaultSandbox;
|
|
237430
|
+
var defaultSandbox, extraBindings, SAFE_MEMBERS;
|
|
237300
237431
|
var init_evaluate = __esm({
|
|
237301
237432
|
"../../packages/compiler/tw/eval/evaluate.ts"() {
|
|
237302
237433
|
init_sandbox();
|
|
@@ -237304,6 +237435,64 @@ var init_evaluate = __esm({
|
|
|
237304
237435
|
init_builtins();
|
|
237305
237436
|
init_interpolate();
|
|
237306
237437
|
defaultSandbox = new Sandbox();
|
|
237438
|
+
extraBindings = {};
|
|
237439
|
+
SAFE_MEMBERS = /* @__PURE__ */ new Set([
|
|
237440
|
+
"length",
|
|
237441
|
+
"toUpperCase",
|
|
237442
|
+
"toLowerCase",
|
|
237443
|
+
"trim",
|
|
237444
|
+
"trimStart",
|
|
237445
|
+
"trimEnd",
|
|
237446
|
+
"slice",
|
|
237447
|
+
"substring",
|
|
237448
|
+
"substr",
|
|
237449
|
+
"charAt",
|
|
237450
|
+
"at",
|
|
237451
|
+
"includes",
|
|
237452
|
+
"indexOf",
|
|
237453
|
+
"lastIndexOf",
|
|
237454
|
+
"startsWith",
|
|
237455
|
+
"endsWith",
|
|
237456
|
+
"replace",
|
|
237457
|
+
"replaceAll",
|
|
237458
|
+
"split",
|
|
237459
|
+
"join",
|
|
237460
|
+
"concat",
|
|
237461
|
+
"repeat",
|
|
237462
|
+
"padStart",
|
|
237463
|
+
"padEnd",
|
|
237464
|
+
"toString",
|
|
237465
|
+
"toFixed",
|
|
237466
|
+
"map",
|
|
237467
|
+
"filter",
|
|
237468
|
+
"find",
|
|
237469
|
+
"some",
|
|
237470
|
+
"every",
|
|
237471
|
+
"reduce",
|
|
237472
|
+
"sort",
|
|
237473
|
+
"reverse",
|
|
237474
|
+
// Static helpers on the built-ins (JSON.stringify, Object.keys, Math.round).
|
|
237475
|
+
"stringify",
|
|
237476
|
+
"parse",
|
|
237477
|
+
"keys",
|
|
237478
|
+
"values",
|
|
237479
|
+
"entries",
|
|
237480
|
+
"from",
|
|
237481
|
+
"isArray",
|
|
237482
|
+
"isInteger",
|
|
237483
|
+
"isFinite",
|
|
237484
|
+
"abs",
|
|
237485
|
+
"ceil",
|
|
237486
|
+
"floor",
|
|
237487
|
+
"round",
|
|
237488
|
+
"max",
|
|
237489
|
+
"min",
|
|
237490
|
+
"pow",
|
|
237491
|
+
"sqrt",
|
|
237492
|
+
"random",
|
|
237493
|
+
"now",
|
|
237494
|
+
"toISOString"
|
|
237495
|
+
]);
|
|
237307
237496
|
}
|
|
237308
237497
|
});
|
|
237309
237498
|
|
|
@@ -237458,9 +237647,78 @@ function callBuiltin(fn, arg, sandbox) {
|
|
|
237458
237647
|
function applyFilter(value, filter, args, vars, sandbox) {
|
|
237459
237648
|
return callBuiltin(filter, value, sandbox);
|
|
237460
237649
|
}
|
|
237650
|
+
function isBuiltinName(fn) {
|
|
237651
|
+
return BUILTIN_NAMES.has(fn);
|
|
237652
|
+
}
|
|
237653
|
+
var BUILTIN_NAMES;
|
|
237461
237654
|
var init_builtins = __esm({
|
|
237462
237655
|
"../../packages/compiler/tw/eval/builtins.ts"() {
|
|
237463
237656
|
init_evaluate();
|
|
237657
|
+
BUILTIN_NAMES = /* @__PURE__ */ new Set([
|
|
237658
|
+
"Boolean",
|
|
237659
|
+
"Number",
|
|
237660
|
+
"String",
|
|
237661
|
+
"abs",
|
|
237662
|
+
"base64decode",
|
|
237663
|
+
"base64encode",
|
|
237664
|
+
"bool",
|
|
237665
|
+
"camelCase",
|
|
237666
|
+
"capitalize",
|
|
237667
|
+
"ceil",
|
|
237668
|
+
"cos",
|
|
237669
|
+
"date",
|
|
237670
|
+
"decodeURI",
|
|
237671
|
+
"encodeURI",
|
|
237672
|
+
"endsWith",
|
|
237673
|
+
"exp",
|
|
237674
|
+
"first",
|
|
237675
|
+
"float",
|
|
237676
|
+
"floor",
|
|
237677
|
+
"includes",
|
|
237678
|
+
"indexOf",
|
|
237679
|
+
"int",
|
|
237680
|
+
"join",
|
|
237681
|
+
"json",
|
|
237682
|
+
"kebabCase",
|
|
237683
|
+
"last",
|
|
237684
|
+
"len",
|
|
237685
|
+
"length",
|
|
237686
|
+
"log",
|
|
237687
|
+
"lower",
|
|
237688
|
+
"lowercase",
|
|
237689
|
+
"match",
|
|
237690
|
+
"max",
|
|
237691
|
+
"min",
|
|
237692
|
+
"now",
|
|
237693
|
+
"num",
|
|
237694
|
+
"padEnd",
|
|
237695
|
+
"padStart",
|
|
237696
|
+
"parseFloat",
|
|
237697
|
+
"parseInt",
|
|
237698
|
+
"parseJSON",
|
|
237699
|
+
"pow",
|
|
237700
|
+
"random",
|
|
237701
|
+
"repeat",
|
|
237702
|
+
"replace",
|
|
237703
|
+
"reverse",
|
|
237704
|
+
"round",
|
|
237705
|
+
"sin",
|
|
237706
|
+
"size",
|
|
237707
|
+
"slice",
|
|
237708
|
+
"snakeCase",
|
|
237709
|
+
"split",
|
|
237710
|
+
"sqrt",
|
|
237711
|
+
"startsWith",
|
|
237712
|
+
"str",
|
|
237713
|
+
"stringify",
|
|
237714
|
+
"tan",
|
|
237715
|
+
"test",
|
|
237716
|
+
"time",
|
|
237717
|
+
"title",
|
|
237718
|
+
"trim",
|
|
237719
|
+
"upper",
|
|
237720
|
+
"uppercase"
|
|
237721
|
+
]);
|
|
237464
237722
|
}
|
|
237465
237723
|
});
|
|
237466
237724
|
|
|
@@ -238507,16 +238765,61 @@ function addScope(css, scopeId) {
|
|
|
238507
238765
|
out += prelude;
|
|
238508
238766
|
return out;
|
|
238509
238767
|
}
|
|
238510
|
-
function
|
|
238511
|
-
|
|
238512
|
-
|
|
238513
|
-
|
|
238768
|
+
function interpolateBraces(input, vars) {
|
|
238769
|
+
let out = "";
|
|
238770
|
+
let i = 0;
|
|
238771
|
+
while (i < input.length) {
|
|
238772
|
+
const ch = input[i];
|
|
238773
|
+
if (ch !== "{") {
|
|
238774
|
+
out += ch;
|
|
238775
|
+
i++;
|
|
238776
|
+
continue;
|
|
238777
|
+
}
|
|
238778
|
+
let depth = 1;
|
|
238779
|
+
let j = i + 1;
|
|
238780
|
+
let quote = "";
|
|
238781
|
+
while (j < input.length && depth > 0) {
|
|
238782
|
+
const c = input[j];
|
|
238783
|
+
if (quote) {
|
|
238784
|
+
if (c === "\\") {
|
|
238785
|
+
j += 2;
|
|
238786
|
+
continue;
|
|
238787
|
+
}
|
|
238788
|
+
if (c === quote) quote = "";
|
|
238789
|
+
j++;
|
|
238790
|
+
continue;
|
|
238791
|
+
}
|
|
238792
|
+
if (c === "'" || c === '"' || c === "`") {
|
|
238793
|
+
quote = c;
|
|
238794
|
+
j++;
|
|
238795
|
+
continue;
|
|
238796
|
+
}
|
|
238797
|
+
if (c === "{") depth++;
|
|
238798
|
+
else if (c === "}") {
|
|
238799
|
+
depth--;
|
|
238800
|
+
if (depth === 0) break;
|
|
238801
|
+
}
|
|
238802
|
+
j++;
|
|
238803
|
+
}
|
|
238804
|
+
if (depth !== 0) {
|
|
238805
|
+
out += ch;
|
|
238806
|
+
i++;
|
|
238807
|
+
continue;
|
|
238808
|
+
}
|
|
238809
|
+
const key2 = input.slice(i + 1, j).trim();
|
|
238514
238810
|
if (/^[a-zA-Z_$][\w$]*$/.test(key2)) {
|
|
238515
|
-
|
|
238811
|
+
out += vars[key2] ?? "";
|
|
238812
|
+
} else {
|
|
238813
|
+
const val = evaluate(key2, vars);
|
|
238814
|
+
out += val === null ? "" : val;
|
|
238516
238815
|
}
|
|
238517
|
-
|
|
238518
|
-
|
|
238519
|
-
|
|
238816
|
+
i = j + 1;
|
|
238817
|
+
}
|
|
238818
|
+
return out;
|
|
238819
|
+
}
|
|
238820
|
+
function interpolate2(expr, vars) {
|
|
238821
|
+
const masked = expr.replace(/\\\{/g, "O").replace(/\\\}/g, "C");
|
|
238822
|
+
const out = interpolateBraces(masked, vars);
|
|
238520
238823
|
return out.replace(/\u0001O/g, "{").replace(/\u0001C/g, "}");
|
|
238521
238824
|
}
|
|
238522
238825
|
function isTruthy2(val) {
|
|
@@ -238995,7 +239298,21 @@ function renderBindings(bindings, ctx) {
|
|
|
238995
239298
|
if (bindings.length === 0) return "";
|
|
238996
239299
|
const parts = [];
|
|
238997
239300
|
for (const binding of bindings) {
|
|
238998
|
-
|
|
239301
|
+
const expr = String(binding.expression ?? "");
|
|
239302
|
+
let initial = "";
|
|
239303
|
+
try {
|
|
239304
|
+
if (/^[A-Za-z_$][\w$]*$/.test(expr)) {
|
|
239305
|
+
const v = ctx.stateVars[expr];
|
|
239306
|
+
if (v !== void 0 && v !== null) initial = String(v);
|
|
239307
|
+
} else if (expr) {
|
|
239308
|
+
initial = interpolate2("{" + expr + "}", ctx.stateVars);
|
|
239309
|
+
}
|
|
239310
|
+
} catch {
|
|
239311
|
+
}
|
|
239312
|
+
if (initial && !/[{}]/.test(initial)) {
|
|
239313
|
+
parts.push(`${binding.property}="${escapeAttr3(initial)}"`);
|
|
239314
|
+
}
|
|
239315
|
+
parts.push(`data-tw-bind-${binding.property}="${escapeAttr3(expr)}"`);
|
|
238999
239316
|
}
|
|
239000
239317
|
return parts.join(" ");
|
|
239001
239318
|
}
|
|
@@ -239110,8 +239427,13 @@ function generateComponent(comp, ctx) {
|
|
|
239110
239427
|
const savedStateVars = { ...ctx.stateVars };
|
|
239111
239428
|
for (const prop of comp.props) {
|
|
239112
239429
|
if (typeof prop.value === "string") {
|
|
239113
|
-
const
|
|
239114
|
-
|
|
239430
|
+
const raw = String(prop.value);
|
|
239431
|
+
const head = raw.split(/[.[]/)[0];
|
|
239432
|
+
if (!/[{}]/.test(raw) && Object.prototype.hasOwnProperty.call(ctx.stateVars, head)) {
|
|
239433
|
+
ctx.stateVars[prop.name] = interpolate2("{" + raw + "}", ctx.stateVars);
|
|
239434
|
+
} else {
|
|
239435
|
+
ctx.stateVars[prop.name] = interpolate2(raw, ctx.stateVars);
|
|
239436
|
+
}
|
|
239115
239437
|
}
|
|
239116
239438
|
}
|
|
239117
239439
|
const componentProgram = componentRegistry.get(comp.name);
|
|
@@ -239205,6 +239527,14 @@ function generateFor(node, ctx) {
|
|
|
239205
239527
|
}
|
|
239206
239528
|
}
|
|
239207
239529
|
}
|
|
239530
|
+
if (items.length === 0 && !/^\s*\[/.test(iterableStr)) {
|
|
239531
|
+
try {
|
|
239532
|
+
const evaluated = evaluate(String(node.iterable ?? "").replace(/^\{/, "").replace(/\}$/, "").trim(), ctx.stateVars);
|
|
239533
|
+
const parsed = JSON.parse(evaluated);
|
|
239534
|
+
if (Array.isArray(parsed)) items = parsed;
|
|
239535
|
+
} catch {
|
|
239536
|
+
}
|
|
239537
|
+
}
|
|
239208
239538
|
for (let i = 0; i < items.length; i++) {
|
|
239209
239539
|
const childCtx = { ...ctx };
|
|
239210
239540
|
childCtx.stateVars = { ...ctx.stateVars };
|
|
@@ -243013,16 +243343,22 @@ function generateWithLayoutChain(layoutPrograms, pageProgram, stateVars) {
|
|
|
243013
243343
|
}
|
|
243014
243344
|
};
|
|
243015
243345
|
let pageTitle = "TW Page";
|
|
243346
|
+
let pageDescription = "";
|
|
243016
243347
|
for (const dir of pageProgram.directives || []) {
|
|
243017
|
-
if (dir.type === "PageDirective"
|
|
243018
|
-
|
|
243019
|
-
|
|
243348
|
+
if (dir.type === "PageDirective") {
|
|
243349
|
+
if (dir.key === "title") {
|
|
243350
|
+
const t3 = dir.value;
|
|
243351
|
+
if (t3 != null && String(t3) !== "") pageTitle = String(t3);
|
|
243352
|
+
} else if (dir.key === "description") {
|
|
243353
|
+
const d = dir.value;
|
|
243354
|
+
if (d != null) pageDescription = String(d);
|
|
243355
|
+
}
|
|
243020
243356
|
}
|
|
243021
243357
|
}
|
|
243022
243358
|
const pageCtx = createContext("ssr");
|
|
243023
243359
|
seedCtx(pageCtx);
|
|
243024
243360
|
try {
|
|
243025
|
-
pageCtx.stateVars["page"] = JSON.stringify({ title: pageTitle });
|
|
243361
|
+
pageCtx.stateVars["page"] = JSON.stringify({ title: pageTitle, description: pageDescription });
|
|
243026
243362
|
} catch {
|
|
243027
243363
|
}
|
|
243028
243364
|
{
|
|
@@ -243178,6 +243514,10 @@ function generate(program, stateVars, genOpts2) {
|
|
|
243178
243514
|
const startTime = performance.now();
|
|
243179
243515
|
const ctx = createContext("ssr");
|
|
243180
243516
|
if (stateVars) ctx.stateVars = stateVars;
|
|
243517
|
+
if (genOpts2?.imports) {
|
|
243518
|
+
ctx.imports = genOpts2.imports;
|
|
243519
|
+
setEvalBindings(genOpts2.imports);
|
|
243520
|
+
} else setEvalBindings({});
|
|
243181
243521
|
if (genOpts2 && genOpts2.scopedStyles === false) ctx.scopedStyles = false;
|
|
243182
243522
|
if (genOpts2?.scopeId) ctx.scopeId = genOpts2.scopeId;
|
|
243183
243523
|
if (genOpts2?.cssPrefix) ctx.cssPrefix = genOpts2.cssPrefix;
|
|
@@ -243262,6 +243602,7 @@ var init_vdom = __esm({
|
|
|
243262
243602
|
init_types4();
|
|
243263
243603
|
init_html2();
|
|
243264
243604
|
init_builtin_components();
|
|
243605
|
+
init_evaluate();
|
|
243265
243606
|
}
|
|
243266
243607
|
});
|
|
243267
243608
|
|
|
@@ -243603,6 +243944,7 @@ var init_codes = __esm({
|
|
|
243603
243944
|
TW095: { message: "Invalid for-loop iterable -- use {varName} or a valid comma-separated literal", severity: "error", category: "syntax" },
|
|
243604
243945
|
TW096: { message: "Unknown state variable in interpolation (renders empty)", severity: "warning", category: "semantic" },
|
|
243605
243946
|
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" },
|
|
243947
|
+
TW098: { message: "Interpolation cannot be evaluated at build time -- it renders as its source text", severity: "warning", category: "semantic" },
|
|
243606
243948
|
// Server blocks (TW099-TW101)
|
|
243607
243949
|
TW099: { message: "Unknown server modifier (only ':defer' is supported)", severity: "error", category: "syntax" },
|
|
243608
243950
|
TW100: { message: "Expected '{' after 'server'", severity: "error", category: "syntax" },
|
|
@@ -243690,6 +244032,7 @@ function checkUnknownTags(program, filePath, diags) {
|
|
|
243690
244032
|
forEachNode(program, (node, ctx) => {
|
|
243691
244033
|
if (!isElement(node)) return;
|
|
243692
244034
|
if (node.tag[0] === node.tag[0]?.toUpperCase()) return;
|
|
244035
|
+
if (BUILTIN_COMPONENT_TAGS.has(node.tag)) return;
|
|
243693
244036
|
const parent = ctx?.parent;
|
|
243694
244037
|
if (parent && (parent.type === "Element" && typeof parent.tag === "string" && parent.tag[0] === parent.tag[0]?.toUpperCase() || parent.type === "Component")) return;
|
|
243695
244038
|
if (HTML_ELEMENTS2.has(node.tag.toLowerCase())) return;
|
|
@@ -244044,6 +244387,59 @@ function checkUnknownStateVars(program, filePath, diags) {
|
|
|
244044
244387
|
"null",
|
|
244045
244388
|
"undefined"
|
|
244046
244389
|
]);
|
|
244390
|
+
const EVALUABLE_METHODS = /* @__PURE__ */ new Set([
|
|
244391
|
+
"length",
|
|
244392
|
+
"toUpperCase",
|
|
244393
|
+
"toLowerCase",
|
|
244394
|
+
"trim",
|
|
244395
|
+
"trimStart",
|
|
244396
|
+
"trimEnd",
|
|
244397
|
+
"slice",
|
|
244398
|
+
"substring",
|
|
244399
|
+
"substr",
|
|
244400
|
+
"charAt",
|
|
244401
|
+
"at",
|
|
244402
|
+
"includes",
|
|
244403
|
+
"indexOf",
|
|
244404
|
+
"lastIndexOf",
|
|
244405
|
+
"startsWith",
|
|
244406
|
+
"endsWith",
|
|
244407
|
+
"replace",
|
|
244408
|
+
"replaceAll",
|
|
244409
|
+
"split",
|
|
244410
|
+
"join",
|
|
244411
|
+
"concat",
|
|
244412
|
+
"repeat",
|
|
244413
|
+
"padStart",
|
|
244414
|
+
"padEnd",
|
|
244415
|
+
"toString",
|
|
244416
|
+
"toFixed",
|
|
244417
|
+
"map",
|
|
244418
|
+
"filter",
|
|
244419
|
+
"find",
|
|
244420
|
+
"some",
|
|
244421
|
+
"every",
|
|
244422
|
+
"reduce",
|
|
244423
|
+
"sort",
|
|
244424
|
+
"reverse",
|
|
244425
|
+
"stringify",
|
|
244426
|
+
"parse",
|
|
244427
|
+
"keys",
|
|
244428
|
+
"values",
|
|
244429
|
+
"entries",
|
|
244430
|
+
"from",
|
|
244431
|
+
"isArray",
|
|
244432
|
+
"round",
|
|
244433
|
+
"floor",
|
|
244434
|
+
"ceil",
|
|
244435
|
+
"abs",
|
|
244436
|
+
"max",
|
|
244437
|
+
"min",
|
|
244438
|
+
"pow",
|
|
244439
|
+
"sqrt",
|
|
244440
|
+
"random",
|
|
244441
|
+
"now"
|
|
244442
|
+
]);
|
|
244047
244443
|
for (const node of texts) {
|
|
244048
244444
|
const text = String(node.value ?? "");
|
|
244049
244445
|
if (!text.includes("{")) continue;
|
|
@@ -244059,6 +244455,22 @@ function checkUnknownStateVars(program, filePath, diags) {
|
|
|
244059
244455
|
[`Declare it in the state block, or check the spelling`]
|
|
244060
244456
|
));
|
|
244061
244457
|
}
|
|
244458
|
+
for (const m of text.matchAll(/\{([^{}]+)\}/g)) {
|
|
244459
|
+
const expr = m[1].trim();
|
|
244460
|
+
if (/^[A-Za-z_$][\w$]*$/.test(expr)) continue;
|
|
244461
|
+
for (const c of expr.matchAll(/([A-Za-z_$][\w$]*)\s*\(/g)) {
|
|
244462
|
+
const fn = c[1];
|
|
244463
|
+
if (known.has(fn) || BUILTINS2.has(fn) || EVALUABLE_METHODS.has(fn)) continue;
|
|
244464
|
+
diags.push(createDiagnostic(
|
|
244465
|
+
"TW098",
|
|
244466
|
+
node.line,
|
|
244467
|
+
node.col,
|
|
244468
|
+
filePath,
|
|
244469
|
+
`{${expr}}`,
|
|
244470
|
+
[`Cannot evaluate this at build time -- it renders as its source. Check the name, or simplify the expression`]
|
|
244471
|
+
));
|
|
244472
|
+
}
|
|
244473
|
+
}
|
|
244062
244474
|
}
|
|
244063
244475
|
}
|
|
244064
244476
|
function checkWhileComparisons(program, filePath, diags) {
|
|
@@ -244091,7 +244503,7 @@ function checkDeprecatedImports(program, filePath, diags) {
|
|
|
244091
244503
|
}
|
|
244092
244504
|
}
|
|
244093
244505
|
}
|
|
244094
|
-
var HTML_ELEMENTS2, HTML_ATTRIBUTES2, GLOBAL_ATTRIBUTES2, CSS_PROPERTIES2, CSS_ALIASES2, EVENT_TYPES2, ARIA_ROLES2, ARIA_ATTRIBUTES2, DEPRECATED_IMPORTS;
|
|
244506
|
+
var HTML_ELEMENTS2, HTML_ATTRIBUTES2, GLOBAL_ATTRIBUTES2, CSS_PROPERTIES2, CSS_ALIASES2, EVENT_TYPES2, ARIA_ROLES2, ARIA_ATTRIBUTES2, BUILTIN_COMPONENT_TAGS, DEPRECATED_IMPORTS;
|
|
244095
244507
|
var init_rules = __esm({
|
|
244096
244508
|
"../../packages/compiler/tw/diagnostics/rules.ts"() {
|
|
244097
244509
|
init_visitors();
|
|
@@ -244955,6 +245367,7 @@ var init_rules = __esm({
|
|
|
244955
245367
|
"aria-valuenow",
|
|
244956
245368
|
"aria-valuetext"
|
|
244957
245369
|
]);
|
|
245370
|
+
BUILTIN_COMPONENT_TAGS = /* @__PURE__ */ new Set(["optImage", "twImage", "twLink", "twImg"]);
|
|
244958
245371
|
DEPRECATED_IMPORTS = [
|
|
244959
245372
|
{ source: "utils/string/transform", name: "toHex", replacement: "stringToHex", removal: "2.1.0" },
|
|
244960
245373
|
{ source: "utils/string/transform", name: "fromHex", replacement: "hexToString", removal: "2.1.0" },
|
|
@@ -255268,7 +255681,8 @@ function genOpts(opts, scopeId) {
|
|
|
255268
255681
|
scopedStyles: opts?.compiler?.scopedStyles,
|
|
255269
255682
|
scopeId,
|
|
255270
255683
|
cssPrefix: opts?.cssPrefix,
|
|
255271
|
-
cssImportPaths: opts?.cssImportPaths
|
|
255684
|
+
cssImportPaths: opts?.cssImportPaths,
|
|
255685
|
+
imports: opts?.imports
|
|
255272
255686
|
};
|
|
255273
255687
|
}
|
|
255274
255688
|
function scopeIdFor(filePath) {
|
|
@@ -256007,14 +256421,14 @@ function verifyJwt_hs256(token, secret) {
|
|
|
256007
256421
|
return false;
|
|
256008
256422
|
}
|
|
256009
256423
|
if (header?.alg !== "HS256") return false;
|
|
256010
|
-
const expected = (0,
|
|
256424
|
+
const expected = (0, import_node_crypto5.createHmac)("sha256", secret).update(`${h3}.${p}`).digest();
|
|
256011
256425
|
let got;
|
|
256012
256426
|
try {
|
|
256013
256427
|
got = b64urlDecode(sig);
|
|
256014
256428
|
} catch {
|
|
256015
256429
|
return false;
|
|
256016
256430
|
}
|
|
256017
|
-
if (expected.length !== got.length || !(0,
|
|
256431
|
+
if (expected.length !== got.length || !(0, import_node_crypto5.timingSafeEqual)(expected, got)) return false;
|
|
256018
256432
|
if (typeof payload?.exp === "number" && payload.exp * 1e3 < Date.now()) return false;
|
|
256019
256433
|
return true;
|
|
256020
256434
|
}
|
|
@@ -256185,11 +256599,11 @@ function evaluateRules(rules, request) {
|
|
|
256185
256599
|
}
|
|
256186
256600
|
return null;
|
|
256187
256601
|
}
|
|
256188
|
-
var
|
|
256602
|
+
var import_node_crypto5, Parser, rateBuckets, RATE_BUCKETS_MAX;
|
|
256189
256603
|
var init_twm_rules = __esm({
|
|
256190
256604
|
"../../packages/server/tw/routing/twm-rules.ts"() {
|
|
256191
256605
|
init_tw();
|
|
256192
|
-
|
|
256606
|
+
import_node_crypto5 = require("node:crypto");
|
|
256193
256607
|
Parser = class {
|
|
256194
256608
|
constructor(toks, file) {
|
|
256195
256609
|
this.toks = toks;
|
|
@@ -256700,6 +257114,18 @@ function findGlobalError(rootNode2) {
|
|
|
256700
257114
|
function findRootError(rootNode2) {
|
|
256701
257115
|
return (rootNode2.files || []).find((f) => f.type === "error");
|
|
256702
257116
|
}
|
|
257117
|
+
function findNearestNotFound(rootNode2, pathParts) {
|
|
257118
|
+
let found = (rootNode2.files || []).find((f) => f.type === "not-found");
|
|
257119
|
+
let node = rootNode2;
|
|
257120
|
+
for (const part of pathParts) {
|
|
257121
|
+
const child = (node.children || []).find((c) => c.segment === part);
|
|
257122
|
+
if (!child) break;
|
|
257123
|
+
const f = (child.files || []).find((x) => x.type === "not-found");
|
|
257124
|
+
if (f) found = f;
|
|
257125
|
+
node = child;
|
|
257126
|
+
}
|
|
257127
|
+
return found;
|
|
257128
|
+
}
|
|
256703
257129
|
function findRootNotFound(rootNode2) {
|
|
256704
257130
|
return rootNode2.files.find((f) => f.type === "not-found");
|
|
256705
257131
|
}
|
|
@@ -257502,6 +257928,26 @@ var init_render_pipeline = __esm({
|
|
|
257502
257928
|
};
|
|
257503
257929
|
}
|
|
257504
257930
|
console.error(`[tw] server block failed for ${pageFile}: ${err2?.message ?? err2}`);
|
|
257931
|
+
try {
|
|
257932
|
+
const errFile = ctx.error ?? findRootError(tree);
|
|
257933
|
+
if (errFile) {
|
|
257934
|
+
const compiled = this.compileFile(errFile);
|
|
257935
|
+
let eh = String(compiled.html || "");
|
|
257936
|
+
const em = eh.match(/<body[^>]*>([\s\S]*)<\/body>/i);
|
|
257937
|
+
eh = (em ? em[1] : eh).replace(/<\/?html[^>]*>/gi, "").replace(/<\/?body[^>]*>/gi, "");
|
|
257938
|
+
return {
|
|
257939
|
+
html: this.assembleHTML(eh, compiled.css, "", "Error"),
|
|
257940
|
+
css: "",
|
|
257941
|
+
js: "",
|
|
257942
|
+
status: 500,
|
|
257943
|
+
headers: { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "private, no-store" },
|
|
257944
|
+
fromCache: false,
|
|
257945
|
+
durationMs: 0,
|
|
257946
|
+
serverBlocks: true
|
|
257947
|
+
};
|
|
257948
|
+
}
|
|
257949
|
+
} catch {
|
|
257950
|
+
}
|
|
257505
257951
|
return {
|
|
257506
257952
|
html: "<!DOCTYPE html><html><head><title>Server Error</title></head><body><h1>Internal Server Error</h1></body></html>",
|
|
257507
257953
|
css: "",
|
|
@@ -257567,7 +258013,7 @@ var init_render_pipeline = __esm({
|
|
|
257567
258013
|
}
|
|
257568
258014
|
const match = matchRoute(tree, pathname);
|
|
257569
258015
|
if (!match) {
|
|
257570
|
-
const rootNotFound =
|
|
258016
|
+
const rootNotFound = findNearestNotFound(tree, pathname.split("/").filter(Boolean));
|
|
257571
258017
|
if (rootNotFound) {
|
|
257572
258018
|
const compiled = this.compileFile(rootNotFound);
|
|
257573
258019
|
let nf = String(compiled.html || "");
|
|
@@ -257633,6 +258079,7 @@ var init_render_pipeline = __esm({
|
|
|
257633
258079
|
}
|
|
257634
258080
|
let bodyHTML = pageCompiled.html;
|
|
257635
258081
|
let allCSS = pageCompiled.css;
|
|
258082
|
+
const parallelSlots = [];
|
|
257636
258083
|
try {
|
|
257637
258084
|
const parentNode = match?.node;
|
|
257638
258085
|
if (parentNode && Array.isArray(parentNode.children)) {
|
|
@@ -257643,9 +258090,7 @@ var init_render_pipeline = __esm({
|
|
|
257643
258090
|
const sbm = slotBody.match(/<body[^>]*>([\s\S]*)<\/body>/i);
|
|
257644
258091
|
if (sbm) slotBody = sbm[1];
|
|
257645
258092
|
slotBody = slotBody.replace(/<\/?html[^>]*>/gi, "").replace(/<\/?body[^>]*>/gi, "");
|
|
257646
|
-
|
|
257647
|
-
const slotRe = new RegExp(`<${comp}(s[^>]*)?>([sS]*?)</${comp}>|<${comp}(s[^>]*)?/>`, "gi");
|
|
257648
|
-
bodyHTML = bodyHTML.replace(slotRe, slotBody);
|
|
258093
|
+
parallelSlots.push({ comp: slotName.charAt(0).toUpperCase() + slotName.slice(1), body: slotBody });
|
|
257649
258094
|
if (slotCompiled.css) allCSS = slotCompiled.css + "\n" + allCSS;
|
|
257650
258095
|
}
|
|
257651
258096
|
}
|
|
@@ -257664,6 +258109,11 @@ var init_render_pipeline = __esm({
|
|
|
257664
258109
|
allJS = templateCompiled.js + "\n" + allJS;
|
|
257665
258110
|
bodyHTML = this.wrapInLayout(templateCompiled.html, bodyHTML);
|
|
257666
258111
|
}
|
|
258112
|
+
for (const { comp, body } of parallelSlots) {
|
|
258113
|
+
const slotRe = new RegExp(`<${comp}(\\s[^>]*)?>([\\s\\S]*?)<\\/${comp}>|<${comp}(\\s[^>]*)?/>`, "gi");
|
|
258114
|
+
bodyHTML = bodyHTML.replace(slotRe, body);
|
|
258115
|
+
bodyHTML = bodyHTML.replace(new RegExp(`<!--\\s*TW: unknown component '${comp}'[\\s\\S]*?-->`, "gi"), body);
|
|
258116
|
+
}
|
|
257667
258117
|
let layoutTitle;
|
|
257668
258118
|
const liftedParts = [];
|
|
257669
258119
|
{
|
|
@@ -258082,8 +258532,6 @@ ${bodyHTML}`;
|
|
|
258082
258532
|
};
|
|
258083
258533
|
let tags = "";
|
|
258084
258534
|
const description = pick2("description");
|
|
258085
|
-
if (description) tags += ` <meta name="description" content="${esc4(description)}">
|
|
258086
|
-
`;
|
|
258087
258535
|
const keywords = pick2("keywords");
|
|
258088
258536
|
if (keywords) tags += ` <meta name="keywords" content="${esc4(keywords)}">
|
|
258089
258537
|
`;
|
|
@@ -260265,6 +260713,7 @@ var init_static = __esm({
|
|
|
260265
260713
|
if (!(0, import_node_fs13.existsSync)(filePath)) {
|
|
260266
260714
|
filePath = (0, import_node_path20.join)(this.root, safePath + ".html");
|
|
260267
260715
|
}
|
|
260716
|
+
let notFoundFallback = false;
|
|
260268
260717
|
if (!(0, import_node_fs13.existsSync)(filePath)) {
|
|
260269
260718
|
filePath = (0, import_node_path20.join)(this.root, safePath, this.indexFile);
|
|
260270
260719
|
}
|
|
@@ -260276,6 +260725,7 @@ var init_static = __esm({
|
|
|
260276
260725
|
}
|
|
260277
260726
|
} else {
|
|
260278
260727
|
filePath = (0, import_node_path20.join)(this.root, "404.html");
|
|
260728
|
+
notFoundFallback = true;
|
|
260279
260729
|
if (!(0, import_node_fs13.existsSync)(filePath)) {
|
|
260280
260730
|
return new Response("Not Found", { status: 404, headers: { "Content-Type": "text/plain" } });
|
|
260281
260731
|
}
|
|
@@ -260343,12 +260793,12 @@ var init_static = __esm({
|
|
|
260343
260793
|
const gzHeaders = { ...headers, "Content-Encoding": "gzip", "Content-Length": String(gz.length), Vary: "Accept-Encoding" };
|
|
260344
260794
|
delete gzHeaders["Content-Length"];
|
|
260345
260795
|
if (this.lastModified) delete gzHeaders["Last-Modified"];
|
|
260346
|
-
return new Response(gz, { headers: gzHeaders });
|
|
260796
|
+
return new Response(gz, { headers: gzHeaders, status: notFoundFallback ? 404 : 200 });
|
|
260347
260797
|
}
|
|
260348
260798
|
} catch {
|
|
260349
260799
|
}
|
|
260350
260800
|
}
|
|
260351
|
-
return new Response(bytes, { headers });
|
|
260801
|
+
return new Response(bytes, { headers, status: notFoundFallback ? 404 : 200 });
|
|
260352
260802
|
}
|
|
260353
260803
|
/**
|
|
260354
260804
|
* Parse a `Range: bytes=...` header against a known content length.
|
|
@@ -260401,10 +260851,10 @@ var init_static = __esm({
|
|
|
260401
260851
|
});
|
|
260402
260852
|
|
|
260403
260853
|
// ../../packages/security/tw/policy.ts
|
|
260404
|
-
var
|
|
260854
|
+
var import_node_crypto6, CSPBuilder, XSSSanitizer, RateLimiter, CSRFProtection, InputValidator;
|
|
260405
260855
|
var init_policy = __esm({
|
|
260406
260856
|
"../../packages/security/tw/policy.ts"() {
|
|
260407
|
-
|
|
260857
|
+
import_node_crypto6 = require("node:crypto");
|
|
260408
260858
|
CSPBuilder = class _CSPBuilder {
|
|
260409
260859
|
directives = /* @__PURE__ */ new Map();
|
|
260410
260860
|
nonces = /* @__PURE__ */ new Set();
|
|
@@ -260422,7 +260872,7 @@ var init_policy = __esm({
|
|
|
260422
260872
|
return this;
|
|
260423
260873
|
}
|
|
260424
260874
|
generateNonce() {
|
|
260425
|
-
const nonce = (0,
|
|
260875
|
+
const nonce = (0, import_node_crypto6.randomBytes)(16).toString("base64");
|
|
260426
260876
|
this.nonces.add(nonce);
|
|
260427
260877
|
return nonce;
|
|
260428
260878
|
}
|
|
@@ -260671,7 +261121,7 @@ var init_policy = __esm({
|
|
|
260671
261121
|
* Generate a Subresource Integrity hash for a script/style.
|
|
260672
261122
|
*/
|
|
260673
261123
|
generateSRI(content, alg = "sha384") {
|
|
260674
|
-
const hash2 = (0,
|
|
261124
|
+
const hash2 = (0, import_node_crypto6.createHash)(alg).update(content).digest("base64");
|
|
260675
261125
|
return `${alg}-${hash2}`;
|
|
260676
261126
|
}
|
|
260677
261127
|
/**
|
|
@@ -260752,10 +261202,10 @@ var init_policy = __esm({
|
|
|
260752
261202
|
tokens = /* @__PURE__ */ new Map();
|
|
260753
261203
|
secret;
|
|
260754
261204
|
constructor(secret) {
|
|
260755
|
-
this.secret = secret ?? (0,
|
|
261205
|
+
this.secret = secret ?? (0, import_node_crypto6.randomBytes)(32).toString("hex");
|
|
260756
261206
|
}
|
|
260757
261207
|
generateToken(sessionId) {
|
|
260758
|
-
const token = (0,
|
|
261208
|
+
const token = (0, import_node_crypto6.randomBytes)(32).toString("hex");
|
|
260759
261209
|
const expires = Date.now() + 36e5;
|
|
260760
261210
|
this.tokens.set(sessionId, { value: token, expires });
|
|
260761
261211
|
return token;
|
|
@@ -260998,6 +261448,12 @@ var init_xss_detector = __esm({
|
|
|
260998
261448
|
{ name: "SQL comment", regex: /--\s/g, severity: "medium", recommendation: "Check for SQL comment injection" },
|
|
260999
261449
|
{ name: "Stacked queries", regex: /;\s*(SELECT|INSERT|UPDATE|DELETE|DROP|UNION)/gi, severity: "high", recommendation: "Prevent stacked queries" },
|
|
261000
261450
|
{ name: "UNION injection", regex: /UNION\s+SELECT/gi, severity: "critical", recommendation: "Filter UNION SELECT from user input" },
|
|
261451
|
+
// Comment-only injections (`admin'--`, `admin'#`, `admin'/*`): a quote
|
|
261452
|
+
// followed by a comment truncates the query and bypasses the login
|
|
261453
|
+
// check, with no OR/UNION for the other patterns to catch.
|
|
261454
|
+
{ name: "Comment-terminated injection", regex: /['"`]\s*(--|#|\/\*)/g, severity: "high", recommendation: "A quote followed by a comment truncates the query -- use parameterized queries" },
|
|
261455
|
+
// Parenthesised boolean injection: `') OR ('1'='1`.
|
|
261456
|
+
{ 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" },
|
|
261001
261457
|
{ name: "Boolean-based injection", regex: /OR\s+1\s*=\s*1/gi, severity: "high", recommendation: "Use parameterized queries to prevent boolean injection" },
|
|
261002
261458
|
{ name: "Time-based injection", regex: /WAITFOR\s+DELAY/gi, severity: "high", recommendation: "Filter WAITFOR DELAY from user input" },
|
|
261003
261459
|
{ name: "DROP statement", regex: /DROP\s+(TABLE|DATABASE)/gi, severity: "critical", recommendation: "Prevent DROP statements in queries" },
|
|
@@ -261528,6 +261984,7 @@ var init_logger3 = __esm({
|
|
|
261528
261984
|
}
|
|
261529
261985
|
/** Records an audit event. */
|
|
261530
261986
|
log(event) {
|
|
261987
|
+
event = { type: "system", actor: "unknown", resource: "unknown", ...event };
|
|
261531
261988
|
const fullEvent = {
|
|
261532
261989
|
...event,
|
|
261533
261990
|
id: crypto.randomUUID(),
|
|
@@ -262544,6 +263001,13 @@ var FILE_SIGNATURES, DANGEROUS_EXTENSIONS, DANGEROUS_MIME_TYPES, ContentSecurity
|
|
|
262544
263001
|
var init_protector = __esm({
|
|
262545
263002
|
"../../packages/security/tw/content-security/protector.ts"() {
|
|
262546
263003
|
FILE_SIGNATURES = [
|
|
263004
|
+
// Executables and HTML: without these, content renamed to an image extension
|
|
263005
|
+
// had no detected type, the signature check was skipped, and the upload was
|
|
263006
|
+
// accepted as risk "low" -- the exact rename bypass the check exists for.
|
|
263007
|
+
{ ext: ["exe", "dll", "sys"], mime: "application/x-msdownload", signature: [77, 90] },
|
|
263008
|
+
{ ext: ["elf", "so", "bin"], mime: "application/x-executable", signature: [127, 69, 76, 70] },
|
|
263009
|
+
{ ext: ["html", "htm", "xhtml"], mime: "text/html", signature: [60, 104, 116, 109, 108] },
|
|
263010
|
+
{ ext: ["html", "htm"], mime: "text/html", signature: [60, 33, 68, 79, 67, 84, 89, 80, 69] },
|
|
262547
263011
|
{ ext: ["jpg", "jpeg"], mime: "image/jpeg", signature: [255, 216, 255] },
|
|
262548
263012
|
{ ext: ["png"], mime: "image/png", signature: [137, 80, 78, 71, 13, 10, 26, 10] },
|
|
262549
263013
|
{ ext: ["gif"], mime: "image/gif", signature: [71, 73, 70, 56] },
|
|
@@ -262595,10 +263059,39 @@ var init_protector = __esm({
|
|
|
262595
263059
|
".htpasswd",
|
|
262596
263060
|
".svg",
|
|
262597
263061
|
// Can contain JavaScript
|
|
262598
|
-
".xml"
|
|
263062
|
+
".xml",
|
|
262599
263063
|
// Can contain XXE
|
|
263064
|
+
// Active-content types the set used to let through while still blocking .svg
|
|
263065
|
+
// "because it can contain JavaScript" -- .html/.js are script-capable by
|
|
263066
|
+
// definition, so an accepted upload served from our own origin is stored XSS.
|
|
263067
|
+
".html",
|
|
263068
|
+
".htm",
|
|
263069
|
+
".xhtml",
|
|
263070
|
+
".shtml",
|
|
263071
|
+
".js",
|
|
263072
|
+
".mjs",
|
|
263073
|
+
".cjs",
|
|
263074
|
+
".jsx",
|
|
263075
|
+
".ts",
|
|
263076
|
+
".tsx",
|
|
263077
|
+
".mhtml",
|
|
263078
|
+
".hta",
|
|
263079
|
+
".jsp",
|
|
263080
|
+
".asp",
|
|
263081
|
+
".aspx",
|
|
263082
|
+
".cgi",
|
|
263083
|
+
".pl"
|
|
262600
263084
|
]);
|
|
262601
263085
|
DANGEROUS_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
263086
|
+
// Active-content types: they can carry script (svg, xhtml, xml) or be
|
|
263087
|
+
// rendered as a document (html, pdf). Blocking .svg by extension while
|
|
263088
|
+
// reporting isContentTypeSafe("image/svg+xml") === true was inconsistent.
|
|
263089
|
+
"image/svg+xml",
|
|
263090
|
+
"text/html",
|
|
263091
|
+
"application/xhtml+xml",
|
|
263092
|
+
"application/pdf",
|
|
263093
|
+
"text/xml",
|
|
263094
|
+
"application/xml",
|
|
262602
263095
|
"application/javascript",
|
|
262603
263096
|
"text/javascript",
|
|
262604
263097
|
"application/x-javascript",
|
|
@@ -262653,7 +263146,9 @@ var init_protector = __esm({
|
|
|
262653
263146
|
reason: "Empty file",
|
|
262654
263147
|
sanitizedFilename: sanitized,
|
|
262655
263148
|
detectedType: "unknown",
|
|
262656
|
-
risk
|
|
263149
|
+
// Unknown content is not low risk -- it is content whose type we could
|
|
263150
|
+
// not establish, which is what a rename bypass looks like.
|
|
263151
|
+
risk: "medium"
|
|
262657
263152
|
};
|
|
262658
263153
|
}
|
|
262659
263154
|
if (this.blockedExtensions.has(ext)) {
|
|
@@ -263816,6 +264311,7 @@ var init_nonce_manager = __esm({
|
|
|
263816
264311
|
validate(nonce) {
|
|
263817
264312
|
const entry = this.nonces.get(nonce);
|
|
263818
264313
|
if (!entry) return false;
|
|
264314
|
+
if (entry.used) return false;
|
|
263819
264315
|
if (Date.now() > entry.expiresAt) {
|
|
263820
264316
|
this.nonces.delete(nonce);
|
|
263821
264317
|
return false;
|
|
@@ -264608,7 +265104,15 @@ var init_token_manager = __esm({
|
|
|
264608
265104
|
rotateAfterUse;
|
|
264609
265105
|
maxTokens;
|
|
264610
265106
|
tokens = /* @__PURE__ */ new Map();
|
|
265107
|
+
/** Tokens this manager issued and already consumed. A rotated token is
|
|
265108
|
+
* deleted from `tokens`, so without this marker a replay found no entry and
|
|
265109
|
+
* fell through to the stateless branch, which accepted it -- defeating the
|
|
265110
|
+
* documented single-use guarantee of `rotateAfterUse`. */
|
|
265111
|
+
consumedTokens = /* @__PURE__ */ new Set();
|
|
264611
265112
|
constructor(options) {
|
|
265113
|
+
if (!options || typeof options.secret !== "string" || options.secret.length === 0) {
|
|
265114
|
+
throw new Error("createCSRFManager: options.secret is required (the HMAC key used to sign tokens)");
|
|
265115
|
+
}
|
|
264612
265116
|
this.secret = options.secret;
|
|
264613
265117
|
this.tokenLength = options.tokenLength ?? 32;
|
|
264614
265118
|
this.ttl = options.ttl ?? 36e5;
|
|
@@ -264708,12 +265212,16 @@ var init_token_manager = __esm({
|
|
|
264708
265212
|
}
|
|
264709
265213
|
entry.used = true;
|
|
264710
265214
|
if (this.rotateAfterUse) {
|
|
265215
|
+
this.consumedTokens.add(headerPart);
|
|
264711
265216
|
this.tokens.delete(headerPart);
|
|
264712
265217
|
const rotated = await this.generate(sessionId ?? void 0);
|
|
264713
265218
|
return { valid: true, rotated };
|
|
264714
265219
|
}
|
|
264715
265220
|
return { valid: true };
|
|
264716
265221
|
}
|
|
265222
|
+
if (this.consumedTokens.has(headerPart)) {
|
|
265223
|
+
return { valid: false, reason: "Token already used" };
|
|
265224
|
+
}
|
|
264717
265225
|
const expectedSig = await hmacSign3(
|
|
264718
265226
|
this.secret,
|
|
264719
265227
|
`${headerPart}.${sessionId ?? ""}`
|
|
@@ -269205,14 +269713,14 @@ function getCachedSSR(pathname) {
|
|
|
269205
269713
|
const diskPath = (0, import_node_path21.join)(_diskCacheDir, pathname.replace(/[\/]/g, "_") + ".html");
|
|
269206
269714
|
if ((0, import_node_fs15.existsSync)(diskPath)) {
|
|
269207
269715
|
const content = (0, import_node_fs14.readFileSync)(diskPath, "utf-8");
|
|
269208
|
-
const etag = (0,
|
|
269716
|
+
const etag = (0, import_node_crypto7.createHash)("sha256").update(content).digest("hex").slice(0, 16);
|
|
269209
269717
|
_ssrCache.set(pathname, { html: content, etag, expires: Date.now() + 6e4 });
|
|
269210
269718
|
return { html: content, etag };
|
|
269211
269719
|
}
|
|
269212
269720
|
return null;
|
|
269213
269721
|
}
|
|
269214
269722
|
function setCachedSSR(pathname, html) {
|
|
269215
|
-
const etag = (0,
|
|
269723
|
+
const etag = (0, import_node_crypto7.createHash)("sha256").update(html).digest("hex").slice(0, 16);
|
|
269216
269724
|
_ssrCache.set(pathname, { html, etag, expires: Date.now() + 6e4 });
|
|
269217
269725
|
try {
|
|
269218
269726
|
if (!(0, import_node_fs15.existsSync)(_diskCacheDir)) (0, import_node_fs14.mkdirSync)(_diskCacheDir, { recursive: true });
|
|
@@ -269244,13 +269752,13 @@ function generatePreloadHints(assets) {
|
|
|
269244
269752
|
}
|
|
269245
269753
|
return hints.join("\n");
|
|
269246
269754
|
}
|
|
269247
|
-
var import_node_fs14,
|
|
269755
|
+
var import_node_fs14, import_node_crypto7, import_node_path21, import_node_fs15, SSRRenderer, _ssrCache, _diskCacheDir;
|
|
269248
269756
|
var init_ssr = __esm({
|
|
269249
269757
|
"../../packages/server/tw/ssr/index.ts"() {
|
|
269250
269758
|
import_node_fs14 = require("node:fs");
|
|
269251
269759
|
init_fs_compat();
|
|
269252
269760
|
init_tw();
|
|
269253
|
-
|
|
269761
|
+
import_node_crypto7 = require("node:crypto");
|
|
269254
269762
|
import_node_path21 = require("node:path");
|
|
269255
269763
|
import_node_fs15 = require("node:fs");
|
|
269256
269764
|
SSRRenderer = class {
|
|
@@ -269674,7 +270182,7 @@ function generateNonce2(length = 32) {
|
|
|
269674
270182
|
if (typeof crypto !== "undefined" && crypto.getRandomValues) {
|
|
269675
270183
|
crypto.getRandomValues(bytes);
|
|
269676
270184
|
} else {
|
|
269677
|
-
const hash2 = (0,
|
|
270185
|
+
const hash2 = (0, import_node_crypto8.createHash)("sha256");
|
|
269678
270186
|
hash2.update(`${Date.now()}-${nonceCounter++}-${Math.random()}`);
|
|
269679
270187
|
return hash2.digest("base64");
|
|
269680
270188
|
}
|
|
@@ -269792,10 +270300,10 @@ function createProdCSP() {
|
|
|
269792
270300
|
"block-all-mixed-content": true
|
|
269793
270301
|
};
|
|
269794
270302
|
}
|
|
269795
|
-
var
|
|
270303
|
+
var import_node_crypto8, DEFAULT_CSP, DEFAULT_OPTIONS5, nonceCounter;
|
|
269796
270304
|
var init_security_headers2 = __esm({
|
|
269797
270305
|
"../../packages/server/tw/security-headers.ts"() {
|
|
269798
|
-
|
|
270306
|
+
import_node_crypto8 = require("node:crypto");
|
|
269799
270307
|
DEFAULT_CSP = {
|
|
269800
270308
|
"default-src": ["'self'"],
|
|
269801
270309
|
"script-src": ["'self'"],
|
|
@@ -270589,17 +271097,17 @@ async function hs256(payload, secret, maxAge) {
|
|
|
270589
271097
|
const header = { alg: "HS256", typ: "JWT" };
|
|
270590
271098
|
const body = { ...payload, iat: now, exp: now + maxAge };
|
|
270591
271099
|
const data = enc(header) + "." + enc(body);
|
|
270592
|
-
const { createHmac:
|
|
270593
|
-
const sig =
|
|
271100
|
+
const { createHmac: createHmac3 } = await import("node:crypto");
|
|
271101
|
+
const sig = createHmac3("sha256", secret).update(data).digest("base64url");
|
|
270594
271102
|
return data + "." + sig;
|
|
270595
271103
|
}
|
|
270596
271104
|
async function verifyHs256(token, secret) {
|
|
270597
271105
|
const parts = token.split(".");
|
|
270598
271106
|
if (parts.length !== 3) return null;
|
|
270599
|
-
const { createHmac:
|
|
270600
|
-
const expected =
|
|
271107
|
+
const { createHmac: createHmac3, timingSafeEqual: timingSafeEqual3 } = await import("node:crypto");
|
|
271108
|
+
const expected = createHmac3("sha256", secret).update(parts[0] + "." + parts[1]).digest("base64url");
|
|
270601
271109
|
const a = Buffer.from(expected), b = Buffer.from(parts[2]);
|
|
270602
|
-
if (a.length !== b.length || !
|
|
271110
|
+
if (a.length !== b.length || !timingSafeEqual3(a, b)) return null;
|
|
270603
271111
|
try {
|
|
270604
271112
|
const body = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8"));
|
|
270605
271113
|
if (typeof body.exp === "number" && body.exp < Math.floor(Date.now() / 1e3)) return null;
|
|
@@ -271904,6 +272412,19 @@ ${err2.stack ?? ""}`);
|
|
|
271904
272412
|
const isrLookupPath = pathname.length > 1 ? pathname.replace(/\/+$/, "") : pathname;
|
|
271905
272413
|
if (this.staticHandler && !this.isrRoutes.has(isrLookupPath)) {
|
|
271906
272414
|
let staticResponse = await this.staticHandler.serve(pathname, request);
|
|
272415
|
+
if (staticResponse.status !== 404) {
|
|
272416
|
+
const reqMethod = request.method.toUpperCase();
|
|
272417
|
+
const allow = "GET, HEAD, OPTIONS";
|
|
272418
|
+
if (reqMethod === "TRACE") {
|
|
272419
|
+
return new Response("Method Not Allowed", { status: 405, headers: { Allow: allow } });
|
|
272420
|
+
}
|
|
272421
|
+
if (reqMethod === "OPTIONS") {
|
|
272422
|
+
return new Response(null, { status: 204, headers: { Allow: allow } });
|
|
272423
|
+
}
|
|
272424
|
+
if (reqMethod !== "GET" && reqMethod !== "HEAD") {
|
|
272425
|
+
return new Response("Method Not Allowed", { status: 405, headers: { Allow: allow } });
|
|
272426
|
+
}
|
|
272427
|
+
}
|
|
271907
272428
|
if (staticResponse && this.cspEnabled() && (staticResponse.headers.get("content-type") || "").includes("text/html")) {
|
|
271908
272429
|
try {
|
|
271909
272430
|
let buf = Buffer.from(await staticResponse.arrayBuffer());
|
|
@@ -272048,6 +272569,32 @@ ${err2.stack ?? ""}`);
|
|
|
272048
272569
|
});
|
|
272049
272570
|
|
|
272050
272571
|
// ../../packages/runtime/tw/dependency-graph.ts
|
|
272572
|
+
function flushPendingSubs() {
|
|
272573
|
+
if (isFlushing || !pendingSubs) return;
|
|
272574
|
+
isFlushing = true;
|
|
272575
|
+
try {
|
|
272576
|
+
let guard = 0;
|
|
272577
|
+
while (pendingSubs && pendingSubs.size > 0 && guard++ < 1e5) {
|
|
272578
|
+
const batchOf = pendingSubs;
|
|
272579
|
+
pendingSubs = /* @__PURE__ */ new Set();
|
|
272580
|
+
for (const sub of batchOf) {
|
|
272581
|
+
try {
|
|
272582
|
+
sub();
|
|
272583
|
+
} catch (e) {
|
|
272584
|
+
console.error("[TW Reactivity] Signal subscriber error:", e);
|
|
272585
|
+
}
|
|
272586
|
+
}
|
|
272587
|
+
}
|
|
272588
|
+
} finally {
|
|
272589
|
+
isFlushing = false;
|
|
272590
|
+
pendingSubs = null;
|
|
272591
|
+
}
|
|
272592
|
+
}
|
|
272593
|
+
function enqueueSubs(subs) {
|
|
272594
|
+
if (!pendingSubs) pendingSubs = /* @__PURE__ */ new Set();
|
|
272595
|
+
for (const s of subs) pendingSubs.add(s);
|
|
272596
|
+
if (batchDepth === 0) flushPendingSubs();
|
|
272597
|
+
}
|
|
272051
272598
|
function startBatch() {
|
|
272052
272599
|
batchDepth++;
|
|
272053
272600
|
}
|
|
@@ -272057,13 +272604,7 @@ function endBatch() {
|
|
|
272057
272604
|
batchDepth = 0;
|
|
272058
272605
|
const effects = Array.from(batchedEffects);
|
|
272059
272606
|
batchedEffects.clear();
|
|
272060
|
-
|
|
272061
|
-
try {
|
|
272062
|
-
eff();
|
|
272063
|
-
} catch (e) {
|
|
272064
|
-
console.error("[TW Reactivity] Batch effect error:", e);
|
|
272065
|
-
}
|
|
272066
|
-
}
|
|
272607
|
+
enqueueSubs(effects);
|
|
272067
272608
|
}
|
|
272068
272609
|
}
|
|
272069
272610
|
function batch(fn) {
|
|
@@ -272245,12 +272786,14 @@ function isComputed(value) {
|
|
|
272245
272786
|
function createScope3() {
|
|
272246
272787
|
return new ReactiveScope();
|
|
272247
272788
|
}
|
|
272248
|
-
var currentEffect, batchDepth, batchedEffects, SignalImpl, ComputedImpl, watchEffect, currentCleanup, ReactiveScope;
|
|
272789
|
+
var currentEffect, batchDepth, batchedEffects, pendingSubs, isFlushing, SignalImpl, ComputedImpl, watchEffect, currentCleanup, ReactiveScope;
|
|
272249
272790
|
var init_dependency_graph = __esm({
|
|
272250
272791
|
"../../packages/runtime/tw/dependency-graph.ts"() {
|
|
272251
272792
|
currentEffect = null;
|
|
272252
272793
|
batchDepth = 0;
|
|
272253
272794
|
batchedEffects = /* @__PURE__ */ new Set();
|
|
272795
|
+
pendingSubs = null;
|
|
272796
|
+
isFlushing = false;
|
|
272254
272797
|
SignalImpl = class {
|
|
272255
272798
|
value;
|
|
272256
272799
|
subscribers = /* @__PURE__ */ new Set();
|
|
@@ -272269,19 +272812,9 @@ var init_dependency_graph = __esm({
|
|
|
272269
272812
|
if (this.equals(this.value, value)) return;
|
|
272270
272813
|
this.value = value;
|
|
272271
272814
|
if (batchDepth > 0) {
|
|
272272
|
-
for (const sub of this.subscribers)
|
|
272273
|
-
batchedEffects.add(sub);
|
|
272274
|
-
}
|
|
272275
|
-
} else {
|
|
272276
|
-
const subs = Array.from(this.subscribers);
|
|
272277
|
-
for (const sub of subs) {
|
|
272278
|
-
try {
|
|
272279
|
-
sub();
|
|
272280
|
-
} catch (e) {
|
|
272281
|
-
console.error("[TW Reactivity] Signal subscriber error:", e);
|
|
272282
|
-
}
|
|
272283
|
-
}
|
|
272815
|
+
for (const sub of this.subscribers) batchedEffects.add(sub);
|
|
272284
272816
|
}
|
|
272817
|
+
enqueueSubs(this.subscribers);
|
|
272285
272818
|
}
|
|
272286
272819
|
update(fn) {
|
|
272287
272820
|
this.set(fn(this.value));
|
|
@@ -272347,14 +272880,11 @@ var init_dependency_graph = __esm({
|
|
|
272347
272880
|
return this.cachedValue;
|
|
272348
272881
|
}
|
|
272349
272882
|
notify() {
|
|
272350
|
-
|
|
272351
|
-
|
|
272352
|
-
|
|
272353
|
-
sub();
|
|
272354
|
-
} catch (e) {
|
|
272355
|
-
console.error("[TW Reactivity] Computed subscriber error:", e);
|
|
272356
|
-
}
|
|
272883
|
+
if (batchDepth > 0) {
|
|
272884
|
+
for (const sub of this.subscribers) batchedEffects.add(sub);
|
|
272885
|
+
return;
|
|
272357
272886
|
}
|
|
272887
|
+
enqueueSubs(this.subscribers);
|
|
272358
272888
|
}
|
|
272359
272889
|
set(_value) {
|
|
272360
272890
|
throw new Error("Cannot set a computed signal");
|
|
@@ -275539,10 +276069,12 @@ var init_diff = __esm({
|
|
|
275539
276069
|
"../../packages/runtime/tw/diff.ts"() {
|
|
275540
276070
|
init_vdom2();
|
|
275541
276071
|
HTML_ESCAPE_MAP = {
|
|
275542
|
-
|
|
275543
|
-
|
|
275544
|
-
"
|
|
275545
|
-
|
|
276072
|
+
// These four mapped to THEMSELVES, so a <script> tag passed through
|
|
276073
|
+
// untouched -- only ' was actually escaped.
|
|
276074
|
+
"&": "&",
|
|
276075
|
+
"<": "<",
|
|
276076
|
+
">": ">",
|
|
276077
|
+
'"': """,
|
|
275546
276078
|
"'": "'"
|
|
275547
276079
|
};
|
|
275548
276080
|
HTML_ESCAPE_REGEX = /[&<>"']/g;
|
|
@@ -279072,8 +279604,19 @@ async function hydrateElementById(id, component, root = typeof document !== "und
|
|
|
279072
279604
|
);
|
|
279073
279605
|
}
|
|
279074
279606
|
function markForHydration(element, componentId, props) {
|
|
279607
|
+
if (!element || typeof componentId !== "string") return;
|
|
279075
279608
|
element.setAttribute(HYDRATE_ATTR, componentId);
|
|
279076
|
-
|
|
279609
|
+
let serialised = null;
|
|
279610
|
+
if (props && typeof props === "object") {
|
|
279611
|
+
try {
|
|
279612
|
+
serialised = JSON.stringify(props);
|
|
279613
|
+
} catch {
|
|
279614
|
+
serialised = null;
|
|
279615
|
+
}
|
|
279616
|
+
}
|
|
279617
|
+
if (serialised !== null && serialised !== void 0) {
|
|
279618
|
+
element.setAttribute("data-tw-props", serialised);
|
|
279619
|
+
}
|
|
279077
279620
|
}
|
|
279078
279621
|
function unmarkForHydration(element) {
|
|
279079
279622
|
element.removeAttribute(HYDRATE_ATTR);
|
|
@@ -282373,15 +282916,21 @@ function usePathname(opts) {
|
|
|
282373
282916
|
function matchPathPattern(path, pattern) {
|
|
282374
282917
|
const pSeg = path.split("/").filter(Boolean);
|
|
282375
282918
|
const tSeg = pattern.split("/").filter(Boolean);
|
|
282376
|
-
|
|
282919
|
+
const catchAllAt = tSeg.findIndex((t3) => t3.startsWith("[...") || t3.startsWith(":..."));
|
|
282920
|
+
if (catchAllAt === -1) {
|
|
282921
|
+
if (pSeg.length !== tSeg.length) return { matches: false, params: {}, score: 0 };
|
|
282922
|
+
} else if (pSeg.length < tSeg.length) {
|
|
282923
|
+
return { matches: false, params: {}, score: 0 };
|
|
282924
|
+
}
|
|
282377
282925
|
const params = {};
|
|
282378
282926
|
let literal = 0;
|
|
282379
282927
|
for (let i = 0; i < tSeg.length; i++) {
|
|
282380
282928
|
const t3 = tSeg[i];
|
|
282381
282929
|
const isParam = t3.startsWith("[") && t3.endsWith("]") || t3.startsWith(":");
|
|
282382
282930
|
if (isParam) {
|
|
282383
|
-
const name = t3.startsWith("[") ? t3.slice(1, -1).replace(
|
|
282384
|
-
|
|
282931
|
+
const name = t3.startsWith("[") ? t3.slice(1, -1).replace(/^\.\.\./, "") : t3.slice(1);
|
|
282932
|
+
const isCatchAll = t3.startsWith("[...") || t3.startsWith(":...");
|
|
282933
|
+
params[name] = isCatchAll ? pSeg.slice(i).map((seg) => decodeURIComponent(seg)).join("/") : decodeURIComponent(pSeg[i]);
|
|
282385
282934
|
} else if (t3 !== pSeg[i]) {
|
|
282386
282935
|
return { matches: false, params: {}, score: 0 };
|
|
282387
282936
|
} else {
|
|
@@ -283196,7 +283745,11 @@ function defineStore(idOrDefinition, maybeDefinition) {
|
|
|
283196
283745
|
const getterDeps = Object.values(reactiveState);
|
|
283197
283746
|
for (const key2 of Object.keys(getters)) {
|
|
283198
283747
|
const getter = getters[key2];
|
|
283199
|
-
getterValues[key2] = computed4(getterDeps,
|
|
283748
|
+
getterValues[key2] = computed4(getterDeps, () => {
|
|
283749
|
+
const snap = {};
|
|
283750
|
+
for (const k of Object.keys(reactiveState)) snap[k] = reactiveState[k].value;
|
|
283751
|
+
return getter.call(snap, snap);
|
|
283752
|
+
});
|
|
283200
283753
|
}
|
|
283201
283754
|
const subscribers = /* @__PURE__ */ new Set();
|
|
283202
283755
|
const actionListeners = /* @__PURE__ */ new Set();
|
|
@@ -283239,7 +283792,7 @@ function defineStore(idOrDefinition, maybeDefinition) {
|
|
|
283239
283792
|
const fn = actions[key2];
|
|
283240
283793
|
boundActions[key2] = (...args) => {
|
|
283241
283794
|
actionListeners.forEach((cb) => cb({ name: key2, args }));
|
|
283242
|
-
return fn.call(
|
|
283795
|
+
return fn.call(stateProxy, stateProxy, ...args);
|
|
283243
283796
|
};
|
|
283244
283797
|
}
|
|
283245
283798
|
for (const rv of Object.values(reactiveState)) {
|
|
@@ -283351,9 +283904,10 @@ var init_store = __esm({
|
|
|
283351
283904
|
};
|
|
283352
283905
|
}
|
|
283353
283906
|
_notify() {
|
|
283907
|
+
const current2 = this.value ?? this._value;
|
|
283354
283908
|
for (const l of this._listeners) {
|
|
283355
283909
|
try {
|
|
283356
|
-
l();
|
|
283910
|
+
l(current2);
|
|
283357
283911
|
} catch {
|
|
283358
283912
|
}
|
|
283359
283913
|
}
|
|
@@ -284359,7 +284913,8 @@ var init_virtual_list = __esm({
|
|
|
284359
284913
|
return this.container.scrollTop;
|
|
284360
284914
|
}
|
|
284361
284915
|
getFirstVisibleIndex() {
|
|
284362
|
-
|
|
284916
|
+
const last = Math.max(0, this.options?.itemCount ?? 0 - 1);
|
|
284917
|
+
return Math.min(Math.max(this.visibleRange.start, 0), last);
|
|
284363
284918
|
}
|
|
284364
284919
|
getLastVisibleIndex() {
|
|
284365
284920
|
return this.visibleRange.end;
|