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/tw-server.mjs
CHANGED
|
@@ -6908,20 +6908,39 @@ function sha12(data) {
|
|
|
6908
6908
|
async function md52(data) {
|
|
6909
6909
|
return hashWithWebCrypto("md5", data);
|
|
6910
6910
|
}
|
|
6911
|
+
function webcryptoHashName(algorithm) {
|
|
6912
|
+
const key2 = String(algorithm).toLowerCase().replace(/[-_]/g, "");
|
|
6913
|
+
const map2 = {
|
|
6914
|
+
md5: "MD5",
|
|
6915
|
+
sha1: "SHA-1",
|
|
6916
|
+
sha224: "SHA-224",
|
|
6917
|
+
sha256: "SHA-256",
|
|
6918
|
+
sha384: "SHA-384",
|
|
6919
|
+
sha512: "SHA-512"
|
|
6920
|
+
};
|
|
6921
|
+
return map2[key2] ?? algorithm;
|
|
6922
|
+
}
|
|
6911
6923
|
async function hmac2(data, key2, algorithm = "sha256") {
|
|
6912
6924
|
const encoder2 = new TextEncoder();
|
|
6913
6925
|
const keyData = encoder2.encode(key2);
|
|
6914
6926
|
const messageData = encoder2.encode(data);
|
|
6915
|
-
const cryptoKey = await crypto.subtle.importKey("raw", keyData, { name: "HMAC", hash: algorithm }, false, ["sign"]);
|
|
6927
|
+
const cryptoKey = await crypto.subtle.importKey("raw", keyData, { name: "HMAC", hash: webcryptoHashName(algorithm) }, false, ["sign"]);
|
|
6916
6928
|
const signature = await crypto.subtle.sign("HMAC", cryptoKey, messageData);
|
|
6917
6929
|
const hashArray = Array.from(new Uint8Array(signature));
|
|
6918
6930
|
return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
6919
6931
|
}
|
|
6932
|
+
async function verifyHmac(data, key2, signature, algorithm = "sha256") {
|
|
6933
|
+
const expected = await hmac2(data, key2, algorithm);
|
|
6934
|
+
if (expected.length !== signature.length) return false;
|
|
6935
|
+
let diff2 = 0;
|
|
6936
|
+
for (let i = 0; i < expected.length; i++) diff2 |= expected.charCodeAt(i) ^ signature.charCodeAt(i);
|
|
6937
|
+
return diff2 === 0;
|
|
6938
|
+
}
|
|
6920
6939
|
async function pbkdf2(password, salt, iterations = 1e5, keyLength = 256) {
|
|
6921
6940
|
const encoder2 = new TextEncoder();
|
|
6922
6941
|
const keyMaterial = await crypto.subtle.importKey("raw", encoder2.encode(password), "PBKDF2", false, ["deriveBits"]);
|
|
6923
6942
|
const derivedBits = await crypto.subtle.deriveBits(
|
|
6924
|
-
{ name: "PBKDF2", salt: encoder2.encode(salt), iterations, hash: "
|
|
6943
|
+
{ name: "PBKDF2", salt: encoder2.encode(salt), iterations, hash: "SHA-256" },
|
|
6925
6944
|
keyMaterial,
|
|
6926
6945
|
keyLength
|
|
6927
6946
|
);
|
|
@@ -6966,9 +6985,16 @@ async function decryptString(encryptedData, password, salt) {
|
|
|
6966
6985
|
const key2 = await deriveKey(password, salt);
|
|
6967
6986
|
return decryptAES(encrypted, key2, iv);
|
|
6968
6987
|
}
|
|
6988
|
+
async function generateRSASigningKeyPair(bits = 2048) {
|
|
6989
|
+
return crypto.subtle.generateKey(
|
|
6990
|
+
{ name: "RSASSA-PKCS1-v1_5", modulusLength: bits, publicExponent: new Uint8Array([1, 0, 1]), hash: "SHA-256" },
|
|
6991
|
+
true,
|
|
6992
|
+
["sign", "verify"]
|
|
6993
|
+
);
|
|
6994
|
+
}
|
|
6969
6995
|
async function generateRSAKeyPair(bits = 2048) {
|
|
6970
6996
|
return crypto.subtle.generateKey(
|
|
6971
|
-
{ name: "RSA-OAEP", modulusLength: bits, publicExponent: new Uint8Array([1, 0, 1]), hash: "
|
|
6997
|
+
{ name: "RSA-OAEP", modulusLength: bits, publicExponent: new Uint8Array([1, 0, 1]), hash: "SHA-256" },
|
|
6972
6998
|
true,
|
|
6973
6999
|
["encrypt", "decrypt"]
|
|
6974
7000
|
);
|
|
@@ -8416,36 +8442,6 @@ var init_etag = __esm({
|
|
|
8416
8442
|
});
|
|
8417
8443
|
|
|
8418
8444
|
// ../../packages/shared/tw/hashing/hmac.ts
|
|
8419
|
-
function bufferToHex2(buffer) {
|
|
8420
|
-
const bytes = new Uint8Array(buffer);
|
|
8421
|
-
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
8422
|
-
}
|
|
8423
|
-
function timingSafeEqual2(a, b) {
|
|
8424
|
-
if (a.length !== b.length) return false;
|
|
8425
|
-
let result = 0;
|
|
8426
|
-
for (let i = 0; i < a.length; i++) {
|
|
8427
|
-
result |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
8428
|
-
}
|
|
8429
|
-
return result === 0;
|
|
8430
|
-
}
|
|
8431
|
-
async function hmac3(key2, message, algorithm = "SHA-256") {
|
|
8432
|
-
const encoder2 = new TextEncoder();
|
|
8433
|
-
const keyData = encoder2.encode(key2);
|
|
8434
|
-
const messageData = encoder2.encode(message);
|
|
8435
|
-
const cryptoKey = await crypto.subtle.importKey(
|
|
8436
|
-
"raw",
|
|
8437
|
-
keyData,
|
|
8438
|
-
{ name: "HMAC", hash: algorithm },
|
|
8439
|
-
false,
|
|
8440
|
-
["sign"]
|
|
8441
|
-
);
|
|
8442
|
-
const signature = await crypto.subtle.sign("HMAC", cryptoKey, messageData);
|
|
8443
|
-
return bufferToHex2(signature);
|
|
8444
|
-
}
|
|
8445
|
-
async function verifyHmac(key2, message, signature, algorithm = "SHA-256") {
|
|
8446
|
-
const computed6 = await hmac3(key2, message, algorithm);
|
|
8447
|
-
return timingSafeEqual2(computed6, signature);
|
|
8448
|
-
}
|
|
8449
8445
|
var init_hmac = __esm({
|
|
8450
8446
|
"../../packages/shared/tw/hashing/hmac.ts"() {
|
|
8451
8447
|
}
|
|
@@ -9110,6 +9106,7 @@ __export(tw_exports, {
|
|
|
9110
9106
|
generatePIN: () => generatePIN,
|
|
9111
9107
|
generateProofOfKnowledge: () => generateProofOfKnowledge,
|
|
9112
9108
|
generateRSAKeyPair: () => generateRSAKeyPair,
|
|
9109
|
+
generateRSASigningKeyPair: () => generateRSASigningKeyPair,
|
|
9113
9110
|
generateSecret: () => generateSecret,
|
|
9114
9111
|
generateTOTP: () => generateTOTP,
|
|
9115
9112
|
geolocation: () => geolocation,
|
|
@@ -9615,6 +9612,7 @@ __export(tw_exports, {
|
|
|
9615
9612
|
zip: () => zip,
|
|
9616
9613
|
zipWith: () => zipWith
|
|
9617
9614
|
});
|
|
9615
|
+
import { createHmac } from "node:crypto";
|
|
9618
9616
|
function generateETag(data) {
|
|
9619
9617
|
let hash2 = 0;
|
|
9620
9618
|
for (let i = 0; i < data.length; i++) {
|
|
@@ -9640,19 +9638,14 @@ function merkleRoot(hashes) {
|
|
|
9640
9638
|
return level[0];
|
|
9641
9639
|
}
|
|
9642
9640
|
function hmacSign(data, key2) {
|
|
9643
|
-
|
|
9644
|
-
for (let i = 0; i < data.length; i++) {
|
|
9645
|
-
h1 = (h1 << 5) - h1 + data.charCodeAt(i);
|
|
9646
|
-
h1 |= 0;
|
|
9647
|
-
}
|
|
9648
|
-
for (let i = 0; i < key2.length; i++) {
|
|
9649
|
-
h22 = (h22 << 5) - h22 + key2.charCodeAt(i);
|
|
9650
|
-
h22 |= 0;
|
|
9651
|
-
}
|
|
9652
|
-
return (h1 ^ h22).toString(16);
|
|
9641
|
+
return createHmac("sha256", key2).update(data).digest("hex");
|
|
9653
9642
|
}
|
|
9654
9643
|
function hmacVerify(data, key2, signature) {
|
|
9655
|
-
|
|
9644
|
+
const expected = hmacSign(data, key2);
|
|
9645
|
+
if (expected.length !== signature.length) return false;
|
|
9646
|
+
let diff2 = 0;
|
|
9647
|
+
for (let i = 0; i < expected.length; i++) diff2 |= expected.charCodeAt(i) ^ signature.charCodeAt(i);
|
|
9648
|
+
return diff2 === 0;
|
|
9656
9649
|
}
|
|
9657
9650
|
var HTML_TAGS;
|
|
9658
9651
|
var init_tw = __esm({
|
|
@@ -9809,6 +9802,9 @@ function optimizedImageUrl(src, opts = {}) {
|
|
|
9809
9802
|
if (opts.quality) p.set("q", String(opts.quality));
|
|
9810
9803
|
p.set("f", opts.format ?? "auto");
|
|
9811
9804
|
const qs = p.toString();
|
|
9805
|
+
if (/^https?:\/\//i.test(src)) {
|
|
9806
|
+
return `/_tw/img/_remote?src=${encodeURIComponent(src)}&${qs}`;
|
|
9807
|
+
}
|
|
9812
9808
|
return `/_tw/img${src.startsWith("/") ? src : "/" + src}?${qs}`;
|
|
9813
9809
|
}
|
|
9814
9810
|
function imageAttributesFor(opts, config = DEFAULT_IMAGE_CONFIG) {
|
|
@@ -9816,7 +9812,7 @@ function imageAttributesFor(opts, config = DEFAULT_IMAGE_CONFIG) {
|
|
|
9816
9812
|
const quality = opts.quality ?? config.quality;
|
|
9817
9813
|
const format = config.formats[0] ?? "webp";
|
|
9818
9814
|
const isRemote = /^https?:\/\//i.test(opts.src);
|
|
9819
|
-
const src = isRemote ? `/_tw/img/_remote?src=${encodeURIComponent(opts.src)}
|
|
9815
|
+
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 });
|
|
9820
9816
|
const widths = config.breakpoints.filter((bp) => width === 0 || bp <= width * 2);
|
|
9821
9817
|
const srcset = widths.map(
|
|
9822
9818
|
(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`
|
|
@@ -9996,7 +9992,7 @@ function createImageHandler(options) {
|
|
|
9996
9992
|
}
|
|
9997
9993
|
if (!warnedNoSharp) {
|
|
9998
9994
|
warnedNoSharp = true;
|
|
9999
|
-
console.warn(" [tw/image] sharp not installed \u2014
|
|
9995
|
+
console.warn(" [tw/image] sharp not installed \u2014 using the built-in converter (WebP/AVIF); install sharp for faster resizing");
|
|
10000
9996
|
}
|
|
10001
9997
|
return { data, contentType: "", viaSharp: false };
|
|
10002
9998
|
}
|
|
@@ -10788,6 +10784,18 @@ function findGlobalError(rootNode2) {
|
|
|
10788
10784
|
function findRootError(rootNode2) {
|
|
10789
10785
|
return (rootNode2.files || []).find((f) => f.type === "error");
|
|
10790
10786
|
}
|
|
10787
|
+
function findNearestNotFound(rootNode2, pathParts) {
|
|
10788
|
+
let found = (rootNode2.files || []).find((f) => f.type === "not-found");
|
|
10789
|
+
let node = rootNode2;
|
|
10790
|
+
for (const part of pathParts) {
|
|
10791
|
+
const child = (node.children || []).find((c) => c.segment === part);
|
|
10792
|
+
if (!child) break;
|
|
10793
|
+
const f = (child.files || []).find((x) => x.type === "not-found");
|
|
10794
|
+
if (f) found = f;
|
|
10795
|
+
node = child;
|
|
10796
|
+
}
|
|
10797
|
+
return found;
|
|
10798
|
+
}
|
|
10791
10799
|
function findRootNotFound(rootNode2) {
|
|
10792
10800
|
return rootNode2.files.find((f) => f.type === "not-found");
|
|
10793
10801
|
}
|
|
@@ -228157,6 +228165,10 @@ function parseTWSelector(cursor, errors, state, parseBody) {
|
|
|
228157
228165
|
while (!cursor.done) {
|
|
228158
228166
|
const ct = cursor.peek();
|
|
228159
228167
|
if (!ct) break;
|
|
228168
|
+
if (ct.type === "IDENT" && ct.value === "on") {
|
|
228169
|
+
const after2 = cursor.peek(1);
|
|
228170
|
+
if (after2 && after2.type === "COLON") break;
|
|
228171
|
+
}
|
|
228160
228172
|
if (ct.type === "IDENT" && !nextPrevMinus && nextClass.length > 0 && (CLASS_BOUNDARY_BOOLEAN_ATTRS.has(ct.value) || inlineAttrStartsAt(cursor))) {
|
|
228161
228173
|
break;
|
|
228162
228174
|
}
|
|
@@ -228447,7 +228459,9 @@ function parseTWSelector(cursor, errors, state, parseBody) {
|
|
|
228447
228459
|
const first = cursor.peek();
|
|
228448
228460
|
const second = cursor.peek(1);
|
|
228449
228461
|
const CONTROL_FLOW = /* @__PURE__ */ new Set(["for", "if", "while", "switch", "else", "try", "catch"]);
|
|
228450
|
-
const
|
|
228462
|
+
const third = cursor.peek(2);
|
|
228463
|
+
const isPropBlock = second && second.type === "ASSIGN" && third && third.type === "LBRACE";
|
|
228464
|
+
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");
|
|
228451
228465
|
if (isAttrBlock) {
|
|
228452
228466
|
while (!cursor.done) {
|
|
228453
228467
|
const t3 = cursor.peek();
|
|
@@ -228455,6 +228469,34 @@ function parseTWSelector(cursor, errors, state, parseBody) {
|
|
|
228455
228469
|
if (t3.type === "IDENT" || t3.type === "KEYWORD") {
|
|
228456
228470
|
const attrName = t3.value;
|
|
228457
228471
|
cursor.advance();
|
|
228472
|
+
if (cursor.peek() && (cursor.peek().type === "ASSIGN" || cursor.peek().type === "EQUALS")) {
|
|
228473
|
+
cursor.advance();
|
|
228474
|
+
cursor.skipWhitespace();
|
|
228475
|
+
if (cursor.peek() && cursor.peek().type === "LBRACE") {
|
|
228476
|
+
cursor.advance();
|
|
228477
|
+
let expr = "";
|
|
228478
|
+
let depth = 1;
|
|
228479
|
+
let prevWS = false;
|
|
228480
|
+
while (!cursor.done) {
|
|
228481
|
+
const tk = cursor.peek();
|
|
228482
|
+
if (!tk || tk.type === "EOF") break;
|
|
228483
|
+
if (tk.type === "LBRACE") depth++;
|
|
228484
|
+
else if (tk.type === "RBRACE") {
|
|
228485
|
+
depth--;
|
|
228486
|
+
if (depth === 0) {
|
|
228487
|
+
cursor.advance();
|
|
228488
|
+
break;
|
|
228489
|
+
}
|
|
228490
|
+
}
|
|
228491
|
+
if (expr && prevWS) expr += " ";
|
|
228492
|
+
expr += tk.raw ?? tk.value;
|
|
228493
|
+
prevWS = !!tk.followedByWhitespace;
|
|
228494
|
+
cursor.advance();
|
|
228495
|
+
}
|
|
228496
|
+
el.attrs.push(createAttribute(attrName, expr.trim(), t3.pos.line, t3.pos.col, true));
|
|
228497
|
+
continue;
|
|
228498
|
+
}
|
|
228499
|
+
}
|
|
228458
228500
|
const vToken = cursor.peek();
|
|
228459
228501
|
if (vToken && (vToken.type === "STRING" || vToken.type === "NUMBER")) {
|
|
228460
228502
|
cursor.advance();
|
|
@@ -237200,13 +237242,17 @@ var init_types4 = __esm({
|
|
|
237200
237242
|
var TW_VERSION, TW_GENERATOR_META;
|
|
237201
237243
|
var init_version = __esm({
|
|
237202
237244
|
"../../packages/compiler/tw/codegen/version.ts"() {
|
|
237203
|
-
TW_VERSION = "2.0.
|
|
237245
|
+
TW_VERSION = "2.0.1";
|
|
237204
237246
|
TW_GENERATOR_META = `<meta name="generator" content="TW Framework ${TW_VERSION}">`;
|
|
237205
237247
|
}
|
|
237206
237248
|
});
|
|
237207
237249
|
|
|
237208
237250
|
// ../../packages/link/tw/index.ts
|
|
237251
|
+
function isDangerousHref(href) {
|
|
237252
|
+
return DANGEROUS_SCHEME.test(href.replace(/[\u0000-\u0020]/g, ""));
|
|
237253
|
+
}
|
|
237209
237254
|
function isExternalHref(href) {
|
|
237255
|
+
if (isDangerousHref(href)) return false;
|
|
237210
237256
|
return /^[a-z][a-z0-9+.-]*:/i.test(href) || // any scheme: http(s), mailto, tel, ...
|
|
237211
237257
|
href.startsWith("//");
|
|
237212
237258
|
}
|
|
@@ -237215,7 +237261,8 @@ function isWebExternal(href) {
|
|
|
237215
237261
|
return true;
|
|
237216
237262
|
}
|
|
237217
237263
|
function linkAttributesFor(opts) {
|
|
237218
|
-
const
|
|
237264
|
+
const rawHref = (opts.href ?? "").trim();
|
|
237265
|
+
const href = isDangerousHref(rawHref) ? "#" : rawHref;
|
|
237219
237266
|
const external = opts.external === true || isExternalHref(href);
|
|
237220
237267
|
const attrs = {};
|
|
237221
237268
|
if (external) {
|
|
@@ -237244,8 +237291,10 @@ function linkAttributesFor(opts) {
|
|
|
237244
237291
|
if (opts.id) attrs["id"] = opts.id;
|
|
237245
237292
|
return { href, external, attrs };
|
|
237246
237293
|
}
|
|
237294
|
+
var DANGEROUS_SCHEME;
|
|
237247
237295
|
var init_tw3 = __esm({
|
|
237248
237296
|
"../../packages/link/tw/index.ts"() {
|
|
237297
|
+
DANGEROUS_SCHEME = /^(javascript|data|vbscript):/i;
|
|
237249
237298
|
}
|
|
237250
237299
|
});
|
|
237251
237300
|
|
|
@@ -237758,12 +237807,31 @@ var init_interpolate = __esm({
|
|
|
237758
237807
|
});
|
|
237759
237808
|
|
|
237760
237809
|
// ../../packages/compiler/tw/eval/evaluate.ts
|
|
237810
|
+
function setEvalBindings(bindings) {
|
|
237811
|
+
extraBindings = bindings ?? {};
|
|
237812
|
+
}
|
|
237761
237813
|
function evaluate(expr, vars, sandbox) {
|
|
237762
237814
|
if (!expr) return "";
|
|
237763
237815
|
const sb = sandbox ?? defaultSandbox;
|
|
237764
237816
|
if (vars[expr] !== void 0) {
|
|
237765
237817
|
return vars[expr];
|
|
237766
237818
|
}
|
|
237819
|
+
if (expr.startsWith("(") && expr.endsWith(")")) {
|
|
237820
|
+
let d = 0;
|
|
237821
|
+
let wraps = true;
|
|
237822
|
+
for (let i = 0; i < expr.length; i++) {
|
|
237823
|
+
const ch = expr[i];
|
|
237824
|
+
if (ch === "(") d++;
|
|
237825
|
+
else if (ch === ")") {
|
|
237826
|
+
d--;
|
|
237827
|
+
if (d === 0 && i < expr.length - 1) {
|
|
237828
|
+
wraps = false;
|
|
237829
|
+
break;
|
|
237830
|
+
}
|
|
237831
|
+
}
|
|
237832
|
+
}
|
|
237833
|
+
if (wraps && d === 0) return evaluate(expr.slice(1, -1).trim(), vars, sb);
|
|
237834
|
+
}
|
|
237767
237835
|
if (/^-?\d+(\.\d+)?$/.test(expr)) return expr;
|
|
237768
237836
|
if (expr.startsWith('"') && expr.endsWith('"') || expr.startsWith("'") && expr.endsWith("'")) {
|
|
237769
237837
|
return expr.slice(1, -1);
|
|
@@ -237771,11 +237839,54 @@ function evaluate(expr, vars, sandbox) {
|
|
|
237771
237839
|
if (expr === "true") return "true";
|
|
237772
237840
|
if (expr === "false") return "false";
|
|
237773
237841
|
if (expr === "null" || expr === "undefined") return "";
|
|
237774
|
-
|
|
237775
|
-
|
|
237776
|
-
|
|
237777
|
-
|
|
237842
|
+
{
|
|
237843
|
+
let depth = 0;
|
|
237844
|
+
let pending2 = 0;
|
|
237845
|
+
let q = -1;
|
|
237846
|
+
let quote = "";
|
|
237847
|
+
for (let i = 0; i < expr.length; i++) {
|
|
237848
|
+
const ch = expr[i];
|
|
237849
|
+
if (quote) {
|
|
237850
|
+
if (ch === "\\") {
|
|
237851
|
+
i++;
|
|
237852
|
+
continue;
|
|
237853
|
+
}
|
|
237854
|
+
if (ch === quote) quote = "";
|
|
237855
|
+
continue;
|
|
237856
|
+
}
|
|
237857
|
+
if (ch === "'" || ch === '"' || ch === "`") {
|
|
237858
|
+
quote = ch;
|
|
237859
|
+
continue;
|
|
237860
|
+
}
|
|
237861
|
+
if (ch === "(" || ch === "[" || ch === "{") {
|
|
237862
|
+
depth++;
|
|
237863
|
+
continue;
|
|
237864
|
+
}
|
|
237865
|
+
if (ch === ")" || ch === "]" || ch === "}") {
|
|
237866
|
+
depth--;
|
|
237867
|
+
continue;
|
|
237868
|
+
}
|
|
237869
|
+
if (depth !== 0) continue;
|
|
237870
|
+
if (ch === "?") {
|
|
237871
|
+
if (pending2 === 0) q = i;
|
|
237872
|
+
pending2++;
|
|
237873
|
+
continue;
|
|
237874
|
+
}
|
|
237875
|
+
if (ch === ":" && pending2 > 0) {
|
|
237876
|
+
pending2--;
|
|
237877
|
+
if (pending2 > 0) continue;
|
|
237878
|
+
const cond = expr.slice(0, q).trim();
|
|
237879
|
+
const cons = expr.slice(q + 1, i).trim();
|
|
237880
|
+
const alt = expr.slice(i + 1).trim();
|
|
237881
|
+
if (cond && cons && alt) {
|
|
237882
|
+
return isTruthy(resolve2(cond, vars, sb)) ? resolve2(cons, vars, sb) : resolve2(alt, vars, sb);
|
|
237883
|
+
}
|
|
237884
|
+
break;
|
|
237885
|
+
}
|
|
237886
|
+
}
|
|
237778
237887
|
}
|
|
237888
|
+
const dyn = evalWithVars(expr, vars);
|
|
237889
|
+
if (dyn !== null) return dyn;
|
|
237779
237890
|
const orParts = splitLogical(expr, "||");
|
|
237780
237891
|
if (orParts.length > 1) {
|
|
237781
237892
|
for (const part of orParts) {
|
|
@@ -237852,7 +237963,7 @@ function evaluate(expr, vars, sandbox) {
|
|
|
237852
237963
|
return current2 !== void 0 && current2 !== null ? String(current2) : "";
|
|
237853
237964
|
}
|
|
237854
237965
|
const fnMatch = expr.match(/^(\w+)\((.*)\)$/);
|
|
237855
|
-
if (fnMatch) {
|
|
237966
|
+
if (fnMatch && isBuiltinName(fnMatch[1])) {
|
|
237856
237967
|
const fn = fnMatch[1];
|
|
237857
237968
|
const arg = resolve2(fnMatch[2].trim(), vars, sb);
|
|
237858
237969
|
return callBuiltin(fn, arg, sb);
|
|
@@ -237871,6 +237982,38 @@ function evaluate(expr, vars, sandbox) {
|
|
|
237871
237982
|
}
|
|
237872
237983
|
return expr;
|
|
237873
237984
|
}
|
|
237985
|
+
function evalWithVars(expr, vars) {
|
|
237986
|
+
if (!expr || /[`\\]/.test(expr)) return null;
|
|
237987
|
+
if (!/^[\w\s.$[\]{}()'"`,+\-*/%?:<>=!&|]*$/.test(expr)) return null;
|
|
237988
|
+
for (const m of expr.matchAll(/\.([A-Za-z_$][\w$]*)\s*\(/g)) {
|
|
237989
|
+
if (!SAFE_MEMBERS.has(m[1])) return null;
|
|
237990
|
+
}
|
|
237991
|
+
const names = [];
|
|
237992
|
+
const vals = [];
|
|
237993
|
+
for (const k of Object.keys(vars)) {
|
|
237994
|
+
if (!/^[A-Za-z_$][\w$]*$/.test(k)) continue;
|
|
237995
|
+
if (k in extraBindings) continue;
|
|
237996
|
+
names.push(k);
|
|
237997
|
+
try {
|
|
237998
|
+
vals.push(JSON.parse(vars[k]));
|
|
237999
|
+
} catch {
|
|
238000
|
+
vals.push(vars[k]);
|
|
238001
|
+
}
|
|
238002
|
+
}
|
|
238003
|
+
for (const k of Object.keys(extraBindings)) {
|
|
238004
|
+
if (!/^[A-Za-z_$][\w$]*$/.test(k)) continue;
|
|
238005
|
+
names.push(k);
|
|
238006
|
+
vals.push(extraBindings[k]);
|
|
238007
|
+
}
|
|
238008
|
+
try {
|
|
238009
|
+
const fn = new Function(...names, `"use strict"; return (${expr});`);
|
|
238010
|
+
const out = fn(...vals);
|
|
238011
|
+
if (out === void 0 || out === null) return null;
|
|
238012
|
+
return typeof out === "object" ? JSON.stringify(out) : String(out);
|
|
238013
|
+
} catch {
|
|
238014
|
+
return null;
|
|
238015
|
+
}
|
|
238016
|
+
}
|
|
237874
238017
|
function resolve2(token, vars, sandbox) {
|
|
237875
238018
|
const trimmed = token.trim();
|
|
237876
238019
|
if (!trimmed) return "";
|
|
@@ -238039,7 +238182,7 @@ function coerce(value, targetType) {
|
|
|
238039
238182
|
return value;
|
|
238040
238183
|
}
|
|
238041
238184
|
}
|
|
238042
|
-
var defaultSandbox;
|
|
238185
|
+
var defaultSandbox, extraBindings, SAFE_MEMBERS;
|
|
238043
238186
|
var init_evaluate = __esm({
|
|
238044
238187
|
"../../packages/compiler/tw/eval/evaluate.ts"() {
|
|
238045
238188
|
init_sandbox();
|
|
@@ -238047,6 +238190,64 @@ var init_evaluate = __esm({
|
|
|
238047
238190
|
init_builtins();
|
|
238048
238191
|
init_interpolate();
|
|
238049
238192
|
defaultSandbox = new Sandbox();
|
|
238193
|
+
extraBindings = {};
|
|
238194
|
+
SAFE_MEMBERS = /* @__PURE__ */ new Set([
|
|
238195
|
+
"length",
|
|
238196
|
+
"toUpperCase",
|
|
238197
|
+
"toLowerCase",
|
|
238198
|
+
"trim",
|
|
238199
|
+
"trimStart",
|
|
238200
|
+
"trimEnd",
|
|
238201
|
+
"slice",
|
|
238202
|
+
"substring",
|
|
238203
|
+
"substr",
|
|
238204
|
+
"charAt",
|
|
238205
|
+
"at",
|
|
238206
|
+
"includes",
|
|
238207
|
+
"indexOf",
|
|
238208
|
+
"lastIndexOf",
|
|
238209
|
+
"startsWith",
|
|
238210
|
+
"endsWith",
|
|
238211
|
+
"replace",
|
|
238212
|
+
"replaceAll",
|
|
238213
|
+
"split",
|
|
238214
|
+
"join",
|
|
238215
|
+
"concat",
|
|
238216
|
+
"repeat",
|
|
238217
|
+
"padStart",
|
|
238218
|
+
"padEnd",
|
|
238219
|
+
"toString",
|
|
238220
|
+
"toFixed",
|
|
238221
|
+
"map",
|
|
238222
|
+
"filter",
|
|
238223
|
+
"find",
|
|
238224
|
+
"some",
|
|
238225
|
+
"every",
|
|
238226
|
+
"reduce",
|
|
238227
|
+
"sort",
|
|
238228
|
+
"reverse",
|
|
238229
|
+
// Static helpers on the built-ins (JSON.stringify, Object.keys, Math.round).
|
|
238230
|
+
"stringify",
|
|
238231
|
+
"parse",
|
|
238232
|
+
"keys",
|
|
238233
|
+
"values",
|
|
238234
|
+
"entries",
|
|
238235
|
+
"from",
|
|
238236
|
+
"isArray",
|
|
238237
|
+
"isInteger",
|
|
238238
|
+
"isFinite",
|
|
238239
|
+
"abs",
|
|
238240
|
+
"ceil",
|
|
238241
|
+
"floor",
|
|
238242
|
+
"round",
|
|
238243
|
+
"max",
|
|
238244
|
+
"min",
|
|
238245
|
+
"pow",
|
|
238246
|
+
"sqrt",
|
|
238247
|
+
"random",
|
|
238248
|
+
"now",
|
|
238249
|
+
"toISOString"
|
|
238250
|
+
]);
|
|
238050
238251
|
}
|
|
238051
238252
|
});
|
|
238052
238253
|
|
|
@@ -238201,9 +238402,78 @@ function callBuiltin(fn, arg, sandbox) {
|
|
|
238201
238402
|
function applyFilter(value, filter, args, vars, sandbox) {
|
|
238202
238403
|
return callBuiltin(filter, value, sandbox);
|
|
238203
238404
|
}
|
|
238405
|
+
function isBuiltinName(fn) {
|
|
238406
|
+
return BUILTIN_NAMES.has(fn);
|
|
238407
|
+
}
|
|
238408
|
+
var BUILTIN_NAMES;
|
|
238204
238409
|
var init_builtins = __esm({
|
|
238205
238410
|
"../../packages/compiler/tw/eval/builtins.ts"() {
|
|
238206
238411
|
init_evaluate();
|
|
238412
|
+
BUILTIN_NAMES = /* @__PURE__ */ new Set([
|
|
238413
|
+
"Boolean",
|
|
238414
|
+
"Number",
|
|
238415
|
+
"String",
|
|
238416
|
+
"abs",
|
|
238417
|
+
"base64decode",
|
|
238418
|
+
"base64encode",
|
|
238419
|
+
"bool",
|
|
238420
|
+
"camelCase",
|
|
238421
|
+
"capitalize",
|
|
238422
|
+
"ceil",
|
|
238423
|
+
"cos",
|
|
238424
|
+
"date",
|
|
238425
|
+
"decodeURI",
|
|
238426
|
+
"encodeURI",
|
|
238427
|
+
"endsWith",
|
|
238428
|
+
"exp",
|
|
238429
|
+
"first",
|
|
238430
|
+
"float",
|
|
238431
|
+
"floor",
|
|
238432
|
+
"includes",
|
|
238433
|
+
"indexOf",
|
|
238434
|
+
"int",
|
|
238435
|
+
"join",
|
|
238436
|
+
"json",
|
|
238437
|
+
"kebabCase",
|
|
238438
|
+
"last",
|
|
238439
|
+
"len",
|
|
238440
|
+
"length",
|
|
238441
|
+
"log",
|
|
238442
|
+
"lower",
|
|
238443
|
+
"lowercase",
|
|
238444
|
+
"match",
|
|
238445
|
+
"max",
|
|
238446
|
+
"min",
|
|
238447
|
+
"now",
|
|
238448
|
+
"num",
|
|
238449
|
+
"padEnd",
|
|
238450
|
+
"padStart",
|
|
238451
|
+
"parseFloat",
|
|
238452
|
+
"parseInt",
|
|
238453
|
+
"parseJSON",
|
|
238454
|
+
"pow",
|
|
238455
|
+
"random",
|
|
238456
|
+
"repeat",
|
|
238457
|
+
"replace",
|
|
238458
|
+
"reverse",
|
|
238459
|
+
"round",
|
|
238460
|
+
"sin",
|
|
238461
|
+
"size",
|
|
238462
|
+
"slice",
|
|
238463
|
+
"snakeCase",
|
|
238464
|
+
"split",
|
|
238465
|
+
"sqrt",
|
|
238466
|
+
"startsWith",
|
|
238467
|
+
"str",
|
|
238468
|
+
"stringify",
|
|
238469
|
+
"tan",
|
|
238470
|
+
"test",
|
|
238471
|
+
"time",
|
|
238472
|
+
"title",
|
|
238473
|
+
"trim",
|
|
238474
|
+
"upper",
|
|
238475
|
+
"uppercase"
|
|
238476
|
+
]);
|
|
238207
238477
|
}
|
|
238208
238478
|
});
|
|
238209
238479
|
|
|
@@ -239252,16 +239522,61 @@ function addScope(css, scopeId) {
|
|
|
239252
239522
|
out += prelude;
|
|
239253
239523
|
return out;
|
|
239254
239524
|
}
|
|
239255
|
-
function
|
|
239256
|
-
|
|
239257
|
-
|
|
239258
|
-
|
|
239525
|
+
function interpolateBraces(input, vars) {
|
|
239526
|
+
let out = "";
|
|
239527
|
+
let i = 0;
|
|
239528
|
+
while (i < input.length) {
|
|
239529
|
+
const ch = input[i];
|
|
239530
|
+
if (ch !== "{") {
|
|
239531
|
+
out += ch;
|
|
239532
|
+
i++;
|
|
239533
|
+
continue;
|
|
239534
|
+
}
|
|
239535
|
+
let depth = 1;
|
|
239536
|
+
let j = i + 1;
|
|
239537
|
+
let quote = "";
|
|
239538
|
+
while (j < input.length && depth > 0) {
|
|
239539
|
+
const c = input[j];
|
|
239540
|
+
if (quote) {
|
|
239541
|
+
if (c === "\\") {
|
|
239542
|
+
j += 2;
|
|
239543
|
+
continue;
|
|
239544
|
+
}
|
|
239545
|
+
if (c === quote) quote = "";
|
|
239546
|
+
j++;
|
|
239547
|
+
continue;
|
|
239548
|
+
}
|
|
239549
|
+
if (c === "'" || c === '"' || c === "`") {
|
|
239550
|
+
quote = c;
|
|
239551
|
+
j++;
|
|
239552
|
+
continue;
|
|
239553
|
+
}
|
|
239554
|
+
if (c === "{") depth++;
|
|
239555
|
+
else if (c === "}") {
|
|
239556
|
+
depth--;
|
|
239557
|
+
if (depth === 0) break;
|
|
239558
|
+
}
|
|
239559
|
+
j++;
|
|
239560
|
+
}
|
|
239561
|
+
if (depth !== 0) {
|
|
239562
|
+
out += ch;
|
|
239563
|
+
i++;
|
|
239564
|
+
continue;
|
|
239565
|
+
}
|
|
239566
|
+
const key2 = input.slice(i + 1, j).trim();
|
|
239259
239567
|
if (/^[a-zA-Z_$][\w$]*$/.test(key2)) {
|
|
239260
|
-
|
|
239568
|
+
out += vars[key2] ?? "";
|
|
239569
|
+
} else {
|
|
239570
|
+
const val = evaluate(key2, vars);
|
|
239571
|
+
out += val === null ? "" : val;
|
|
239261
239572
|
}
|
|
239262
|
-
|
|
239263
|
-
|
|
239264
|
-
|
|
239573
|
+
i = j + 1;
|
|
239574
|
+
}
|
|
239575
|
+
return out;
|
|
239576
|
+
}
|
|
239577
|
+
function interpolate2(expr, vars) {
|
|
239578
|
+
const masked = expr.replace(/\\\{/g, "O").replace(/\\\}/g, "C");
|
|
239579
|
+
const out = interpolateBraces(masked, vars);
|
|
239265
239580
|
return out.replace(/\u0001O/g, "{").replace(/\u0001C/g, "}");
|
|
239266
239581
|
}
|
|
239267
239582
|
function isTruthy2(val) {
|
|
@@ -239740,7 +240055,21 @@ function renderBindings(bindings, ctx) {
|
|
|
239740
240055
|
if (bindings.length === 0) return "";
|
|
239741
240056
|
const parts = [];
|
|
239742
240057
|
for (const binding of bindings) {
|
|
239743
|
-
|
|
240058
|
+
const expr = String(binding.expression ?? "");
|
|
240059
|
+
let initial = "";
|
|
240060
|
+
try {
|
|
240061
|
+
if (/^[A-Za-z_$][\w$]*$/.test(expr)) {
|
|
240062
|
+
const v = ctx.stateVars[expr];
|
|
240063
|
+
if (v !== void 0 && v !== null) initial = String(v);
|
|
240064
|
+
} else if (expr) {
|
|
240065
|
+
initial = interpolate2("{" + expr + "}", ctx.stateVars);
|
|
240066
|
+
}
|
|
240067
|
+
} catch {
|
|
240068
|
+
}
|
|
240069
|
+
if (initial && !/[{}]/.test(initial)) {
|
|
240070
|
+
parts.push(`${binding.property}="${escapeAttr3(initial)}"`);
|
|
240071
|
+
}
|
|
240072
|
+
parts.push(`data-tw-bind-${binding.property}="${escapeAttr3(expr)}"`);
|
|
239744
240073
|
}
|
|
239745
240074
|
return parts.join(" ");
|
|
239746
240075
|
}
|
|
@@ -239855,8 +240184,13 @@ function generateComponent(comp, ctx) {
|
|
|
239855
240184
|
const savedStateVars = { ...ctx.stateVars };
|
|
239856
240185
|
for (const prop of comp.props) {
|
|
239857
240186
|
if (typeof prop.value === "string") {
|
|
239858
|
-
const
|
|
239859
|
-
|
|
240187
|
+
const raw = String(prop.value);
|
|
240188
|
+
const head = raw.split(/[.[]/)[0];
|
|
240189
|
+
if (!/[{}]/.test(raw) && Object.prototype.hasOwnProperty.call(ctx.stateVars, head)) {
|
|
240190
|
+
ctx.stateVars[prop.name] = interpolate2("{" + raw + "}", ctx.stateVars);
|
|
240191
|
+
} else {
|
|
240192
|
+
ctx.stateVars[prop.name] = interpolate2(raw, ctx.stateVars);
|
|
240193
|
+
}
|
|
239860
240194
|
}
|
|
239861
240195
|
}
|
|
239862
240196
|
const componentProgram = componentRegistry.get(comp.name);
|
|
@@ -239950,6 +240284,14 @@ function generateFor(node, ctx) {
|
|
|
239950
240284
|
}
|
|
239951
240285
|
}
|
|
239952
240286
|
}
|
|
240287
|
+
if (items.length === 0 && !/^\s*\[/.test(iterableStr)) {
|
|
240288
|
+
try {
|
|
240289
|
+
const evaluated = evaluate(String(node.iterable ?? "").replace(/^\{/, "").replace(/\}$/, "").trim(), ctx.stateVars);
|
|
240290
|
+
const parsed = JSON.parse(evaluated);
|
|
240291
|
+
if (Array.isArray(parsed)) items = parsed;
|
|
240292
|
+
} catch {
|
|
240293
|
+
}
|
|
240294
|
+
}
|
|
239953
240295
|
for (let i = 0; i < items.length; i++) {
|
|
239954
240296
|
const childCtx = { ...ctx };
|
|
239955
240297
|
childCtx.stateVars = { ...ctx.stateVars };
|
|
@@ -243756,16 +244098,22 @@ function generateWithLayoutChain(layoutPrograms, pageProgram, stateVars) {
|
|
|
243756
244098
|
}
|
|
243757
244099
|
};
|
|
243758
244100
|
let pageTitle = "TW Page";
|
|
244101
|
+
let pageDescription = "";
|
|
243759
244102
|
for (const dir of pageProgram.directives || []) {
|
|
243760
|
-
if (dir.type === "PageDirective"
|
|
243761
|
-
|
|
243762
|
-
|
|
244103
|
+
if (dir.type === "PageDirective") {
|
|
244104
|
+
if (dir.key === "title") {
|
|
244105
|
+
const t3 = dir.value;
|
|
244106
|
+
if (t3 != null && String(t3) !== "") pageTitle = String(t3);
|
|
244107
|
+
} else if (dir.key === "description") {
|
|
244108
|
+
const d = dir.value;
|
|
244109
|
+
if (d != null) pageDescription = String(d);
|
|
244110
|
+
}
|
|
243763
244111
|
}
|
|
243764
244112
|
}
|
|
243765
244113
|
const pageCtx = createContext("ssr");
|
|
243766
244114
|
seedCtx(pageCtx);
|
|
243767
244115
|
try {
|
|
243768
|
-
pageCtx.stateVars["page"] = JSON.stringify({ title: pageTitle });
|
|
244116
|
+
pageCtx.stateVars["page"] = JSON.stringify({ title: pageTitle, description: pageDescription });
|
|
243769
244117
|
} catch {
|
|
243770
244118
|
}
|
|
243771
244119
|
{
|
|
@@ -243921,6 +244269,10 @@ function generate(program, stateVars, genOpts2) {
|
|
|
243921
244269
|
const startTime = performance.now();
|
|
243922
244270
|
const ctx = createContext("ssr");
|
|
243923
244271
|
if (stateVars) ctx.stateVars = stateVars;
|
|
244272
|
+
if (genOpts2?.imports) {
|
|
244273
|
+
ctx.imports = genOpts2.imports;
|
|
244274
|
+
setEvalBindings(genOpts2.imports);
|
|
244275
|
+
} else setEvalBindings({});
|
|
243924
244276
|
if (genOpts2 && genOpts2.scopedStyles === false) ctx.scopedStyles = false;
|
|
243925
244277
|
if (genOpts2?.scopeId) ctx.scopeId = genOpts2.scopeId;
|
|
243926
244278
|
if (genOpts2?.cssPrefix) ctx.cssPrefix = genOpts2.cssPrefix;
|
|
@@ -244005,6 +244357,7 @@ var init_vdom = __esm({
|
|
|
244005
244357
|
init_types4();
|
|
244006
244358
|
init_html2();
|
|
244007
244359
|
init_builtin_components();
|
|
244360
|
+
init_evaluate();
|
|
244008
244361
|
}
|
|
244009
244362
|
});
|
|
244010
244363
|
|
|
@@ -244345,6 +244698,7 @@ var init_codes = __esm({
|
|
|
244345
244698
|
TW095: { message: "Invalid for-loop iterable -- use {varName} or a valid comma-separated literal", severity: "error", category: "syntax" },
|
|
244346
244699
|
TW096: { message: "Unknown state variable in interpolation (renders empty)", severity: "warning", category: "semantic" },
|
|
244347
244700
|
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" },
|
|
244701
|
+
TW098: { message: "Interpolation cannot be evaluated at build time -- it renders as its source text", severity: "warning", category: "semantic" },
|
|
244348
244702
|
// Server blocks (TW099-TW101)
|
|
244349
244703
|
TW099: { message: "Unknown server modifier (only ':defer' is supported)", severity: "error", category: "syntax" },
|
|
244350
244704
|
TW100: { message: "Expected '{' after 'server'", severity: "error", category: "syntax" },
|
|
@@ -244432,6 +244786,7 @@ function checkUnknownTags(program, filePath, diags) {
|
|
|
244432
244786
|
forEachNode(program, (node, ctx) => {
|
|
244433
244787
|
if (!isElement(node)) return;
|
|
244434
244788
|
if (node.tag[0] === node.tag[0]?.toUpperCase()) return;
|
|
244789
|
+
if (BUILTIN_COMPONENT_TAGS.has(node.tag)) return;
|
|
244435
244790
|
const parent = ctx?.parent;
|
|
244436
244791
|
if (parent && (parent.type === "Element" && typeof parent.tag === "string" && parent.tag[0] === parent.tag[0]?.toUpperCase() || parent.type === "Component")) return;
|
|
244437
244792
|
if (HTML_ELEMENTS2.has(node.tag.toLowerCase())) return;
|
|
@@ -244786,6 +245141,59 @@ function checkUnknownStateVars(program, filePath, diags) {
|
|
|
244786
245141
|
"null",
|
|
244787
245142
|
"undefined"
|
|
244788
245143
|
]);
|
|
245144
|
+
const EVALUABLE_METHODS = /* @__PURE__ */ new Set([
|
|
245145
|
+
"length",
|
|
245146
|
+
"toUpperCase",
|
|
245147
|
+
"toLowerCase",
|
|
245148
|
+
"trim",
|
|
245149
|
+
"trimStart",
|
|
245150
|
+
"trimEnd",
|
|
245151
|
+
"slice",
|
|
245152
|
+
"substring",
|
|
245153
|
+
"substr",
|
|
245154
|
+
"charAt",
|
|
245155
|
+
"at",
|
|
245156
|
+
"includes",
|
|
245157
|
+
"indexOf",
|
|
245158
|
+
"lastIndexOf",
|
|
245159
|
+
"startsWith",
|
|
245160
|
+
"endsWith",
|
|
245161
|
+
"replace",
|
|
245162
|
+
"replaceAll",
|
|
245163
|
+
"split",
|
|
245164
|
+
"join",
|
|
245165
|
+
"concat",
|
|
245166
|
+
"repeat",
|
|
245167
|
+
"padStart",
|
|
245168
|
+
"padEnd",
|
|
245169
|
+
"toString",
|
|
245170
|
+
"toFixed",
|
|
245171
|
+
"map",
|
|
245172
|
+
"filter",
|
|
245173
|
+
"find",
|
|
245174
|
+
"some",
|
|
245175
|
+
"every",
|
|
245176
|
+
"reduce",
|
|
245177
|
+
"sort",
|
|
245178
|
+
"reverse",
|
|
245179
|
+
"stringify",
|
|
245180
|
+
"parse",
|
|
245181
|
+
"keys",
|
|
245182
|
+
"values",
|
|
245183
|
+
"entries",
|
|
245184
|
+
"from",
|
|
245185
|
+
"isArray",
|
|
245186
|
+
"round",
|
|
245187
|
+
"floor",
|
|
245188
|
+
"ceil",
|
|
245189
|
+
"abs",
|
|
245190
|
+
"max",
|
|
245191
|
+
"min",
|
|
245192
|
+
"pow",
|
|
245193
|
+
"sqrt",
|
|
245194
|
+
"random",
|
|
245195
|
+
"now"
|
|
245196
|
+
]);
|
|
244789
245197
|
for (const node of texts) {
|
|
244790
245198
|
const text = String(node.value ?? "");
|
|
244791
245199
|
if (!text.includes("{")) continue;
|
|
@@ -244801,6 +245209,22 @@ function checkUnknownStateVars(program, filePath, diags) {
|
|
|
244801
245209
|
[`Declare it in the state block, or check the spelling`]
|
|
244802
245210
|
));
|
|
244803
245211
|
}
|
|
245212
|
+
for (const m of text.matchAll(/\{([^{}]+)\}/g)) {
|
|
245213
|
+
const expr = m[1].trim();
|
|
245214
|
+
if (/^[A-Za-z_$][\w$]*$/.test(expr)) continue;
|
|
245215
|
+
for (const c of expr.matchAll(/([A-Za-z_$][\w$]*)\s*\(/g)) {
|
|
245216
|
+
const fn = c[1];
|
|
245217
|
+
if (known.has(fn) || BUILTINS2.has(fn) || EVALUABLE_METHODS.has(fn)) continue;
|
|
245218
|
+
diags.push(createDiagnostic(
|
|
245219
|
+
"TW098",
|
|
245220
|
+
node.line,
|
|
245221
|
+
node.col,
|
|
245222
|
+
filePath,
|
|
245223
|
+
`{${expr}}`,
|
|
245224
|
+
[`Cannot evaluate this at build time -- it renders as its source. Check the name, or simplify the expression`]
|
|
245225
|
+
));
|
|
245226
|
+
}
|
|
245227
|
+
}
|
|
244804
245228
|
}
|
|
244805
245229
|
}
|
|
244806
245230
|
function checkWhileComparisons(program, filePath, diags) {
|
|
@@ -244833,7 +245257,7 @@ function checkDeprecatedImports(program, filePath, diags) {
|
|
|
244833
245257
|
}
|
|
244834
245258
|
}
|
|
244835
245259
|
}
|
|
244836
|
-
var HTML_ELEMENTS2, HTML_ATTRIBUTES2, GLOBAL_ATTRIBUTES2, CSS_PROPERTIES2, CSS_ALIASES2, EVENT_TYPES2, ARIA_ROLES2, ARIA_ATTRIBUTES2, DEPRECATED_IMPORTS;
|
|
245260
|
+
var HTML_ELEMENTS2, HTML_ATTRIBUTES2, GLOBAL_ATTRIBUTES2, CSS_PROPERTIES2, CSS_ALIASES2, EVENT_TYPES2, ARIA_ROLES2, ARIA_ATTRIBUTES2, BUILTIN_COMPONENT_TAGS, DEPRECATED_IMPORTS;
|
|
244837
245261
|
var init_rules = __esm({
|
|
244838
245262
|
"../../packages/compiler/tw/diagnostics/rules.ts"() {
|
|
244839
245263
|
init_visitors();
|
|
@@ -245697,6 +246121,7 @@ var init_rules = __esm({
|
|
|
245697
246121
|
"aria-valuenow",
|
|
245698
246122
|
"aria-valuetext"
|
|
245699
246123
|
]);
|
|
246124
|
+
BUILTIN_COMPONENT_TAGS = /* @__PURE__ */ new Set(["optImage", "twImage", "twLink", "twImg"]);
|
|
245700
246125
|
DEPRECATED_IMPORTS = [
|
|
245701
246126
|
{ source: "utils/string/transform", name: "toHex", replacement: "stringToHex", removal: "2.1.0" },
|
|
245702
246127
|
{ source: "utils/string/transform", name: "fromHex", replacement: "hexToString", removal: "2.1.0" },
|
|
@@ -256009,7 +256434,8 @@ function genOpts(opts, scopeId) {
|
|
|
256009
256434
|
scopedStyles: opts?.compiler?.scopedStyles,
|
|
256010
256435
|
scopeId,
|
|
256011
256436
|
cssPrefix: opts?.cssPrefix,
|
|
256012
|
-
cssImportPaths: opts?.cssImportPaths
|
|
256437
|
+
cssImportPaths: opts?.cssImportPaths,
|
|
256438
|
+
imports: opts?.imports
|
|
256013
256439
|
};
|
|
256014
256440
|
}
|
|
256015
256441
|
function scopeIdFor(filePath) {
|
|
@@ -256283,7 +256709,7 @@ __export(twm_rules_exports, {
|
|
|
256283
256709
|
isRuleDsl: () => isRuleDsl,
|
|
256284
256710
|
parseRules: () => parseRules
|
|
256285
256711
|
});
|
|
256286
|
-
import { createHmac, timingSafeEqual as
|
|
256712
|
+
import { createHmac as createHmac2, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
|
|
256287
256713
|
function isRuleDsl(source) {
|
|
256288
256714
|
const stripped = source.split("\n").map((l) => l.replace(/\/\/.*$/, "").trim()).filter((l) => l.length > 0).join("\n");
|
|
256289
256715
|
return stripped.startsWith('rule "') || stripped.startsWith("rule '");
|
|
@@ -256381,14 +256807,14 @@ function verifyJwt_hs256(token, secret) {
|
|
|
256381
256807
|
return false;
|
|
256382
256808
|
}
|
|
256383
256809
|
if (header?.alg !== "HS256") return false;
|
|
256384
|
-
const expected =
|
|
256810
|
+
const expected = createHmac2("sha256", secret).update(`${h3}.${p}`).digest();
|
|
256385
256811
|
let got;
|
|
256386
256812
|
try {
|
|
256387
256813
|
got = b64urlDecode(sig);
|
|
256388
256814
|
} catch {
|
|
256389
256815
|
return false;
|
|
256390
256816
|
}
|
|
256391
|
-
if (expected.length !== got.length || !
|
|
256817
|
+
if (expected.length !== got.length || !timingSafeEqual2(expected, got)) return false;
|
|
256392
256818
|
if (typeof payload?.exp === "number" && payload.exp * 1e3 < Date.now()) return false;
|
|
256393
256819
|
return true;
|
|
256394
256820
|
}
|
|
@@ -256764,6 +257190,32 @@ var init_twm_rules = __esm({
|
|
|
256764
257190
|
});
|
|
256765
257191
|
|
|
256766
257192
|
// ../../packages/runtime/tw/dependency-graph.ts
|
|
257193
|
+
function flushPendingSubs() {
|
|
257194
|
+
if (isFlushing || !pendingSubs) return;
|
|
257195
|
+
isFlushing = true;
|
|
257196
|
+
try {
|
|
257197
|
+
let guard = 0;
|
|
257198
|
+
while (pendingSubs && pendingSubs.size > 0 && guard++ < 1e5) {
|
|
257199
|
+
const batchOf = pendingSubs;
|
|
257200
|
+
pendingSubs = /* @__PURE__ */ new Set();
|
|
257201
|
+
for (const sub of batchOf) {
|
|
257202
|
+
try {
|
|
257203
|
+
sub();
|
|
257204
|
+
} catch (e) {
|
|
257205
|
+
console.error("[TW Reactivity] Signal subscriber error:", e);
|
|
257206
|
+
}
|
|
257207
|
+
}
|
|
257208
|
+
}
|
|
257209
|
+
} finally {
|
|
257210
|
+
isFlushing = false;
|
|
257211
|
+
pendingSubs = null;
|
|
257212
|
+
}
|
|
257213
|
+
}
|
|
257214
|
+
function enqueueSubs(subs) {
|
|
257215
|
+
if (!pendingSubs) pendingSubs = /* @__PURE__ */ new Set();
|
|
257216
|
+
for (const s of subs) pendingSubs.add(s);
|
|
257217
|
+
if (batchDepth === 0) flushPendingSubs();
|
|
257218
|
+
}
|
|
256767
257219
|
function startBatch() {
|
|
256768
257220
|
batchDepth++;
|
|
256769
257221
|
}
|
|
@@ -256773,13 +257225,7 @@ function endBatch() {
|
|
|
256773
257225
|
batchDepth = 0;
|
|
256774
257226
|
const effects = Array.from(batchedEffects);
|
|
256775
257227
|
batchedEffects.clear();
|
|
256776
|
-
|
|
256777
|
-
try {
|
|
256778
|
-
eff();
|
|
256779
|
-
} catch (e) {
|
|
256780
|
-
console.error("[TW Reactivity] Batch effect error:", e);
|
|
256781
|
-
}
|
|
256782
|
-
}
|
|
257228
|
+
enqueueSubs(effects);
|
|
256783
257229
|
}
|
|
256784
257230
|
}
|
|
256785
257231
|
function batch(fn) {
|
|
@@ -256961,12 +257407,14 @@ function isComputed(value) {
|
|
|
256961
257407
|
function createScope3() {
|
|
256962
257408
|
return new ReactiveScope();
|
|
256963
257409
|
}
|
|
256964
|
-
var currentEffect, batchDepth, batchedEffects, SignalImpl, ComputedImpl, watchEffect, currentCleanup, ReactiveScope;
|
|
257410
|
+
var currentEffect, batchDepth, batchedEffects, pendingSubs, isFlushing, SignalImpl, ComputedImpl, watchEffect, currentCleanup, ReactiveScope;
|
|
256965
257411
|
var init_dependency_graph = __esm({
|
|
256966
257412
|
"../../packages/runtime/tw/dependency-graph.ts"() {
|
|
256967
257413
|
currentEffect = null;
|
|
256968
257414
|
batchDepth = 0;
|
|
256969
257415
|
batchedEffects = /* @__PURE__ */ new Set();
|
|
257416
|
+
pendingSubs = null;
|
|
257417
|
+
isFlushing = false;
|
|
256970
257418
|
SignalImpl = class {
|
|
256971
257419
|
value;
|
|
256972
257420
|
subscribers = /* @__PURE__ */ new Set();
|
|
@@ -256985,19 +257433,9 @@ var init_dependency_graph = __esm({
|
|
|
256985
257433
|
if (this.equals(this.value, value)) return;
|
|
256986
257434
|
this.value = value;
|
|
256987
257435
|
if (batchDepth > 0) {
|
|
256988
|
-
for (const sub of this.subscribers)
|
|
256989
|
-
batchedEffects.add(sub);
|
|
256990
|
-
}
|
|
256991
|
-
} else {
|
|
256992
|
-
const subs = Array.from(this.subscribers);
|
|
256993
|
-
for (const sub of subs) {
|
|
256994
|
-
try {
|
|
256995
|
-
sub();
|
|
256996
|
-
} catch (e) {
|
|
256997
|
-
console.error("[TW Reactivity] Signal subscriber error:", e);
|
|
256998
|
-
}
|
|
256999
|
-
}
|
|
257436
|
+
for (const sub of this.subscribers) batchedEffects.add(sub);
|
|
257000
257437
|
}
|
|
257438
|
+
enqueueSubs(this.subscribers);
|
|
257001
257439
|
}
|
|
257002
257440
|
update(fn) {
|
|
257003
257441
|
this.set(fn(this.value));
|
|
@@ -257063,14 +257501,11 @@ var init_dependency_graph = __esm({
|
|
|
257063
257501
|
return this.cachedValue;
|
|
257064
257502
|
}
|
|
257065
257503
|
notify() {
|
|
257066
|
-
|
|
257067
|
-
|
|
257068
|
-
|
|
257069
|
-
sub();
|
|
257070
|
-
} catch (e) {
|
|
257071
|
-
console.error("[TW Reactivity] Computed subscriber error:", e);
|
|
257072
|
-
}
|
|
257504
|
+
if (batchDepth > 0) {
|
|
257505
|
+
for (const sub of this.subscribers) batchedEffects.add(sub);
|
|
257506
|
+
return;
|
|
257073
257507
|
}
|
|
257508
|
+
enqueueSubs(this.subscribers);
|
|
257074
257509
|
}
|
|
257075
257510
|
set(_value) {
|
|
257076
257511
|
throw new Error("Cannot set a computed signal");
|
|
@@ -260255,10 +260690,12 @@ var init_diff = __esm({
|
|
|
260255
260690
|
"../../packages/runtime/tw/diff.ts"() {
|
|
260256
260691
|
init_vdom2();
|
|
260257
260692
|
HTML_ESCAPE_MAP = {
|
|
260258
|
-
|
|
260259
|
-
|
|
260260
|
-
"
|
|
260261
|
-
|
|
260693
|
+
// These four mapped to THEMSELVES, so a <script> tag passed through
|
|
260694
|
+
// untouched -- only ' was actually escaped.
|
|
260695
|
+
"&": "&",
|
|
260696
|
+
"<": "<",
|
|
260697
|
+
">": ">",
|
|
260698
|
+
'"': """,
|
|
260262
260699
|
"'": "'"
|
|
260263
260700
|
};
|
|
260264
260701
|
HTML_ESCAPE_REGEX = /[&<>"']/g;
|
|
@@ -263788,8 +264225,19 @@ async function hydrateElementById(id, component, root = typeof document !== "und
|
|
|
263788
264225
|
);
|
|
263789
264226
|
}
|
|
263790
264227
|
function markForHydration(element, componentId, props) {
|
|
264228
|
+
if (!element || typeof componentId !== "string") return;
|
|
263791
264229
|
element.setAttribute(HYDRATE_ATTR, componentId);
|
|
263792
|
-
|
|
264230
|
+
let serialised = null;
|
|
264231
|
+
if (props && typeof props === "object") {
|
|
264232
|
+
try {
|
|
264233
|
+
serialised = JSON.stringify(props);
|
|
264234
|
+
} catch {
|
|
264235
|
+
serialised = null;
|
|
264236
|
+
}
|
|
264237
|
+
}
|
|
264238
|
+
if (serialised !== null && serialised !== void 0) {
|
|
264239
|
+
element.setAttribute("data-tw-props", serialised);
|
|
264240
|
+
}
|
|
263793
264241
|
}
|
|
263794
264242
|
function unmarkForHydration(element) {
|
|
263795
264243
|
element.removeAttribute(HYDRATE_ATTR);
|
|
@@ -267089,15 +267537,21 @@ function usePathname(opts) {
|
|
|
267089
267537
|
function matchPathPattern(path, pattern) {
|
|
267090
267538
|
const pSeg = path.split("/").filter(Boolean);
|
|
267091
267539
|
const tSeg = pattern.split("/").filter(Boolean);
|
|
267092
|
-
|
|
267540
|
+
const catchAllAt = tSeg.findIndex((t3) => t3.startsWith("[...") || t3.startsWith(":..."));
|
|
267541
|
+
if (catchAllAt === -1) {
|
|
267542
|
+
if (pSeg.length !== tSeg.length) return { matches: false, params: {}, score: 0 };
|
|
267543
|
+
} else if (pSeg.length < tSeg.length) {
|
|
267544
|
+
return { matches: false, params: {}, score: 0 };
|
|
267545
|
+
}
|
|
267093
267546
|
const params = {};
|
|
267094
267547
|
let literal = 0;
|
|
267095
267548
|
for (let i = 0; i < tSeg.length; i++) {
|
|
267096
267549
|
const t3 = tSeg[i];
|
|
267097
267550
|
const isParam = t3.startsWith("[") && t3.endsWith("]") || t3.startsWith(":");
|
|
267098
267551
|
if (isParam) {
|
|
267099
|
-
const name = t3.startsWith("[") ? t3.slice(1, -1).replace(
|
|
267100
|
-
|
|
267552
|
+
const name = t3.startsWith("[") ? t3.slice(1, -1).replace(/^\.\.\./, "") : t3.slice(1);
|
|
267553
|
+
const isCatchAll = t3.startsWith("[...") || t3.startsWith(":...");
|
|
267554
|
+
params[name] = isCatchAll ? pSeg.slice(i).map((seg) => decodeURIComponent(seg)).join("/") : decodeURIComponent(pSeg[i]);
|
|
267101
267555
|
} else if (t3 !== pSeg[i]) {
|
|
267102
267556
|
return { matches: false, params: {}, score: 0 };
|
|
267103
267557
|
} else {
|
|
@@ -267912,7 +268366,11 @@ function defineStore(idOrDefinition, maybeDefinition) {
|
|
|
267912
268366
|
const getterDeps = Object.values(reactiveState);
|
|
267913
268367
|
for (const key2 of Object.keys(getters)) {
|
|
267914
268368
|
const getter = getters[key2];
|
|
267915
|
-
getterValues[key2] = computed4(getterDeps,
|
|
268369
|
+
getterValues[key2] = computed4(getterDeps, () => {
|
|
268370
|
+
const snap = {};
|
|
268371
|
+
for (const k of Object.keys(reactiveState)) snap[k] = reactiveState[k].value;
|
|
268372
|
+
return getter.call(snap, snap);
|
|
268373
|
+
});
|
|
267916
268374
|
}
|
|
267917
268375
|
const subscribers = /* @__PURE__ */ new Set();
|
|
267918
268376
|
const actionListeners = /* @__PURE__ */ new Set();
|
|
@@ -267955,7 +268413,7 @@ function defineStore(idOrDefinition, maybeDefinition) {
|
|
|
267955
268413
|
const fn = actions[key2];
|
|
267956
268414
|
boundActions[key2] = (...args) => {
|
|
267957
268415
|
actionListeners.forEach((cb) => cb({ name: key2, args }));
|
|
267958
|
-
return fn.call(
|
|
268416
|
+
return fn.call(stateProxy, stateProxy, ...args);
|
|
267959
268417
|
};
|
|
267960
268418
|
}
|
|
267961
268419
|
for (const rv of Object.values(reactiveState)) {
|
|
@@ -268067,9 +268525,10 @@ var init_store = __esm({
|
|
|
268067
268525
|
};
|
|
268068
268526
|
}
|
|
268069
268527
|
_notify() {
|
|
268528
|
+
const current2 = this.value ?? this._value;
|
|
268070
268529
|
for (const l of this._listeners) {
|
|
268071
268530
|
try {
|
|
268072
|
-
l();
|
|
268531
|
+
l(current2);
|
|
268073
268532
|
} catch {
|
|
268074
268533
|
}
|
|
268075
268534
|
}
|
|
@@ -269075,7 +269534,8 @@ var init_virtual_list = __esm({
|
|
|
269075
269534
|
return this.container.scrollTop;
|
|
269076
269535
|
}
|
|
269077
269536
|
getFirstVisibleIndex() {
|
|
269078
|
-
|
|
269537
|
+
const last = Math.max(0, this.options?.itemCount ?? 0 - 1);
|
|
269538
|
+
return Math.min(Math.max(this.visibleRange.start, 0), last);
|
|
269079
269539
|
}
|
|
269080
269540
|
getLastVisibleIndex() {
|
|
269081
269541
|
return this.visibleRange.end;
|
|
@@ -271924,6 +272384,12 @@ var init_xss_detector = __esm({
|
|
|
271924
272384
|
{ name: "SQL comment", regex: /--\s/g, severity: "medium", recommendation: "Check for SQL comment injection" },
|
|
271925
272385
|
{ name: "Stacked queries", regex: /;\s*(SELECT|INSERT|UPDATE|DELETE|DROP|UNION)/gi, severity: "high", recommendation: "Prevent stacked queries" },
|
|
271926
272386
|
{ name: "UNION injection", regex: /UNION\s+SELECT/gi, severity: "critical", recommendation: "Filter UNION SELECT from user input" },
|
|
272387
|
+
// Comment-only injections (`admin'--`, `admin'#`, `admin'/*`): a quote
|
|
272388
|
+
// followed by a comment truncates the query and bypasses the login
|
|
272389
|
+
// check, with no OR/UNION for the other patterns to catch.
|
|
272390
|
+
{ name: "Comment-terminated injection", regex: /['"`]\s*(--|#|\/\*)/g, severity: "high", recommendation: "A quote followed by a comment truncates the query -- use parameterized queries" },
|
|
272391
|
+
// Parenthesised boolean injection: `') OR ('1'='1`.
|
|
272392
|
+
{ 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" },
|
|
271927
272393
|
{ name: "Boolean-based injection", regex: /OR\s+1\s*=\s*1/gi, severity: "high", recommendation: "Use parameterized queries to prevent boolean injection" },
|
|
271928
272394
|
{ name: "Time-based injection", regex: /WAITFOR\s+DELAY/gi, severity: "high", recommendation: "Filter WAITFOR DELAY from user input" },
|
|
271929
272395
|
{ name: "DROP statement", regex: /DROP\s+(TABLE|DATABASE)/gi, severity: "critical", recommendation: "Prevent DROP statements in queries" },
|
|
@@ -272454,6 +272920,7 @@ var init_logger4 = __esm({
|
|
|
272454
272920
|
}
|
|
272455
272921
|
/** Records an audit event. */
|
|
272456
272922
|
log(event) {
|
|
272923
|
+
event = { type: "system", actor: "unknown", resource: "unknown", ...event };
|
|
272457
272924
|
const fullEvent = {
|
|
272458
272925
|
...event,
|
|
272459
272926
|
id: crypto.randomUUID(),
|
|
@@ -273470,6 +273937,13 @@ var FILE_SIGNATURES, DANGEROUS_EXTENSIONS, DANGEROUS_MIME_TYPES, ContentSecurity
|
|
|
273470
273937
|
var init_protector = __esm({
|
|
273471
273938
|
"../../packages/security/tw/content-security/protector.ts"() {
|
|
273472
273939
|
FILE_SIGNATURES = [
|
|
273940
|
+
// Executables and HTML: without these, content renamed to an image extension
|
|
273941
|
+
// had no detected type, the signature check was skipped, and the upload was
|
|
273942
|
+
// accepted as risk "low" -- the exact rename bypass the check exists for.
|
|
273943
|
+
{ ext: ["exe", "dll", "sys"], mime: "application/x-msdownload", signature: [77, 90] },
|
|
273944
|
+
{ ext: ["elf", "so", "bin"], mime: "application/x-executable", signature: [127, 69, 76, 70] },
|
|
273945
|
+
{ ext: ["html", "htm", "xhtml"], mime: "text/html", signature: [60, 104, 116, 109, 108] },
|
|
273946
|
+
{ ext: ["html", "htm"], mime: "text/html", signature: [60, 33, 68, 79, 67, 84, 89, 80, 69] },
|
|
273473
273947
|
{ ext: ["jpg", "jpeg"], mime: "image/jpeg", signature: [255, 216, 255] },
|
|
273474
273948
|
{ ext: ["png"], mime: "image/png", signature: [137, 80, 78, 71, 13, 10, 26, 10] },
|
|
273475
273949
|
{ ext: ["gif"], mime: "image/gif", signature: [71, 73, 70, 56] },
|
|
@@ -273521,10 +273995,39 @@ var init_protector = __esm({
|
|
|
273521
273995
|
".htpasswd",
|
|
273522
273996
|
".svg",
|
|
273523
273997
|
// Can contain JavaScript
|
|
273524
|
-
".xml"
|
|
273998
|
+
".xml",
|
|
273525
273999
|
// Can contain XXE
|
|
274000
|
+
// Active-content types the set used to let through while still blocking .svg
|
|
274001
|
+
// "because it can contain JavaScript" -- .html/.js are script-capable by
|
|
274002
|
+
// definition, so an accepted upload served from our own origin is stored XSS.
|
|
274003
|
+
".html",
|
|
274004
|
+
".htm",
|
|
274005
|
+
".xhtml",
|
|
274006
|
+
".shtml",
|
|
274007
|
+
".js",
|
|
274008
|
+
".mjs",
|
|
274009
|
+
".cjs",
|
|
274010
|
+
".jsx",
|
|
274011
|
+
".ts",
|
|
274012
|
+
".tsx",
|
|
274013
|
+
".mhtml",
|
|
274014
|
+
".hta",
|
|
274015
|
+
".jsp",
|
|
274016
|
+
".asp",
|
|
274017
|
+
".aspx",
|
|
274018
|
+
".cgi",
|
|
274019
|
+
".pl"
|
|
273526
274020
|
]);
|
|
273527
274021
|
DANGEROUS_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
274022
|
+
// Active-content types: they can carry script (svg, xhtml, xml) or be
|
|
274023
|
+
// rendered as a document (html, pdf). Blocking .svg by extension while
|
|
274024
|
+
// reporting isContentTypeSafe("image/svg+xml") === true was inconsistent.
|
|
274025
|
+
"image/svg+xml",
|
|
274026
|
+
"text/html",
|
|
274027
|
+
"application/xhtml+xml",
|
|
274028
|
+
"application/pdf",
|
|
274029
|
+
"text/xml",
|
|
274030
|
+
"application/xml",
|
|
273528
274031
|
"application/javascript",
|
|
273529
274032
|
"text/javascript",
|
|
273530
274033
|
"application/x-javascript",
|
|
@@ -273579,7 +274082,9 @@ var init_protector = __esm({
|
|
|
273579
274082
|
reason: "Empty file",
|
|
273580
274083
|
sanitizedFilename: sanitized,
|
|
273581
274084
|
detectedType: "unknown",
|
|
273582
|
-
risk
|
|
274085
|
+
// Unknown content is not low risk -- it is content whose type we could
|
|
274086
|
+
// not establish, which is what a rename bypass looks like.
|
|
274087
|
+
risk: "medium"
|
|
273583
274088
|
};
|
|
273584
274089
|
}
|
|
273585
274090
|
if (this.blockedExtensions.has(ext)) {
|
|
@@ -274742,6 +275247,7 @@ var init_nonce_manager = __esm({
|
|
|
274742
275247
|
validate(nonce) {
|
|
274743
275248
|
const entry = this.nonces.get(nonce);
|
|
274744
275249
|
if (!entry) return false;
|
|
275250
|
+
if (entry.used) return false;
|
|
274745
275251
|
if (Date.now() > entry.expiresAt) {
|
|
274746
275252
|
this.nonces.delete(nonce);
|
|
274747
275253
|
return false;
|
|
@@ -275534,7 +276040,15 @@ var init_token_manager = __esm({
|
|
|
275534
276040
|
rotateAfterUse;
|
|
275535
276041
|
maxTokens;
|
|
275536
276042
|
tokens = /* @__PURE__ */ new Map();
|
|
276043
|
+
/** Tokens this manager issued and already consumed. A rotated token is
|
|
276044
|
+
* deleted from `tokens`, so without this marker a replay found no entry and
|
|
276045
|
+
* fell through to the stateless branch, which accepted it -- defeating the
|
|
276046
|
+
* documented single-use guarantee of `rotateAfterUse`. */
|
|
276047
|
+
consumedTokens = /* @__PURE__ */ new Set();
|
|
275537
276048
|
constructor(options) {
|
|
276049
|
+
if (!options || typeof options.secret !== "string" || options.secret.length === 0) {
|
|
276050
|
+
throw new Error("createCSRFManager: options.secret is required (the HMAC key used to sign tokens)");
|
|
276051
|
+
}
|
|
275538
276052
|
this.secret = options.secret;
|
|
275539
276053
|
this.tokenLength = options.tokenLength ?? 32;
|
|
275540
276054
|
this.ttl = options.ttl ?? 36e5;
|
|
@@ -275634,12 +276148,16 @@ var init_token_manager = __esm({
|
|
|
275634
276148
|
}
|
|
275635
276149
|
entry.used = true;
|
|
275636
276150
|
if (this.rotateAfterUse) {
|
|
276151
|
+
this.consumedTokens.add(headerPart);
|
|
275637
276152
|
this.tokens.delete(headerPart);
|
|
275638
276153
|
const rotated = await this.generate(sessionId ?? void 0);
|
|
275639
276154
|
return { valid: true, rotated };
|
|
275640
276155
|
}
|
|
275641
276156
|
return { valid: true };
|
|
275642
276157
|
}
|
|
276158
|
+
if (this.consumedTokens.has(headerPart)) {
|
|
276159
|
+
return { valid: false, reason: "Token already used" };
|
|
276160
|
+
}
|
|
275643
276161
|
const expectedSig = await hmacSign3(
|
|
275644
276162
|
this.secret,
|
|
275645
276163
|
`${headerPart}.${sessionId ?? ""}`
|
|
@@ -281709,6 +282227,26 @@ var init_render_pipeline = __esm({
|
|
|
281709
282227
|
};
|
|
281710
282228
|
}
|
|
281711
282229
|
console.error(`[tw] server block failed for ${pageFile}: ${err2?.message ?? err2}`);
|
|
282230
|
+
try {
|
|
282231
|
+
const errFile = ctx.error ?? findRootError(tree);
|
|
282232
|
+
if (errFile) {
|
|
282233
|
+
const compiled = this.compileFile(errFile);
|
|
282234
|
+
let eh = String(compiled.html || "");
|
|
282235
|
+
const em = eh.match(/<body[^>]*>([\s\S]*)<\/body>/i);
|
|
282236
|
+
eh = (em ? em[1] : eh).replace(/<\/?html[^>]*>/gi, "").replace(/<\/?body[^>]*>/gi, "");
|
|
282237
|
+
return {
|
|
282238
|
+
html: this.assembleHTML(eh, compiled.css, "", "Error"),
|
|
282239
|
+
css: "",
|
|
282240
|
+
js: "",
|
|
282241
|
+
status: 500,
|
|
282242
|
+
headers: { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "private, no-store" },
|
|
282243
|
+
fromCache: false,
|
|
282244
|
+
durationMs: 0,
|
|
282245
|
+
serverBlocks: true
|
|
282246
|
+
};
|
|
282247
|
+
}
|
|
282248
|
+
} catch {
|
|
282249
|
+
}
|
|
281712
282250
|
return {
|
|
281713
282251
|
html: "<!DOCTYPE html><html><head><title>Server Error</title></head><body><h1>Internal Server Error</h1></body></html>",
|
|
281714
282252
|
css: "",
|
|
@@ -281774,7 +282312,7 @@ var init_render_pipeline = __esm({
|
|
|
281774
282312
|
}
|
|
281775
282313
|
const match = matchRoute2(tree, pathname);
|
|
281776
282314
|
if (!match) {
|
|
281777
|
-
const rootNotFound =
|
|
282315
|
+
const rootNotFound = findNearestNotFound(tree, pathname.split("/").filter(Boolean));
|
|
281778
282316
|
if (rootNotFound) {
|
|
281779
282317
|
const compiled = this.compileFile(rootNotFound);
|
|
281780
282318
|
let nf = String(compiled.html || "");
|
|
@@ -281840,6 +282378,7 @@ var init_render_pipeline = __esm({
|
|
|
281840
282378
|
}
|
|
281841
282379
|
let bodyHTML = pageCompiled.html;
|
|
281842
282380
|
let allCSS = pageCompiled.css;
|
|
282381
|
+
const parallelSlots = [];
|
|
281843
282382
|
try {
|
|
281844
282383
|
const parentNode = match?.node;
|
|
281845
282384
|
if (parentNode && Array.isArray(parentNode.children)) {
|
|
@@ -281850,9 +282389,7 @@ var init_render_pipeline = __esm({
|
|
|
281850
282389
|
const sbm = slotBody.match(/<body[^>]*>([\s\S]*)<\/body>/i);
|
|
281851
282390
|
if (sbm) slotBody = sbm[1];
|
|
281852
282391
|
slotBody = slotBody.replace(/<\/?html[^>]*>/gi, "").replace(/<\/?body[^>]*>/gi, "");
|
|
281853
|
-
|
|
281854
|
-
const slotRe = new RegExp(`<${comp}(s[^>]*)?>([sS]*?)</${comp}>|<${comp}(s[^>]*)?/>`, "gi");
|
|
281855
|
-
bodyHTML = bodyHTML.replace(slotRe, slotBody);
|
|
282392
|
+
parallelSlots.push({ comp: slotName.charAt(0).toUpperCase() + slotName.slice(1), body: slotBody });
|
|
281856
282393
|
if (slotCompiled.css) allCSS = slotCompiled.css + "\n" + allCSS;
|
|
281857
282394
|
}
|
|
281858
282395
|
}
|
|
@@ -281871,6 +282408,11 @@ var init_render_pipeline = __esm({
|
|
|
281871
282408
|
allJS = templateCompiled.js + "\n" + allJS;
|
|
281872
282409
|
bodyHTML = this.wrapInLayout(templateCompiled.html, bodyHTML);
|
|
281873
282410
|
}
|
|
282411
|
+
for (const { comp, body } of parallelSlots) {
|
|
282412
|
+
const slotRe = new RegExp(`<${comp}(\\s[^>]*)?>([\\s\\S]*?)<\\/${comp}>|<${comp}(\\s[^>]*)?/>`, "gi");
|
|
282413
|
+
bodyHTML = bodyHTML.replace(slotRe, body);
|
|
282414
|
+
bodyHTML = bodyHTML.replace(new RegExp(`<!--\\s*TW: unknown component '${comp}'[\\s\\S]*?-->`, "gi"), body);
|
|
282415
|
+
}
|
|
281874
282416
|
let layoutTitle;
|
|
281875
282417
|
const liftedParts = [];
|
|
281876
282418
|
{
|
|
@@ -282289,8 +282831,6 @@ ${bodyHTML}`;
|
|
|
282289
282831
|
};
|
|
282290
282832
|
let tags = "";
|
|
282291
282833
|
const description = pick2("description");
|
|
282292
|
-
if (description) tags += ` <meta name="description" content="${esc4(description)}">
|
|
282293
|
-
`;
|
|
282294
282834
|
const keywords = pick2("keywords");
|
|
282295
282835
|
if (keywords) tags += ` <meta name="keywords" content="${esc4(keywords)}">
|
|
282296
282836
|
`;
|
|
@@ -284472,6 +285012,7 @@ var init_static = __esm({
|
|
|
284472
285012
|
if (!existsSync15(filePath)) {
|
|
284473
285013
|
filePath = join19(this.root, safePath + ".html");
|
|
284474
285014
|
}
|
|
285015
|
+
let notFoundFallback = false;
|
|
284475
285016
|
if (!existsSync15(filePath)) {
|
|
284476
285017
|
filePath = join19(this.root, safePath, this.indexFile);
|
|
284477
285018
|
}
|
|
@@ -284483,6 +285024,7 @@ var init_static = __esm({
|
|
|
284483
285024
|
}
|
|
284484
285025
|
} else {
|
|
284485
285026
|
filePath = join19(this.root, "404.html");
|
|
285027
|
+
notFoundFallback = true;
|
|
284486
285028
|
if (!existsSync15(filePath)) {
|
|
284487
285029
|
return new Response("Not Found", { status: 404, headers: { "Content-Type": "text/plain" } });
|
|
284488
285030
|
}
|
|
@@ -284550,12 +285092,12 @@ var init_static = __esm({
|
|
|
284550
285092
|
const gzHeaders = { ...headers, "Content-Encoding": "gzip", "Content-Length": String(gz.length), Vary: "Accept-Encoding" };
|
|
284551
285093
|
delete gzHeaders["Content-Length"];
|
|
284552
285094
|
if (this.lastModified) delete gzHeaders["Last-Modified"];
|
|
284553
|
-
return new Response(gz, { headers: gzHeaders });
|
|
285095
|
+
return new Response(gz, { headers: gzHeaders, status: notFoundFallback ? 404 : 200 });
|
|
284554
285096
|
}
|
|
284555
285097
|
} catch {
|
|
284556
285098
|
}
|
|
284557
285099
|
}
|
|
284558
|
-
return new Response(bytes, { headers });
|
|
285100
|
+
return new Response(bytes, { headers, status: notFoundFallback ? 404 : 200 });
|
|
284559
285101
|
}
|
|
284560
285102
|
/**
|
|
284561
285103
|
* Parse a `Range: bytes=...` header against a known content length.
|
|
@@ -285879,17 +286421,17 @@ async function hs256(payload, secret, maxAge) {
|
|
|
285879
286421
|
const header = { alg: "HS256", typ: "JWT" };
|
|
285880
286422
|
const body = { ...payload, iat: now, exp: now + maxAge };
|
|
285881
286423
|
const data = enc(header) + "." + enc(body);
|
|
285882
|
-
const { createHmac:
|
|
285883
|
-
const sig =
|
|
286424
|
+
const { createHmac: createHmac3 } = await import("node:crypto");
|
|
286425
|
+
const sig = createHmac3("sha256", secret).update(data).digest("base64url");
|
|
285884
286426
|
return data + "." + sig;
|
|
285885
286427
|
}
|
|
285886
286428
|
async function verifyHs256(token, secret) {
|
|
285887
286429
|
const parts = token.split(".");
|
|
285888
286430
|
if (parts.length !== 3) return null;
|
|
285889
|
-
const { createHmac:
|
|
285890
|
-
const expected =
|
|
286431
|
+
const { createHmac: createHmac3, timingSafeEqual: timingSafeEqual3 } = await import("node:crypto");
|
|
286432
|
+
const expected = createHmac3("sha256", secret).update(parts[0] + "." + parts[1]).digest("base64url");
|
|
285891
286433
|
const a = Buffer.from(expected), b = Buffer.from(parts[2]);
|
|
285892
|
-
if (a.length !== b.length || !
|
|
286434
|
+
if (a.length !== b.length || !timingSafeEqual3(a, b)) return null;
|
|
285893
286435
|
try {
|
|
285894
286436
|
const body = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8"));
|
|
285895
286437
|
if (typeof body.exp === "number" && body.exp < Math.floor(Date.now() / 1e3)) return null;
|
|
@@ -287194,6 +287736,19 @@ ${err2.stack ?? ""}`);
|
|
|
287194
287736
|
const isrLookupPath = pathname.length > 1 ? pathname.replace(/\/+$/, "") : pathname;
|
|
287195
287737
|
if (this.staticHandler && !this.isrRoutes.has(isrLookupPath)) {
|
|
287196
287738
|
let staticResponse = await this.staticHandler.serve(pathname, request);
|
|
287739
|
+
if (staticResponse.status !== 404) {
|
|
287740
|
+
const reqMethod = request.method.toUpperCase();
|
|
287741
|
+
const allow = "GET, HEAD, OPTIONS";
|
|
287742
|
+
if (reqMethod === "TRACE") {
|
|
287743
|
+
return new Response("Method Not Allowed", { status: 405, headers: { Allow: allow } });
|
|
287744
|
+
}
|
|
287745
|
+
if (reqMethod === "OPTIONS") {
|
|
287746
|
+
return new Response(null, { status: 204, headers: { Allow: allow } });
|
|
287747
|
+
}
|
|
287748
|
+
if (reqMethod !== "GET" && reqMethod !== "HEAD") {
|
|
287749
|
+
return new Response("Method Not Allowed", { status: 405, headers: { Allow: allow } });
|
|
287750
|
+
}
|
|
287751
|
+
}
|
|
287197
287752
|
if (staticResponse && this.cspEnabled() && (staticResponse.headers.get("content-type") || "").includes("text/html")) {
|
|
287198
287753
|
try {
|
|
287199
287754
|
let buf = Buffer.from(await staticResponse.arrayBuffer());
|