tw-framework 1.0.6 → 1.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/hydration-runtime.js +86 -8
- package/dist/testing.cjs +7793 -572
- package/dist/tw-server.mjs +56209 -0
- package/dist/tw.mjs +2188 -834
- package/package.json +7 -2
package/dist/tw.mjs
CHANGED
|
@@ -3,7 +3,9 @@ globalThis.__TW_BUNDLE_DIR = new URL(".", import.meta.url).pathname;
|
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
const require = createRequire(import.meta.url);
|
|
5
5
|
var __defProp = Object.defineProperty;
|
|
6
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
7
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
9
|
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
8
10
|
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
9
11
|
}) : x)(function(x) {
|
|
@@ -17,6 +19,15 @@ var __export = (target, all) => {
|
|
|
17
19
|
for (var name in all)
|
|
18
20
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
19
21
|
};
|
|
22
|
+
var __copyProps = (to, from, except, desc) => {
|
|
23
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
24
|
+
for (let key of __getOwnPropNames(from))
|
|
25
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
26
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
27
|
+
}
|
|
28
|
+
return to;
|
|
29
|
+
};
|
|
30
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
31
|
|
|
21
32
|
// ../../packages/shared/tw/hashing/md5.ts
|
|
22
33
|
function bytesToWords(bytes) {
|
|
@@ -2555,6 +2566,97 @@ var init_config = __esm({
|
|
|
2555
2566
|
}
|
|
2556
2567
|
});
|
|
2557
2568
|
|
|
2569
|
+
// ../../packages/shared/tw/source-mask.ts
|
|
2570
|
+
function blank(s) {
|
|
2571
|
+
return s.replace(/[^\n]/g, " ");
|
|
2572
|
+
}
|
|
2573
|
+
function maskSourceStringsAndComments(src) {
|
|
2574
|
+
let out = "";
|
|
2575
|
+
let i = 0;
|
|
2576
|
+
const n = src.length;
|
|
2577
|
+
while (i < n) {
|
|
2578
|
+
const ch = src[i];
|
|
2579
|
+
const next = src[i + 1];
|
|
2580
|
+
if (ch === "/" && next === "/") {
|
|
2581
|
+
let j = i;
|
|
2582
|
+
while (j < n && src[j] !== "\n") j++;
|
|
2583
|
+
out += blank(src.slice(i, j));
|
|
2584
|
+
i = j;
|
|
2585
|
+
continue;
|
|
2586
|
+
}
|
|
2587
|
+
if (ch === "/" && next === "*") {
|
|
2588
|
+
let j = i + 2;
|
|
2589
|
+
while (j < n && !(src[j] === "*" && src[j + 1] === "/")) j++;
|
|
2590
|
+
j = Math.min(j + 2, n);
|
|
2591
|
+
out += blank(src.slice(i, j));
|
|
2592
|
+
i = j;
|
|
2593
|
+
continue;
|
|
2594
|
+
}
|
|
2595
|
+
if (ch === '"' || ch === "'" || ch === "`") {
|
|
2596
|
+
let j = i + 1;
|
|
2597
|
+
while (j < n) {
|
|
2598
|
+
const c = src[j];
|
|
2599
|
+
if (c === "\\") {
|
|
2600
|
+
j += 2;
|
|
2601
|
+
continue;
|
|
2602
|
+
}
|
|
2603
|
+
if (c === ch) {
|
|
2604
|
+
j++;
|
|
2605
|
+
break;
|
|
2606
|
+
}
|
|
2607
|
+
j++;
|
|
2608
|
+
}
|
|
2609
|
+
out += blank(src.slice(i, j));
|
|
2610
|
+
i = j;
|
|
2611
|
+
continue;
|
|
2612
|
+
}
|
|
2613
|
+
out += ch;
|
|
2614
|
+
i++;
|
|
2615
|
+
}
|
|
2616
|
+
return out;
|
|
2617
|
+
}
|
|
2618
|
+
function stripCommentsStringAware(src) {
|
|
2619
|
+
let out = "";
|
|
2620
|
+
let i = 0;
|
|
2621
|
+
const n = src.length;
|
|
2622
|
+
while (i < n) {
|
|
2623
|
+
const c = src[i];
|
|
2624
|
+
if (c === '"' || c === "'") {
|
|
2625
|
+
const q = c;
|
|
2626
|
+
out += c;
|
|
2627
|
+
i++;
|
|
2628
|
+
while (i < n && src[i] !== q) {
|
|
2629
|
+
if (src[i] === "\\") {
|
|
2630
|
+
out += src[i++];
|
|
2631
|
+
if (i < n) out += src[i++];
|
|
2632
|
+
continue;
|
|
2633
|
+
}
|
|
2634
|
+
out += src[i++];
|
|
2635
|
+
}
|
|
2636
|
+
out += src[i] ?? "";
|
|
2637
|
+
i++;
|
|
2638
|
+
continue;
|
|
2639
|
+
}
|
|
2640
|
+
if (c === "/" && src[i + 1] === "/") {
|
|
2641
|
+
while (i < n && src[i] !== "\n") i++;
|
|
2642
|
+
continue;
|
|
2643
|
+
}
|
|
2644
|
+
if (c === "/" && src[i + 1] === "*") {
|
|
2645
|
+
const end = src.indexOf("*/", i + 2);
|
|
2646
|
+
i = end === -1 ? n : end + 2;
|
|
2647
|
+
out += " ";
|
|
2648
|
+
continue;
|
|
2649
|
+
}
|
|
2650
|
+
out += c;
|
|
2651
|
+
i++;
|
|
2652
|
+
}
|
|
2653
|
+
return out;
|
|
2654
|
+
}
|
|
2655
|
+
var init_source_mask = __esm({
|
|
2656
|
+
"../../packages/shared/tw/source-mask.ts"() {
|
|
2657
|
+
}
|
|
2658
|
+
});
|
|
2659
|
+
|
|
2558
2660
|
// ../../packages/shared/tw/cache.ts
|
|
2559
2661
|
function parseCacheBody(body) {
|
|
2560
2662
|
const meta = {};
|
|
@@ -2577,9 +2679,12 @@ function parseCacheBody(body) {
|
|
|
2577
2679
|
return meta;
|
|
2578
2680
|
}
|
|
2579
2681
|
function extractCacheDirective(source) {
|
|
2580
|
-
const
|
|
2682
|
+
const masked = maskSourceStringsAndComments(source);
|
|
2683
|
+
const m = /page\s*\{[^}]*?\bcache\s*\{([^}]*)\}/.exec(masked);
|
|
2581
2684
|
if (!m) return null;
|
|
2582
|
-
|
|
2685
|
+
const bodyStart = m.index + m[0].length - m[1].length - 1;
|
|
2686
|
+
const bodyEnd = bodyStart + m[1].length;
|
|
2687
|
+
return parseCacheBody(source.slice(bodyStart, bodyEnd));
|
|
2583
2688
|
}
|
|
2584
2689
|
function cacheMetaIsUsable(meta) {
|
|
2585
2690
|
if (!meta) return false;
|
|
@@ -2711,6 +2816,7 @@ function parseCacheProfilesSource(src) {
|
|
|
2711
2816
|
var BUILTIN_CACHE_PROFILES;
|
|
2712
2817
|
var init_cache = __esm({
|
|
2713
2818
|
"../../packages/shared/tw/cache.ts"() {
|
|
2819
|
+
init_source_mask();
|
|
2714
2820
|
BUILTIN_CACHE_PROFILES = {
|
|
2715
2821
|
seconds: { stale: 1, revalidate: 1, expire: 2 },
|
|
2716
2822
|
minutes: { stale: 60, revalidate: 300, expire: 3600 },
|
|
@@ -6653,6 +6759,7 @@ __export(tw_exports, {
|
|
|
6653
6759
|
maskPhone: () => maskPhone,
|
|
6654
6760
|
maskSSN: () => maskSSN,
|
|
6655
6761
|
maskSensitiveData: () => maskSensitiveData,
|
|
6762
|
+
maskSourceStringsAndComments: () => maskSourceStringsAndComments,
|
|
6656
6763
|
md5: () => md5,
|
|
6657
6764
|
md5Async: () => md52,
|
|
6658
6765
|
mergeConfig: () => mergeConfig,
|
|
@@ -6992,6 +7099,7 @@ __export(tw_exports, {
|
|
|
6992
7099
|
shortHash: () => shortHash,
|
|
6993
7100
|
signData: () => signData,
|
|
6994
7101
|
statSync: () => statSync,
|
|
7102
|
+
stripCommentsStringAware: () => stripCommentsStringAware,
|
|
6995
7103
|
supportsColor: () => supportsColor,
|
|
6996
7104
|
supportsFileSystem: () => supportsFileSystem,
|
|
6997
7105
|
supportsWebSocket: () => supportsWebSocket,
|
|
@@ -7072,6 +7180,7 @@ var init_tw = __esm({
|
|
|
7072
7180
|
init_utils();
|
|
7073
7181
|
init_config();
|
|
7074
7182
|
init_cache();
|
|
7183
|
+
init_source_mask();
|
|
7075
7184
|
init_constants();
|
|
7076
7185
|
init_crypto();
|
|
7077
7186
|
init_env();
|
|
@@ -9518,6 +9627,9 @@ function readEscape(source, start) {
|
|
|
9518
9627
|
if (LINE_TERMINATORS.has(ch)) {
|
|
9519
9628
|
return { value: "", end: start + 2 };
|
|
9520
9629
|
}
|
|
9630
|
+
if (ch === "{" || ch === "}") {
|
|
9631
|
+
return { value: "\\" + ch, end: start + 2 };
|
|
9632
|
+
}
|
|
9521
9633
|
return { value: ch, end: start + 2 };
|
|
9522
9634
|
}
|
|
9523
9635
|
function validateString(str) {
|
|
@@ -12848,6 +12960,7 @@ function parsePageDirective(cursor, token) {
|
|
|
12848
12960
|
if (valToken && (valToken.type === "STRING" || valToken.type === "NUMBER")) {
|
|
12849
12961
|
cursor.advance();
|
|
12850
12962
|
value = valToken.value;
|
|
12963
|
+
if (valToken.type === "STRING") value = String(value).replace(/\\([{}"])/g, "$1");
|
|
12851
12964
|
if (valToken.type === "NUMBER") value = parseFloat(valToken.value);
|
|
12852
12965
|
} else if (cursor.match("EQUALS") || cursor.match("COLON")) {
|
|
12853
12966
|
cursor.skipWhitespace();
|
|
@@ -12855,6 +12968,7 @@ function parsePageDirective(cursor, token) {
|
|
|
12855
12968
|
if (vToken) {
|
|
12856
12969
|
cursor.advance();
|
|
12857
12970
|
value = vToken.value;
|
|
12971
|
+
if (vToken.type === "STRING") value = String(value).replace(/\\([{}"])/g, "$1");
|
|
12858
12972
|
if (vToken.type === "NUMBER") value = parseFloat(vToken.value);
|
|
12859
12973
|
if (vToken.type === "BOOLEAN") value = vToken.value === "true";
|
|
12860
12974
|
}
|
|
@@ -12878,7 +12992,7 @@ function parsePageDirective(cursor, token) {
|
|
|
12878
12992
|
const nextToken = cursor.peek();
|
|
12879
12993
|
if (nextToken && (nextToken.type === "STRING" || nextToken.type === "NUMBER" || nextToken.type === "IDENT" || nextToken.type === "KEYWORD")) {
|
|
12880
12994
|
cursor.advance();
|
|
12881
|
-
options[optName] = nextToken.value;
|
|
12995
|
+
options[optName] = nextToken.type === "STRING" ? String(nextToken.value).replace(/\\([{}"])/g, "$1") : nextToken.value;
|
|
12882
12996
|
} else if (cursor.match("EQUALS")) {
|
|
12883
12997
|
cursor.skipWhitespace();
|
|
12884
12998
|
const optVal = cursor.consumeValue();
|
|
@@ -13258,8 +13372,16 @@ function consumeSignalDeclaration(cursor) {
|
|
|
13258
13372
|
function consumeStateValue(cursor) {
|
|
13259
13373
|
const t2 = cursor.peek();
|
|
13260
13374
|
if (!t2) return "";
|
|
13375
|
+
if (t2.value === "-" || t2.value === "+") {
|
|
13376
|
+
const after = cursor.peek(1);
|
|
13377
|
+
if (after && (after.type === "NUMBER" || after.type === "IDENT" || after.type === "KEYWORD")) {
|
|
13378
|
+
cursor.advance();
|
|
13379
|
+
return (t2.value === "-" ? "-" : "") + String(after.value);
|
|
13380
|
+
}
|
|
13381
|
+
}
|
|
13261
13382
|
if (t2.type === "STRING" || t2.type === "NUMBER" || t2.type === "IDENT" || t2.type === "KEYWORD") {
|
|
13262
13383
|
cursor.advance();
|
|
13384
|
+
if (t2.type === "STRING") return String(t2.value).replace(/\\([{}"])/g, "$1");
|
|
13263
13385
|
return t2.value;
|
|
13264
13386
|
}
|
|
13265
13387
|
if (t2.type === "LBRACKET" || t2.type === "LBRACE" || t2.type === "LBRACK") {
|
|
@@ -13272,7 +13394,7 @@ function consumeStateValue(cursor) {
|
|
|
13272
13394
|
else if (tk.type === "RBRACKET" || tk.type === "RBRACE" || tk.type === "RBRACK") depth--;
|
|
13273
13395
|
const after = cursor.peek(1);
|
|
13274
13396
|
if (tk.type === "STRING") {
|
|
13275
|
-
parts.push(`"${tk.value}"`);
|
|
13397
|
+
parts.push(`"${String(tk.value).replace(/\\([{}"])/g, "$1")}"`);
|
|
13276
13398
|
} else if (tk.type === "IDENT" && after && after.type === "COLON" && depth > 0) {
|
|
13277
13399
|
parts.push(`"${tk.value}"`);
|
|
13278
13400
|
} else {
|
|
@@ -14274,6 +14396,20 @@ function parseTWSelector(cursor, errors, state, parseBody) {
|
|
|
14274
14396
|
}
|
|
14275
14397
|
cursor.consumeIf("RBRACE");
|
|
14276
14398
|
} else {
|
|
14399
|
+
for (; ; ) {
|
|
14400
|
+
const t2 = cursor.peek();
|
|
14401
|
+
if (!t2 || t2.type === "RBRACE" || t2.type === "EOF") break;
|
|
14402
|
+
if ((t2.type === "IDENT" || t2.type === "KEYWORD") && ATTR_PAIR_NAMES.has(t2.value)) {
|
|
14403
|
+
const after = cursor.peek(1);
|
|
14404
|
+
if (after && (after.type === "STRING" || after.type === "NUMBER")) {
|
|
14405
|
+
cursor.advance();
|
|
14406
|
+
cursor.advance();
|
|
14407
|
+
el.attrs.push(createAttribute(t2.value, after.value, t2.pos.line, t2.pos.col));
|
|
14408
|
+
continue;
|
|
14409
|
+
}
|
|
14410
|
+
}
|
|
14411
|
+
break;
|
|
14412
|
+
}
|
|
14277
14413
|
el.children = parseBody(["RBRACE"]);
|
|
14278
14414
|
cursor.consumeIf("RBRACE");
|
|
14279
14415
|
}
|
|
@@ -14892,10 +15028,13 @@ function parseWhileStatement(cursor, errors, parseBody) {
|
|
|
14892
15028
|
const whileToken = cursor.advance();
|
|
14893
15029
|
if (!whileToken) return null;
|
|
14894
15030
|
cursor.skipWhitespace();
|
|
14895
|
-
const condToken = cursor.peek();
|
|
14896
15031
|
let condition = "";
|
|
14897
|
-
|
|
14898
|
-
|
|
15032
|
+
while (!cursor.done) {
|
|
15033
|
+
cursor.skipWhitespace();
|
|
15034
|
+
const t2 = cursor.peek();
|
|
15035
|
+
if (!t2 || t2.type === "EOF") break;
|
|
15036
|
+
if (t2.type === "LBRACE" || t2.value === "{") break;
|
|
15037
|
+
condition += (condition ? " " : "") + t2.value;
|
|
14899
15038
|
cursor.advance();
|
|
14900
15039
|
}
|
|
14901
15040
|
const body = parseBlockBody2(cursor, parseBody);
|
|
@@ -15016,13 +15155,133 @@ function parseBlockBody2(cursor, parseBody) {
|
|
|
15016
15155
|
}
|
|
15017
15156
|
return parseBody(["KEYWORD", "RBRACE"]);
|
|
15018
15157
|
}
|
|
15019
|
-
var CLASS_BOUNDARY_BOOLEAN_ATTRS;
|
|
15158
|
+
var ATTR_PAIR_NAMES, CLASS_BOUNDARY_BOOLEAN_ATTRS;
|
|
15020
15159
|
var init_statements = __esm({
|
|
15021
15160
|
"../../packages/compiler/tw/parser/statements/index.ts"() {
|
|
15022
15161
|
init_nodes();
|
|
15023
15162
|
init_recovery();
|
|
15024
15163
|
init_directives();
|
|
15025
15164
|
init_tw();
|
|
15165
|
+
ATTR_PAIR_NAMES = /* @__PURE__ */ new Set([
|
|
15166
|
+
"href",
|
|
15167
|
+
"src",
|
|
15168
|
+
"type",
|
|
15169
|
+
"id",
|
|
15170
|
+
"name",
|
|
15171
|
+
"value",
|
|
15172
|
+
"placeholder",
|
|
15173
|
+
"action",
|
|
15174
|
+
"method",
|
|
15175
|
+
"title",
|
|
15176
|
+
"alt",
|
|
15177
|
+
"rel",
|
|
15178
|
+
"target",
|
|
15179
|
+
"lang",
|
|
15180
|
+
"charset",
|
|
15181
|
+
"content",
|
|
15182
|
+
"width",
|
|
15183
|
+
"height",
|
|
15184
|
+
"loading",
|
|
15185
|
+
"for",
|
|
15186
|
+
"role",
|
|
15187
|
+
"min",
|
|
15188
|
+
"max",
|
|
15189
|
+
"step",
|
|
15190
|
+
"pattern",
|
|
15191
|
+
"accept",
|
|
15192
|
+
"list",
|
|
15193
|
+
"download",
|
|
15194
|
+
"tabindex",
|
|
15195
|
+
"cols",
|
|
15196
|
+
"rows",
|
|
15197
|
+
"wrap",
|
|
15198
|
+
"datetime",
|
|
15199
|
+
"method",
|
|
15200
|
+
"action",
|
|
15201
|
+
"placeholder",
|
|
15202
|
+
"disabled",
|
|
15203
|
+
"required",
|
|
15204
|
+
"readonly",
|
|
15205
|
+
"checked",
|
|
15206
|
+
"selected",
|
|
15207
|
+
"target",
|
|
15208
|
+
"rel",
|
|
15209
|
+
"download",
|
|
15210
|
+
"autocomplete",
|
|
15211
|
+
"autofocus",
|
|
15212
|
+
"min",
|
|
15213
|
+
"max",
|
|
15214
|
+
"step",
|
|
15215
|
+
"pattern",
|
|
15216
|
+
"multiple",
|
|
15217
|
+
"size",
|
|
15218
|
+
"cols",
|
|
15219
|
+
"rows",
|
|
15220
|
+
"wrap",
|
|
15221
|
+
"for",
|
|
15222
|
+
"hidden",
|
|
15223
|
+
"width",
|
|
15224
|
+
"height",
|
|
15225
|
+
"loading",
|
|
15226
|
+
"loop",
|
|
15227
|
+
"muted",
|
|
15228
|
+
"controls",
|
|
15229
|
+
"poster",
|
|
15230
|
+
"preload",
|
|
15231
|
+
"contenteditable",
|
|
15232
|
+
"draggable",
|
|
15233
|
+
"spellcheck",
|
|
15234
|
+
"translate",
|
|
15235
|
+
"accesskey",
|
|
15236
|
+
"tabindex",
|
|
15237
|
+
"role",
|
|
15238
|
+
"slot",
|
|
15239
|
+
"is",
|
|
15240
|
+
"part",
|
|
15241
|
+
"enterkeyhint",
|
|
15242
|
+
"inert",
|
|
15243
|
+
"inputmode",
|
|
15244
|
+
"sandbox",
|
|
15245
|
+
"allow",
|
|
15246
|
+
"allowfullscreen",
|
|
15247
|
+
"referrerpolicy",
|
|
15248
|
+
"fetchpriority",
|
|
15249
|
+
"blocking",
|
|
15250
|
+
"property",
|
|
15251
|
+
"itemprop",
|
|
15252
|
+
"itemtype",
|
|
15253
|
+
"itemid",
|
|
15254
|
+
"itemref",
|
|
15255
|
+
"itemscope",
|
|
15256
|
+
"charset",
|
|
15257
|
+
"http-equiv",
|
|
15258
|
+
"data",
|
|
15259
|
+
"nonce",
|
|
15260
|
+
"crossorigin",
|
|
15261
|
+
"integrity",
|
|
15262
|
+
"async",
|
|
15263
|
+
"defer",
|
|
15264
|
+
"srcset",
|
|
15265
|
+
"sizes",
|
|
15266
|
+
"media",
|
|
15267
|
+
"ping",
|
|
15268
|
+
"coords",
|
|
15269
|
+
"shape",
|
|
15270
|
+
"usemap",
|
|
15271
|
+
"ismap",
|
|
15272
|
+
"kind",
|
|
15273
|
+
"srclang",
|
|
15274
|
+
"default",
|
|
15275
|
+
"autoplay",
|
|
15276
|
+
"playsinline",
|
|
15277
|
+
"enctype",
|
|
15278
|
+
"novalidate",
|
|
15279
|
+
"formaction",
|
|
15280
|
+
"formmethod",
|
|
15281
|
+
"formtarget",
|
|
15282
|
+
"formenctype",
|
|
15283
|
+
"formnovalidate"
|
|
15284
|
+
]);
|
|
15026
15285
|
CLASS_BOUNDARY_BOOLEAN_ATTRS = /* @__PURE__ */ new Set([
|
|
15027
15286
|
"disabled",
|
|
15028
15287
|
"required",
|
|
@@ -21274,6 +21533,15 @@ function splitArgs2(str) {
|
|
|
21274
21533
|
function findMatchingBrace3(str, start) {
|
|
21275
21534
|
let depth = 1;
|
|
21276
21535
|
for (let i = start + 1; i < str.length; i++) {
|
|
21536
|
+
if (str[i] === '"' || str[i] === "'") {
|
|
21537
|
+
const q = str[i];
|
|
21538
|
+
i++;
|
|
21539
|
+
while (i < str.length && str[i] !== q) {
|
|
21540
|
+
if (str[i] === "\\") i++;
|
|
21541
|
+
i++;
|
|
21542
|
+
}
|
|
21543
|
+
continue;
|
|
21544
|
+
}
|
|
21277
21545
|
if (str[i] === "{") depth++;
|
|
21278
21546
|
else if (str[i] === "}") {
|
|
21279
21547
|
depth--;
|
|
@@ -22826,7 +23094,7 @@ var init_types4 = __esm({
|
|
|
22826
23094
|
var TW_VERSION, TW_GENERATOR_META;
|
|
22827
23095
|
var init_version = __esm({
|
|
22828
23096
|
"../../packages/compiler/tw/codegen/version.ts"() {
|
|
22829
|
-
TW_VERSION = "1.0.
|
|
23097
|
+
TW_VERSION = "1.0.8";
|
|
22830
23098
|
TW_GENERATOR_META = `<meta name="generator" content="TW Framework ${TW_VERSION}">`;
|
|
22831
23099
|
}
|
|
22832
23100
|
});
|
|
@@ -23213,6 +23481,16 @@ function collectBuiltinImports(program) {
|
|
|
23213
23481
|
for (const dir of program.directives ?? []) {
|
|
23214
23482
|
if (dir.type !== "ImportDirective") continue;
|
|
23215
23483
|
const imp = dir;
|
|
23484
|
+
if (imp.source === "tw" || imp.source === "@tw/server") {
|
|
23485
|
+
for (const item of imp.items ?? []) {
|
|
23486
|
+
const name = item.split(" as ").pop().trim();
|
|
23487
|
+
const spec = TW_NAMED_BUILTINS[name];
|
|
23488
|
+
if (spec) out.set(name, spec);
|
|
23489
|
+
}
|
|
23490
|
+
if (imp.defaultImport && TW_NAMED_BUILTINS[imp.defaultImport]) {
|
|
23491
|
+
out.set(imp.defaultImport, TW_NAMED_BUILTINS[imp.defaultImport]);
|
|
23492
|
+
}
|
|
23493
|
+
}
|
|
23216
23494
|
if (!BUILTIN_COMPONENT_SPECIFIERS.includes(imp.source)) continue;
|
|
23217
23495
|
if (imp.defaultImport) out.set(imp.defaultImport, imp.source);
|
|
23218
23496
|
for (const item of imp.items ?? []) {
|
|
@@ -23282,7 +23560,7 @@ function normalizePrefetchProp(props) {
|
|
|
23282
23560
|
return p;
|
|
23283
23561
|
}
|
|
23284
23562
|
function generateRouterLinkTag(props, textContent) {
|
|
23285
|
-
const href = (props.href ?? "").trim();
|
|
23563
|
+
const href = (props.href ?? props.to ?? "").trim();
|
|
23286
23564
|
if (!href) return `<!-- @tw/RouterLink: missing href -->`;
|
|
23287
23565
|
const opts = {
|
|
23288
23566
|
href,
|
|
@@ -23309,13 +23587,18 @@ function generateBuiltinTag(specifier, props, textContent) {
|
|
|
23309
23587
|
if (specifier === "@tw/RouterLink") return generateRouterLinkTag(props, textContent);
|
|
23310
23588
|
return null;
|
|
23311
23589
|
}
|
|
23312
|
-
var BUILTIN_COMPONENT_SPECIFIERS, imageConfig;
|
|
23590
|
+
var BUILTIN_COMPONENT_SPECIFIERS, imageConfig, TW_NAMED_BUILTINS;
|
|
23313
23591
|
var init_builtin_components = __esm({
|
|
23314
23592
|
"../../packages/compiler/tw/codegen/builtin-components.ts"() {
|
|
23315
23593
|
init_tw2();
|
|
23316
23594
|
init_tw3();
|
|
23317
23595
|
BUILTIN_COMPONENT_SPECIFIERS = ["@tw/optImage", "@tw/RouterLink"];
|
|
23318
23596
|
imageConfig = DEFAULT_IMAGE_CONFIG;
|
|
23597
|
+
TW_NAMED_BUILTINS = {
|
|
23598
|
+
RouterLink: "@tw/RouterLink",
|
|
23599
|
+
optImage: "@tw/optImage",
|
|
23600
|
+
Image: "@tw/optImage"
|
|
23601
|
+
};
|
|
23319
23602
|
}
|
|
23320
23603
|
});
|
|
23321
23604
|
|
|
@@ -23386,6 +23669,14 @@ function interpolate(template, vars) {
|
|
|
23386
23669
|
let i = 0;
|
|
23387
23670
|
while (i < template.length) {
|
|
23388
23671
|
const ch = template[i];
|
|
23672
|
+
if (ch === "\\") {
|
|
23673
|
+
const nxt = template[i + 1];
|
|
23674
|
+
if (nxt === "{" || nxt === "}") {
|
|
23675
|
+
result += nxt;
|
|
23676
|
+
i += 2;
|
|
23677
|
+
continue;
|
|
23678
|
+
}
|
|
23679
|
+
}
|
|
23389
23680
|
if (ch === "{" && template[i + 1] !== "{") {
|
|
23390
23681
|
let depth = 1;
|
|
23391
23682
|
let j = i + 1;
|
|
@@ -23874,7 +24165,8 @@ function validatePlainCss(src, filePath) {
|
|
|
23874
24165
|
function compileDecl(decl) {
|
|
23875
24166
|
const trimmed = decl.trim();
|
|
23876
24167
|
if (!trimmed) return "";
|
|
23877
|
-
|
|
24168
|
+
const cssForm = /^([-\w]+)\s*:\s*/.exec(trimmed);
|
|
24169
|
+
if (cssForm) return trimmed.replace(/^([-\w]+)\s*:\s*/, "$1: ");
|
|
23878
24170
|
const spaceIdx = trimmed.search(/[\s]/);
|
|
23879
24171
|
if (spaceIdx === -1) {
|
|
23880
24172
|
return TSS_SHORTHANDS2[trimmed] ?? trimmed;
|
|
@@ -23888,9 +24180,124 @@ function combineSelector(parent, child) {
|
|
|
23888
24180
|
if (child.includes("&")) return child.replace(/&/g, parent);
|
|
23889
24181
|
return `${parent} ${child}`;
|
|
23890
24182
|
}
|
|
24183
|
+
function stripTssComments(src) {
|
|
24184
|
+
let out = "";
|
|
24185
|
+
let i = 0;
|
|
24186
|
+
const n = src.length;
|
|
24187
|
+
while (i < n) {
|
|
24188
|
+
const c = src[i];
|
|
24189
|
+
if (c === '"' || c === "'") {
|
|
24190
|
+
const q = c;
|
|
24191
|
+
out += c;
|
|
24192
|
+
i++;
|
|
24193
|
+
while (i < n && src[i] !== q) {
|
|
24194
|
+
if (src[i] === "\\") {
|
|
24195
|
+
out += src[i++];
|
|
24196
|
+
if (i < n) out += src[i++];
|
|
24197
|
+
continue;
|
|
24198
|
+
}
|
|
24199
|
+
out += src[i++];
|
|
24200
|
+
}
|
|
24201
|
+
out += src[i] ?? "";
|
|
24202
|
+
i++;
|
|
24203
|
+
continue;
|
|
24204
|
+
}
|
|
24205
|
+
if (c === "u" && /^url\s*\(/i.test(src.slice(i, i + 6))) {
|
|
24206
|
+
let depth = 0;
|
|
24207
|
+
let j = i;
|
|
24208
|
+
for (; j < n; j++) {
|
|
24209
|
+
if (src[j] === "(") depth++;
|
|
24210
|
+
else if (src[j] === ")") {
|
|
24211
|
+
depth--;
|
|
24212
|
+
if (depth === 0) {
|
|
24213
|
+
j++;
|
|
24214
|
+
break;
|
|
24215
|
+
}
|
|
24216
|
+
}
|
|
24217
|
+
}
|
|
24218
|
+
out += src.slice(i, j);
|
|
24219
|
+
i = j;
|
|
24220
|
+
continue;
|
|
24221
|
+
}
|
|
24222
|
+
if (c === "/" && src[i + 1] === "/") {
|
|
24223
|
+
while (i < n && src[i] !== "\n") i++;
|
|
24224
|
+
continue;
|
|
24225
|
+
}
|
|
24226
|
+
if (c === "/" && src[i + 1] === "*") {
|
|
24227
|
+
const end = src.indexOf("*/", i + 2);
|
|
24228
|
+
i = end === -1 ? n : end + 2;
|
|
24229
|
+
out += " ";
|
|
24230
|
+
continue;
|
|
24231
|
+
}
|
|
24232
|
+
out += c;
|
|
24233
|
+
i++;
|
|
24234
|
+
}
|
|
24235
|
+
return out;
|
|
24236
|
+
}
|
|
23891
24237
|
function compileTSS(source) {
|
|
24238
|
+
{
|
|
24239
|
+
let depth = 0;
|
|
24240
|
+
let line = 1;
|
|
24241
|
+
let inString = null;
|
|
24242
|
+
let inComment = false;
|
|
24243
|
+
let inUrl = false;
|
|
24244
|
+
for (let i = 0; i < source.length; i++) {
|
|
24245
|
+
const ch = source[i];
|
|
24246
|
+
const prev = i > 0 ? source[i - 1] : "";
|
|
24247
|
+
if (ch === "\n") {
|
|
24248
|
+
line++;
|
|
24249
|
+
inComment = false;
|
|
24250
|
+
continue;
|
|
24251
|
+
}
|
|
24252
|
+
if (inComment) continue;
|
|
24253
|
+
if (inString) {
|
|
24254
|
+
if (ch === inString && prev !== "\\") inString = null;
|
|
24255
|
+
continue;
|
|
24256
|
+
}
|
|
24257
|
+
if (inUrl) {
|
|
24258
|
+
if (ch === ")") inUrl = false;
|
|
24259
|
+
continue;
|
|
24260
|
+
}
|
|
24261
|
+
if (ch === "/" && source[i + 1] === "/" && source.slice(Math.max(0, i - 3), i).includes(":")) {
|
|
24262
|
+
inUrl = true;
|
|
24263
|
+
continue;
|
|
24264
|
+
}
|
|
24265
|
+
if (ch === "u" && source.slice(i, i + 4) === "url(") {
|
|
24266
|
+
inUrl = true;
|
|
24267
|
+
i += 3;
|
|
24268
|
+
continue;
|
|
24269
|
+
}
|
|
24270
|
+
if (ch === "/" && source[i + 1] === "/") {
|
|
24271
|
+
inComment = true;
|
|
24272
|
+
continue;
|
|
24273
|
+
}
|
|
24274
|
+
if (ch === '"' || ch === "'") {
|
|
24275
|
+
inString = ch;
|
|
24276
|
+
continue;
|
|
24277
|
+
}
|
|
24278
|
+
if (ch === "{") depth++;
|
|
24279
|
+
else if (ch === "}") {
|
|
24280
|
+
depth--;
|
|
24281
|
+
if (depth < 0) {
|
|
24282
|
+
throw new Error("TW303: unbalanced '}' in stylesheet (line " + line + ")\n " + (source.split("\n")[line - 1] ?? "").trim().slice(0, 80));
|
|
24283
|
+
}
|
|
24284
|
+
}
|
|
24285
|
+
}
|
|
24286
|
+
if (depth !== 0) {
|
|
24287
|
+
throw new Error("TW303: " + depth + " unclosed '{' in stylesheet -- a rule block is never closed\n last line: " + (source.split("\n").pop() ?? "").trim().slice(0, 80));
|
|
24288
|
+
}
|
|
24289
|
+
}
|
|
24290
|
+
const __twOut = __twCompileTSSInner(source);
|
|
24291
|
+
const leftover = __twOut.match(/\$[A-Za-z_][\w-]*/g);
|
|
24292
|
+
if (leftover && !/\$\{/.test(__twOut)) {
|
|
24293
|
+
const seen = Array.from(new Set(leftover));
|
|
24294
|
+
console.warn("[tss] unsubstituted $var(s) in output: " + seen.join(", ") + " -- define them as state vars or fix the spelling");
|
|
24295
|
+
}
|
|
24296
|
+
return __twOut;
|
|
24297
|
+
}
|
|
24298
|
+
function __twCompileTSSInner(source) {
|
|
23892
24299
|
if (!source || !source.trim()) return "";
|
|
23893
|
-
let src = source
|
|
24300
|
+
let src = stripTssComments(source);
|
|
23894
24301
|
const vars = {};
|
|
23895
24302
|
src = src.replace(/\$([A-Za-z_][\w-]*)\s*:\s*([^;{}\n]+);?/g, (_m, name, val) => {
|
|
23896
24303
|
vars[name] = String(val).trim();
|
|
@@ -24100,6 +24507,11 @@ function stripComments(src) {
|
|
|
24100
24507
|
out += c;
|
|
24101
24508
|
i++;
|
|
24102
24509
|
while (i < n && src[i] !== q) {
|
|
24510
|
+
if (src[i] === "\\") {
|
|
24511
|
+
out += src[i++];
|
|
24512
|
+
if (i < n) out += src[i++];
|
|
24513
|
+
continue;
|
|
24514
|
+
}
|
|
24103
24515
|
out += src[i++];
|
|
24104
24516
|
}
|
|
24105
24517
|
out += src[i] ?? "";
|
|
@@ -24418,9 +24830,19 @@ function emit(items, parent, env, mixins, out) {
|
|
|
24418
24830
|
out.push(...childCss);
|
|
24419
24831
|
}
|
|
24420
24832
|
function compileSCSS(source) {
|
|
24833
|
+
const cleaned0 = stripComments(source);
|
|
24834
|
+
const UNSUPPORTED = /@(import|extend|use|forward|function|each|if|else|for|while)\b/g;
|
|
24835
|
+
const m = cleaned0.match(UNSUPPORTED);
|
|
24836
|
+
if (m) {
|
|
24837
|
+
const seen = Array.from(new Set(m.map((x) => x.trim())));
|
|
24838
|
+
console.warn("[scss] unsupported at-rule(s) ignored: " + seen.join(", ") + " -- TW's SCSS subset supports nesting, $vars, & references and @mixin/@include");
|
|
24839
|
+
}
|
|
24421
24840
|
const items = parseItems(stripComments(source));
|
|
24422
24841
|
const out = [];
|
|
24423
24842
|
emit(items, "", [/* @__PURE__ */ new Map()], /* @__PURE__ */ new Map(), out);
|
|
24843
|
+
if (out.length === 0 && cleaned0.replace(/[\s;]/g, "").length > 0) {
|
|
24844
|
+
console.warn("[scss] compileSCSS produced no output for a non-empty source -- check for unsupported syntax (TW uses `color: #fff` declarations, not `color #fff`)");
|
|
24845
|
+
}
|
|
24424
24846
|
return out.join("\n");
|
|
24425
24847
|
}
|
|
24426
24848
|
var init_scss = __esm({
|
|
@@ -24460,7 +24882,8 @@ function addScope(css, scopeId) {
|
|
|
24460
24882
|
return css.replace(/([.#]?[\w-]+)\s*\{/g, `$1[data-tw-scope="${scopeId}"] {`);
|
|
24461
24883
|
}
|
|
24462
24884
|
function interpolate2(expr, vars) {
|
|
24463
|
-
|
|
24885
|
+
const masked = expr.replace(/\\\{/g, "O").replace(/\\\}/g, "C");
|
|
24886
|
+
const out = masked.replace(/\{([^}]+)\}/g, (_, name) => {
|
|
24464
24887
|
const key = name.trim();
|
|
24465
24888
|
if (/^[a-zA-Z_$][\w$]*$/.test(key)) {
|
|
24466
24889
|
return vars[key] ?? "";
|
|
@@ -24468,6 +24891,7 @@ function interpolate2(expr, vars) {
|
|
|
24468
24891
|
const val = evaluate(key, vars);
|
|
24469
24892
|
return val === null ? "" : val;
|
|
24470
24893
|
});
|
|
24894
|
+
return out.replace(/\u0001O/g, "{").replace(/\u0001C/g, "}");
|
|
24471
24895
|
}
|
|
24472
24896
|
function isTruthy2(val) {
|
|
24473
24897
|
return isTruthy(val);
|
|
@@ -24553,13 +24977,13 @@ function collectTssImports(programs, ctx) {
|
|
|
24553
24977
|
else ctx.inlineStyles.push(css);
|
|
24554
24978
|
}
|
|
24555
24979
|
} catch (e) {
|
|
24556
|
-
if (e && typeof e.message === "string" && /^TW30[
|
|
24980
|
+
if (e && typeof e.message === "string" && /^TW30[123]/.test(e.message)) throw e;
|
|
24557
24981
|
}
|
|
24558
24982
|
}
|
|
24559
24983
|
}
|
|
24560
24984
|
}
|
|
24561
24985
|
} catch (e) {
|
|
24562
|
-
if (e && typeof e.message === "string" && /^TW30[
|
|
24986
|
+
if (e && typeof e.message === "string" && /^TW30[123]/.test(e.message)) throw e;
|
|
24563
24987
|
}
|
|
24564
24988
|
}
|
|
24565
24989
|
function resolveTssPath(fromDir, rel) {
|
|
@@ -24779,6 +25203,10 @@ function generateSuspense(el, ctx) {
|
|
|
24779
25203
|
function generateElement(el, ctx) {
|
|
24780
25204
|
const tag = el.tag;
|
|
24781
25205
|
const voidTag = VOID_TAGS5.has(tag.toLowerCase());
|
|
25206
|
+
if (tag.length > 0 && tag[0] === tag[0].toUpperCase() && !["Link", "Suspense"].includes(tag) && !componentRegistry.has(tag) && !resolveBuiltin(tag, activeBuiltinImports)) {
|
|
25207
|
+
console.warn("[tw] unknown component <" + tag + "> (" + el.line + ":" + el.col + ") -- no import resolves it; nothing was rendered");
|
|
25208
|
+
return "<!-- TW: unknown component '" + tag + "' -- check the import -->";
|
|
25209
|
+
}
|
|
24782
25210
|
{
|
|
24783
25211
|
const builtinSpec = resolveBuiltin(tag, activeBuiltinImports);
|
|
24784
25212
|
if (builtinSpec) {
|
|
@@ -24869,6 +25297,8 @@ function renderAttrs(el, ctx) {
|
|
|
24869
25297
|
} else if (typeof attr.value === "string" && !attr.name.startsWith("data-tw-event") && /\{[^}]+\}/.test(attr.value) && Object.keys(ctx.stateVars).length > 0) {
|
|
24870
25298
|
const val = interpolate2(attr.value, ctx.stateVars);
|
|
24871
25299
|
parts.push(`${attr.name}="${escapeAttr2(sanitizeUrlAttr(attr.name, val))}"`);
|
|
25300
|
+
} else if (typeof attr.value === "string" && /\\[{}]/.test(attr.value)) {
|
|
25301
|
+
parts.push(`${attr.name}="${escapeAttr2(sanitizeUrlAttr(attr.name, String(attr.value).replace(/\\([{}])/g, "$1")))}"`);
|
|
24872
25302
|
} else {
|
|
24873
25303
|
parts.push(`${attr.name}="${escapeAttr2(sanitizeUrlAttr(attr.name, attr.value))}"`);
|
|
24874
25304
|
}
|
|
@@ -24975,6 +25405,9 @@ function generateText(text, ctx) {
|
|
|
24975
25405
|
}
|
|
24976
25406
|
return escapeHTML(interpolate2(text.value, ctx.stateVars));
|
|
24977
25407
|
}
|
|
25408
|
+
if (text.value && /\\[{}]/.test(text.value)) {
|
|
25409
|
+
return escapeHTML(text.value.replace(/\\([{}])/g, "$1"));
|
|
25410
|
+
}
|
|
24978
25411
|
return escapeHTML(text.value);
|
|
24979
25412
|
}
|
|
24980
25413
|
function registerComponentTemplate(name, program) {
|
|
@@ -25033,7 +25466,13 @@ function generateComponent(comp, ctx) {
|
|
|
25033
25466
|
ctx.slotContent = savedSlot;
|
|
25034
25467
|
ctx.inHead = compSavedInHead;
|
|
25035
25468
|
} else {
|
|
25036
|
-
|
|
25469
|
+
const known = ["Link", "Suspense"];
|
|
25470
|
+
if (!known.includes(compName)) {
|
|
25471
|
+
console.warn("[tw] unknown component <" + compName + "> (" + comp.line + ":" + comp.col + ") -- unresolvable import; nothing was rendered");
|
|
25472
|
+
childrenHTML = "<!-- TW: unknown component '" + compName + "' -- check the import -->";
|
|
25473
|
+
} else {
|
|
25474
|
+
childrenHTML = comp.children.map((c) => generateNode(c, ctx)).join("");
|
|
25475
|
+
}
|
|
25037
25476
|
}
|
|
25038
25477
|
ctx.stateVars = savedStateVars;
|
|
25039
25478
|
ctx.componentStack.pop();
|
|
@@ -26458,7 +26897,7 @@ function emitElement(gen, el, ctx) {
|
|
|
26458
26897
|
attrStr = el.attrs.map((a) => {
|
|
26459
26898
|
if (a.value === true) return a.name;
|
|
26460
26899
|
const val = a.isInterpolated ? interpolate3(a.value, ctx.stateVars) : a.value;
|
|
26461
|
-
return `${a.name}="${escapeAttr3(val)}"`;
|
|
26900
|
+
return `${a.name}="${escapeAttr3(sanitizeUrlAttr2(a.name, val))}"`;
|
|
26462
26901
|
}).join(" ");
|
|
26463
26902
|
}
|
|
26464
26903
|
if (el.styles && el.styles.length > 0) {
|
|
@@ -26574,14 +27013,25 @@ function getDirectiveValue2(program, key) {
|
|
|
26574
27013
|
function escapeHTML2(str) {
|
|
26575
27014
|
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
26576
27015
|
}
|
|
27016
|
+
function sanitizeUrlAttr2(name, value) {
|
|
27017
|
+
const URL_ATTRS = /* @__PURE__ */ new Set(["href", "src", "action", "formaction", "xlink:href", "poster", "background"]);
|
|
27018
|
+
if (!URL_ATTRS.has(name.toLowerCase())) return value;
|
|
27019
|
+
const trimmed = String(value).trim().toLowerCase().replace(/[\s\0]/g, "");
|
|
27020
|
+
if (trimmed.startsWith("javascript:") || trimmed.startsWith("data:text/html") || trimmed.startsWith("vbscript:")) {
|
|
27021
|
+
return "#";
|
|
27022
|
+
}
|
|
27023
|
+
return value;
|
|
27024
|
+
}
|
|
26577
27025
|
function escapeAttr3(str) {
|
|
26578
27026
|
return str.replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'");
|
|
26579
27027
|
}
|
|
26580
27028
|
function interpolate3(expr, vars) {
|
|
26581
|
-
|
|
27029
|
+
const masked = expr.replace(/\\\{/g, "O").replace(/\\\}/g, "C");
|
|
27030
|
+
const out = masked.replace(/\{([^}]+)\}/g, (_, name) => {
|
|
26582
27031
|
const key = name.trim();
|
|
26583
27032
|
return vars[key] ?? "";
|
|
26584
27033
|
});
|
|
27034
|
+
return out.replace(/\u0001O/g, "{").replace(/\u0001C/g, "}");
|
|
26585
27035
|
}
|
|
26586
27036
|
function isTruthy3(val) {
|
|
26587
27037
|
if (!val) return false;
|
|
@@ -26785,6 +27235,15 @@ var init_streaming = __esm({
|
|
|
26785
27235
|
});
|
|
26786
27236
|
|
|
26787
27237
|
// ../../packages/compiler/tw/codegen/vdom-gen.ts
|
|
27238
|
+
function sanitizeUrlAttr3(name, value) {
|
|
27239
|
+
const URL_ATTRS = /* @__PURE__ */ new Set(["href", "src", "action", "formaction", "xlink:href", "poster", "background"]);
|
|
27240
|
+
if (!URL_ATTRS.has(name.toLowerCase())) return value;
|
|
27241
|
+
const trimmed = String(value).trim().toLowerCase().replace(/[\s\0]/g, "");
|
|
27242
|
+
if (trimmed.startsWith("javascript:") || trimmed.startsWith("data:text/html") || trimmed.startsWith("vbscript:")) {
|
|
27243
|
+
return "#";
|
|
27244
|
+
}
|
|
27245
|
+
return value;
|
|
27246
|
+
}
|
|
26788
27247
|
function generateVDOMCode(program, ctx) {
|
|
26789
27248
|
const builder2 = new VDOMCodeBuilder(ctx);
|
|
26790
27249
|
return builder2.generateModule(program);
|
|
@@ -26798,10 +27257,12 @@ function generateNodeVDOM(node, ctx) {
|
|
|
26798
27257
|
return builder2.generateFromAST(node);
|
|
26799
27258
|
}
|
|
26800
27259
|
function interpolate4(expr, vars) {
|
|
26801
|
-
|
|
27260
|
+
const masked = expr.replace(/\\\{/g, "O").replace(/\\\}/g, "C");
|
|
27261
|
+
const out = masked.replace(/\{([^}]+)\}/g, (_, name) => {
|
|
26802
27262
|
const key = name.trim();
|
|
26803
27263
|
return vars[key] ?? "";
|
|
26804
27264
|
});
|
|
27265
|
+
return out.replace(/\u0001O/g, "{").replace(/\u0001C/g, "}");
|
|
26805
27266
|
}
|
|
26806
27267
|
function isTruthy4(val) {
|
|
26807
27268
|
if (!val) return false;
|
|
@@ -26935,7 +27396,7 @@ var init_vdom_gen = __esm({
|
|
|
26935
27396
|
const expr = this.interpolateToExpr(attr.value);
|
|
26936
27397
|
attrs.push(`${JSON.stringify(attr.name)}: ${expr}`);
|
|
26937
27398
|
} else {
|
|
26938
|
-
attrs.push(`${JSON.stringify(attr.name)}: ${JSON.stringify(attr.value)}`);
|
|
27399
|
+
attrs.push(`${JSON.stringify(attr.name)}: ${JSON.stringify(sanitizeUrlAttr3(attr.name, attr.value))}`);
|
|
26939
27400
|
}
|
|
26940
27401
|
}
|
|
26941
27402
|
}
|
|
@@ -28806,13 +29267,27 @@ ${indentation})` : type;
|
|
|
28806
29267
|
});
|
|
28807
29268
|
|
|
28808
29269
|
// ../../packages/compiler/tw/codegen/vdom.ts
|
|
29270
|
+
function stateLiteralForRender(raw) {
|
|
29271
|
+
const evalNum = (v) => typeof v === "number" || typeof v === "boolean" ? String(v) : null;
|
|
29272
|
+
try {
|
|
29273
|
+
const n = evalNum(JSON.parse(raw));
|
|
29274
|
+
if (n !== null) return n;
|
|
29275
|
+
} catch {
|
|
29276
|
+
}
|
|
29277
|
+
try {
|
|
29278
|
+
const n = evalNum(new Function("return (" + raw + ")")());
|
|
29279
|
+
if (n !== null) return n;
|
|
29280
|
+
} catch {
|
|
29281
|
+
}
|
|
29282
|
+
return raw;
|
|
29283
|
+
}
|
|
28809
29284
|
function generateWithLayout(layoutProgram, pageProgram, stateVars) {
|
|
28810
29285
|
const ctx = createContext("ssr");
|
|
28811
29286
|
if (stateVars) ctx.stateVars = { ...stateVars };
|
|
28812
29287
|
for (const dir of pageProgram.directives || []) {
|
|
28813
29288
|
if (dir.type === "StateDirective") {
|
|
28814
29289
|
for (const decl of dir.declarations || []) {
|
|
28815
|
-
ctx.stateVars[decl.name] = decl.value;
|
|
29290
|
+
ctx.stateVars[decl.name] = stateLiteralForRender(decl.value);
|
|
28816
29291
|
}
|
|
28817
29292
|
}
|
|
28818
29293
|
}
|
|
@@ -28844,7 +29319,7 @@ function generateWithLayoutChain(layoutPrograms, pageProgram, stateVars) {
|
|
|
28844
29319
|
for (const dir of pageProgram.directives || []) {
|
|
28845
29320
|
if (dir.type === "StateDirective") {
|
|
28846
29321
|
for (const decl of dir.declarations || []) {
|
|
28847
|
-
ctx2.stateVars[decl.name] = decl.value;
|
|
29322
|
+
ctx2.stateVars[decl.name] = stateLiteralForRender(decl.value);
|
|
28848
29323
|
}
|
|
28849
29324
|
}
|
|
28850
29325
|
}
|
|
@@ -28952,7 +29427,7 @@ function generateVDOM(program, ctx) {
|
|
|
28952
29427
|
return {
|
|
28953
29428
|
t: el.tag,
|
|
28954
29429
|
a: el.attrs.reduce((acc, a) => {
|
|
28955
|
-
acc[a.name] = a.value;
|
|
29430
|
+
acc[a.name] = sanitizeUrlAttr4(a.name, String(a.value));
|
|
28956
29431
|
return acc;
|
|
28957
29432
|
}, {}),
|
|
28958
29433
|
c: el.children.map(toVNode)
|
|
@@ -28990,16 +29465,27 @@ function processDirectives(program, ctx) {
|
|
|
28990
29465
|
}
|
|
28991
29466
|
}
|
|
28992
29467
|
function interpolate5(expr, vars) {
|
|
28993
|
-
|
|
29468
|
+
const masked = expr.replace(/\\\{/g, "O").replace(/\\\}/g, "C");
|
|
29469
|
+
const out = masked.replace(/\{([^}]+)\}/g, (_, name) => {
|
|
28994
29470
|
const key = name.trim();
|
|
28995
29471
|
return vars[key] ?? "";
|
|
28996
29472
|
});
|
|
29473
|
+
return out.replace(/\u0001O/g, "{").replace(/\u0001C/g, "}");
|
|
28997
29474
|
}
|
|
28998
29475
|
function isTruthy5(val) {
|
|
28999
29476
|
if (!val) return false;
|
|
29000
29477
|
const lower = val.toLowerCase().trim();
|
|
29001
29478
|
return lower !== "false" && lower !== "0" && lower !== "" && lower !== "null" && lower !== "undefined";
|
|
29002
29479
|
}
|
|
29480
|
+
function sanitizeUrlAttr4(name, value) {
|
|
29481
|
+
const URL_ATTRS = /* @__PURE__ */ new Set(["href", "src", "action", "formaction", "xlink:href", "poster", "background"]);
|
|
29482
|
+
if (!URL_ATTRS.has(name.toLowerCase())) return value;
|
|
29483
|
+
const trimmed = String(value).trim().toLowerCase().replace(/[\s\0]/g, "");
|
|
29484
|
+
if (trimmed.startsWith("javascript:") || trimmed.startsWith("data:text/html") || trimmed.startsWith("vbscript:")) {
|
|
29485
|
+
return "#";
|
|
29486
|
+
}
|
|
29487
|
+
return value;
|
|
29488
|
+
}
|
|
29003
29489
|
function generate(program, stateVars) {
|
|
29004
29490
|
const startTime = performance.now();
|
|
29005
29491
|
const ctx = createContext("ssr");
|
|
@@ -29024,7 +29510,9 @@ function generate(program, stateVars) {
|
|
|
29024
29510
|
ctx.hasInteractivity = true;
|
|
29025
29511
|
continue;
|
|
29026
29512
|
}
|
|
29027
|
-
|
|
29513
|
+
if (!(decl.name in ctx.stateVars)) {
|
|
29514
|
+
ctx.stateVars[decl.name] = stateLiteralForRender(decl.value);
|
|
29515
|
+
}
|
|
29028
29516
|
let v = decl.value;
|
|
29029
29517
|
try {
|
|
29030
29518
|
v = JSON.parse(decl.value);
|
|
@@ -29357,7 +29845,11 @@ var init_codes = __esm({
|
|
|
29357
29845
|
TW090: { message: "cache { } requires a revalidate window or a life profile", severity: "error", category: "syntax" },
|
|
29358
29846
|
TW091: { message: "fn cached handler is impure (request.cookies/headers/body or setSignal)", severity: "error", category: "semantic" },
|
|
29359
29847
|
TW092: { message: "Unknown cache profile", severity: "error", category: "semantic" },
|
|
29360
|
-
TW093: { message: "Non-deterministic call in a cached handler (value freezes into the cache entry)", severity: "warning", category: "best-practice" }
|
|
29848
|
+
TW093: { message: "Non-deterministic call in a cached handler (value freezes into the cache entry)", severity: "warning", category: "best-practice" },
|
|
29849
|
+
TW094: { message: "Invalid state literal -- array/object elements must be comma-separated (and objects need colons)", severity: "error", category: "syntax" },
|
|
29850
|
+
TW095: { message: "Invalid for-loop iterable -- use {varName} or a valid comma-separated literal", severity: "error", category: "syntax" },
|
|
29851
|
+
TW096: { message: "Unknown state variable in interpolation (renders empty)", severity: "warning", category: "semantic" },
|
|
29852
|
+
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" }
|
|
29361
29853
|
};
|
|
29362
29854
|
}
|
|
29363
29855
|
});
|
|
@@ -29678,6 +30170,124 @@ function checkUnusedDeclarations(program, filePath, diags) {
|
|
|
29678
30170
|
}
|
|
29679
30171
|
}
|
|
29680
30172
|
}
|
|
30173
|
+
function literalIsBroken(value) {
|
|
30174
|
+
const v = value.trim();
|
|
30175
|
+
if (!v.startsWith("[") && !v.startsWith("{")) return false;
|
|
30176
|
+
try {
|
|
30177
|
+
new Function("return (" + v + ")");
|
|
30178
|
+
return false;
|
|
30179
|
+
} catch {
|
|
30180
|
+
return true;
|
|
30181
|
+
}
|
|
30182
|
+
}
|
|
30183
|
+
function checkStateLiterals(program, filePath, diags) {
|
|
30184
|
+
for (const decl of collectStateDecls(program)) {
|
|
30185
|
+
if (decl.signalKind === "derived") continue;
|
|
30186
|
+
if (literalIsBroken(String(decl.value))) {
|
|
30187
|
+
diags.push(createDiagnostic(
|
|
30188
|
+
"TW094",
|
|
30189
|
+
decl.line,
|
|
30190
|
+
decl.col,
|
|
30191
|
+
filePath,
|
|
30192
|
+
`${decl.name} = ${decl.value}`,
|
|
30193
|
+
[
|
|
30194
|
+
`Comma-separate array elements: ${decl.name} = ["a", "b", "c"]`,
|
|
30195
|
+
`Objects need colons: ${decl.name} = { key: "value" }`
|
|
30196
|
+
]
|
|
30197
|
+
));
|
|
30198
|
+
}
|
|
30199
|
+
}
|
|
30200
|
+
}
|
|
30201
|
+
function checkLoopIterables(program, filePath, diags) {
|
|
30202
|
+
forEachNode(program, (node) => {
|
|
30203
|
+
if (!node || node.type !== "For") return;
|
|
30204
|
+
const iterable = String(node.iterable ?? "").trim();
|
|
30205
|
+
if (!iterable) {
|
|
30206
|
+
diags.push(createDiagnostic(
|
|
30207
|
+
"TW095",
|
|
30208
|
+
node.line,
|
|
30209
|
+
node.col,
|
|
30210
|
+
filePath,
|
|
30211
|
+
"for-loop has no iterable",
|
|
30212
|
+
[`Use: for item in {list} { ... }`]
|
|
30213
|
+
));
|
|
30214
|
+
return;
|
|
30215
|
+
}
|
|
30216
|
+
if (/^\{[A-Za-z_][\w.]*\}$/.test(iterable)) return;
|
|
30217
|
+
if (/^[A-Za-z_][\w.]*$/.test(iterable)) return;
|
|
30218
|
+
if (literalIsBroken(iterable)) {
|
|
30219
|
+
diags.push(createDiagnostic(
|
|
30220
|
+
"TW095",
|
|
30221
|
+
node.line,
|
|
30222
|
+
node.col,
|
|
30223
|
+
filePath,
|
|
30224
|
+
"for x in " + iterable,
|
|
30225
|
+
[
|
|
30226
|
+
`Wrap a state variable in braces: for item in {items}`,
|
|
30227
|
+
`Literals need commas: for item in {["a", "b", "c"]}`
|
|
30228
|
+
]
|
|
30229
|
+
));
|
|
30230
|
+
}
|
|
30231
|
+
});
|
|
30232
|
+
}
|
|
30233
|
+
function checkUnknownStateVars(program, filePath, diags) {
|
|
30234
|
+
const known = /* @__PURE__ */ new Set();
|
|
30235
|
+
for (const decl of collectStateDecls(program)) known.add(decl.name);
|
|
30236
|
+
forEachNode(program, (node) => {
|
|
30237
|
+
if (node && node.type === "For" && node.varName) known.add(node.varName);
|
|
30238
|
+
if (node && node.type === "For" && node.indexName) known.add(node.indexName);
|
|
30239
|
+
});
|
|
30240
|
+
const BUILTINS2 = /* @__PURE__ */ new Set([
|
|
30241
|
+
"params",
|
|
30242
|
+
"props",
|
|
30243
|
+
"Math",
|
|
30244
|
+
"Date",
|
|
30245
|
+
"JSON",
|
|
30246
|
+
"String",
|
|
30247
|
+
"Number",
|
|
30248
|
+
"Boolean",
|
|
30249
|
+
"Array",
|
|
30250
|
+
"Object",
|
|
30251
|
+
"window",
|
|
30252
|
+
"true",
|
|
30253
|
+
"false",
|
|
30254
|
+
"null",
|
|
30255
|
+
"undefined"
|
|
30256
|
+
]);
|
|
30257
|
+
forEachNode(program, (node) => {
|
|
30258
|
+
if (!isText(node)) return;
|
|
30259
|
+
const text = String(node.value ?? "");
|
|
30260
|
+
if (!text.includes("{")) return;
|
|
30261
|
+
for (const m of text.matchAll(/\{\s*([A-Za-z_][\w]*)\s*\}/g)) {
|
|
30262
|
+
const name = m[1];
|
|
30263
|
+
if (known.has(name) || BUILTINS2.has(name)) continue;
|
|
30264
|
+
diags.push(createDiagnostic(
|
|
30265
|
+
"TW096",
|
|
30266
|
+
node.line,
|
|
30267
|
+
node.col,
|
|
30268
|
+
filePath,
|
|
30269
|
+
`{${name}}`,
|
|
30270
|
+
[`Declare it in the state block, or check the spelling`]
|
|
30271
|
+
));
|
|
30272
|
+
}
|
|
30273
|
+
});
|
|
30274
|
+
}
|
|
30275
|
+
function checkWhileComparisons(program, filePath, diags) {
|
|
30276
|
+
forEachNode(program, (node) => {
|
|
30277
|
+
if (!node || node.type !== "While") return;
|
|
30278
|
+
const cond = String(node.condition ?? "");
|
|
30279
|
+
if (/[<>=!]/.test(cond)) {
|
|
30280
|
+
diags.push(createDiagnostic(
|
|
30281
|
+
"TW097",
|
|
30282
|
+
node.line,
|
|
30283
|
+
node.col,
|
|
30284
|
+
filePath,
|
|
30285
|
+
"while " + cond,
|
|
30286
|
+
["Use `for item in {list}` for repetition", "while is for boolean flags (state { loading = true })"]
|
|
30287
|
+
));
|
|
30288
|
+
}
|
|
30289
|
+
});
|
|
30290
|
+
}
|
|
29681
30291
|
var HTML_ELEMENTS2, HTML_ATTRIBUTES2, GLOBAL_ATTRIBUTES2, CSS_PROPERTIES2, CSS_ALIASES2, EVENT_TYPES2, ARIA_ROLES2, ARIA_ATTRIBUTES2;
|
|
29682
30292
|
var init_rules = __esm({
|
|
29683
30293
|
"../../packages/compiler/tw/diagnostics/rules.ts"() {
|
|
@@ -30568,6 +31178,10 @@ function diagnose(program, filePath) {
|
|
|
30568
31178
|
checkSemanticErrors(program, filePath, diagnostics);
|
|
30569
31179
|
checkDirectives(program, filePath, diagnostics);
|
|
30570
31180
|
checkUnusedDeclarations(program, filePath, diagnostics);
|
|
31181
|
+
checkStateLiterals(program, filePath, diagnostics);
|
|
31182
|
+
checkLoopIterables(program, filePath, diagnostics);
|
|
31183
|
+
checkUnknownStateVars(program, filePath, diagnostics);
|
|
31184
|
+
checkWhileComparisons(program, filePath, diagnostics);
|
|
30571
31185
|
return diagnostics;
|
|
30572
31186
|
}
|
|
30573
31187
|
var RuleEngine;
|
|
@@ -33080,7 +33694,12 @@ async function renderIslandContent(component, props, ctx) {
|
|
|
33080
33694
|
return { html: result.html, css: result.css, js: result.js };
|
|
33081
33695
|
}
|
|
33082
33696
|
function countServerComponents(source) {
|
|
33083
|
-
|
|
33697
|
+
let cleaned = source;
|
|
33698
|
+
try {
|
|
33699
|
+
cleaned = stripCommentsStringAware(source);
|
|
33700
|
+
} catch {
|
|
33701
|
+
}
|
|
33702
|
+
const matches = cleaned.match(/@render\s+mode:\s*['"]server['"]/g);
|
|
33084
33703
|
return matches ? matches.length : 0;
|
|
33085
33704
|
}
|
|
33086
33705
|
function determineHydrationStrategy(component, position) {
|
|
@@ -33118,6 +33737,7 @@ function parseClientDirective(source) {
|
|
|
33118
33737
|
var componentRegistry2;
|
|
33119
33738
|
var init_server_components = __esm({
|
|
33120
33739
|
"../../packages/compiler/tw/server-components.ts"() {
|
|
33740
|
+
init_tw();
|
|
33121
33741
|
init_tw4();
|
|
33122
33742
|
componentRegistry2 = /* @__PURE__ */ new Map();
|
|
33123
33743
|
}
|
|
@@ -40041,385 +40661,6 @@ var init_tw4 = __esm({
|
|
|
40041
40661
|
}
|
|
40042
40662
|
});
|
|
40043
40663
|
|
|
40044
|
-
// ../../packages/shared/tw/node-import.ts
|
|
40045
|
-
var node_import_exports = {};
|
|
40046
|
-
__export(node_import_exports, {
|
|
40047
|
-
twImportTs: () => twImportTs
|
|
40048
|
-
});
|
|
40049
|
-
import { join as join10 } from "node:path";
|
|
40050
|
-
import { tmpdir } from "node:os";
|
|
40051
|
-
import { pathToFileURL as pathToFileURL2 } from "node:url";
|
|
40052
|
-
async function twImportTs(tsPath) {
|
|
40053
|
-
if (!globalThis.__TW_NODE) {
|
|
40054
|
-
return import(tsPath);
|
|
40055
|
-
}
|
|
40056
|
-
const esbuildMain = globalThis.__TW_ESBUILD_MODULE;
|
|
40057
|
-
const esbuild = await (esbuildMain ? import(esbuildMain) : import("esbuild"));
|
|
40058
|
-
const safe = tsPath.replace(/[^a-zA-Z0-9]/g, "_").slice(-120);
|
|
40059
|
-
const cachePath = join10(tmpdir(), "tw-" + safe + ".mjs");
|
|
40060
|
-
await esbuild.build({
|
|
40061
|
-
entryPoints: [tsPath],
|
|
40062
|
-
bundle: true,
|
|
40063
|
-
format: "esm",
|
|
40064
|
-
platform: "node",
|
|
40065
|
-
target: "node18",
|
|
40066
|
-
outfile: cachePath,
|
|
40067
|
-
logLevel: "silent"
|
|
40068
|
-
});
|
|
40069
|
-
return import(pathToFileURL2(cachePath).href);
|
|
40070
|
-
}
|
|
40071
|
-
var init_node_import = __esm({
|
|
40072
|
-
"../../packages/shared/tw/node-import.ts"() {
|
|
40073
|
-
}
|
|
40074
|
-
});
|
|
40075
|
-
|
|
40076
|
-
// tw/commands/client-bundle.ts
|
|
40077
|
-
var client_bundle_exports = {};
|
|
40078
|
-
__export(client_bundle_exports, {
|
|
40079
|
-
BUILTIN_CLIENT_OK_SPECIFIERS: () => BUILTIN_CLIENT_OK_SPECIFIERS,
|
|
40080
|
-
RUNTIME_CLIENT_OK_SPECIFIERS: () => RUNTIME_CLIENT_OK_SPECIFIERS,
|
|
40081
|
-
buildClientChunk: () => buildClientChunk,
|
|
40082
|
-
buildPageScope: () => buildPageScope,
|
|
40083
|
-
collectClientImportsFromSource: () => collectClientImportsFromSource,
|
|
40084
|
-
findServerOnlyViolations: () => findServerOnlyViolations,
|
|
40085
|
-
importNames: () => importNames,
|
|
40086
|
-
isClientImportSpecifier: () => isClientImportSpecifier
|
|
40087
|
-
});
|
|
40088
|
-
import { join as join11, resolve as resolve3 } from "node:path";
|
|
40089
|
-
import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, unlinkSync as unlinkSync2 } from "node:fs";
|
|
40090
|
-
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
40091
|
-
function isClientImportSpecifier(spec) {
|
|
40092
|
-
if (spec.startsWith("@./") || spec.startsWith("./") || spec.startsWith("../")) return false;
|
|
40093
|
-
if (/\.(tw|tss|twm|ts|js)$/.test(spec)) return false;
|
|
40094
|
-
if (spec.startsWith("@tw/")) {
|
|
40095
|
-
return RUNTIME_CLIENT_OK_SPECIFIERS.includes(spec);
|
|
40096
|
-
}
|
|
40097
|
-
return true;
|
|
40098
|
-
}
|
|
40099
|
-
function collectClientImportsFromSource(src) {
|
|
40100
|
-
const lines = [];
|
|
40101
|
-
let m;
|
|
40102
|
-
IMPORT_RE.lastIndex = 0;
|
|
40103
|
-
while (m = IMPORT_RE.exec(src)) {
|
|
40104
|
-
if (isClientImportSpecifier(m[2])) lines.push(m[0].trim().replace(/;$/, ""));
|
|
40105
|
-
}
|
|
40106
|
-
return lines;
|
|
40107
|
-
}
|
|
40108
|
-
function importNames(line) {
|
|
40109
|
-
const clause = line.match(/^import\s+([\s\S]+?)\s+from/);
|
|
40110
|
-
if (!clause) return [];
|
|
40111
|
-
const c = clause[1].trim();
|
|
40112
|
-
const names = [];
|
|
40113
|
-
if (c.startsWith("*")) {
|
|
40114
|
-
const as = c.split(/\s+as\s+/);
|
|
40115
|
-
if (as[1]) names.push(as[1].trim());
|
|
40116
|
-
} else if (c.startsWith("{")) {
|
|
40117
|
-
const braces = c.match(/\{([\s\S]*)\}/);
|
|
40118
|
-
if (braces) {
|
|
40119
|
-
for (const part of braces[1].split(",")) {
|
|
40120
|
-
const p = part.trim();
|
|
40121
|
-
if (!p) continue;
|
|
40122
|
-
const seg = p.split(/\s+as\s+/);
|
|
40123
|
-
names.push(seg[seg.length - 1].trim());
|
|
40124
|
-
}
|
|
40125
|
-
}
|
|
40126
|
-
} else {
|
|
40127
|
-
const def = c.match(/^[A-Za-z_$][\w$]*/);
|
|
40128
|
-
if (def) names.push(def[0]);
|
|
40129
|
-
}
|
|
40130
|
-
return names;
|
|
40131
|
-
}
|
|
40132
|
-
function findEsbuildBin(rootDir) {
|
|
40133
|
-
const candidates = [
|
|
40134
|
-
join11(rootDir, "node_modules", ".bin", "esbuild"),
|
|
40135
|
-
resolve3(rootDir, "..", "node_modules", ".bin", "esbuild"),
|
|
40136
|
-
// CLI repo layout (dev / source runs)
|
|
40137
|
-
resolve3(new URL(".", import.meta.url).pathname, "..", "..", "..", "..", "node_modules", ".bin", "esbuild")
|
|
40138
|
-
];
|
|
40139
|
-
for (const c of candidates) {
|
|
40140
|
-
try {
|
|
40141
|
-
if (existsSync7(c)) return c;
|
|
40142
|
-
} catch {
|
|
40143
|
-
}
|
|
40144
|
-
}
|
|
40145
|
-
const bd = globalThis.__TW_BUNDLE_DIR;
|
|
40146
|
-
if (bd) {
|
|
40147
|
-
const b = resolve3(bd, "..", "..", "..", "node_modules", ".bin", "esbuild");
|
|
40148
|
-
try {
|
|
40149
|
-
if (existsSync7(b)) return b;
|
|
40150
|
-
} catch {
|
|
40151
|
-
}
|
|
40152
|
-
}
|
|
40153
|
-
return "esbuild";
|
|
40154
|
-
}
|
|
40155
|
-
function findServerOnlyViolations(src) {
|
|
40156
|
-
const out = [];
|
|
40157
|
-
let m;
|
|
40158
|
-
IMPORT_RE.lastIndex = 0;
|
|
40159
|
-
while (m = IMPORT_RE.exec(src)) {
|
|
40160
|
-
const spec = m[2];
|
|
40161
|
-
if (spec === "lib" || spec.startsWith("lib/")) {
|
|
40162
|
-
out.push(spec);
|
|
40163
|
-
continue;
|
|
40164
|
-
}
|
|
40165
|
-
if (isClientImportSpecifier(spec)) continue;
|
|
40166
|
-
if (spec.startsWith("@tw/")) {
|
|
40167
|
-
if (BUILTIN_CLIENT_OK_SPECIFIERS.includes(spec)) continue;
|
|
40168
|
-
if (RUNTIME_CLIENT_OK_SPECIFIERS.includes(spec)) continue;
|
|
40169
|
-
out.push(spec);
|
|
40170
|
-
continue;
|
|
40171
|
-
}
|
|
40172
|
-
if (spec.startsWith("@./") || spec.startsWith("./") || spec.startsWith("../")) {
|
|
40173
|
-
if (/\.(tw|tss)$/.test(spec)) continue;
|
|
40174
|
-
out.push(spec);
|
|
40175
|
-
continue;
|
|
40176
|
-
}
|
|
40177
|
-
if (spec.startsWith("lib/")) out.push(spec);
|
|
40178
|
-
}
|
|
40179
|
-
return out;
|
|
40180
|
-
}
|
|
40181
|
-
function contentHash2(s) {
|
|
40182
|
-
let h = 5381;
|
|
40183
|
-
for (let i = 0; i < s.length; i++) h = (h * 33 ^ s.charCodeAt(i)) >>> 0;
|
|
40184
|
-
return h.toString(36);
|
|
40185
|
-
}
|
|
40186
|
-
function vendoredRuntimeClient() {
|
|
40187
|
-
const fromSrc = new URL("../../dist/tw-runtime-client.mjs", import.meta.url).pathname;
|
|
40188
|
-
if (existsSync7(fromSrc)) return fromSrc;
|
|
40189
|
-
const fromBundle = new URL("./tw-runtime-client.mjs", import.meta.url).pathname;
|
|
40190
|
-
if (existsSync7(fromBundle)) return fromBundle;
|
|
40191
|
-
return null;
|
|
40192
|
-
}
|
|
40193
|
-
function rewriteRuntimeSpecifier(importLine) {
|
|
40194
|
-
const vendored = vendoredRuntimeClient();
|
|
40195
|
-
if (!vendored) return importLine;
|
|
40196
|
-
const m = importLine.match(/from\s+["'](@tw\/runtime)["']/);
|
|
40197
|
-
if (!m) return importLine;
|
|
40198
|
-
return importLine.replace(m[0], `from ${JSON.stringify(vendored)}`);
|
|
40199
|
-
}
|
|
40200
|
-
function buildClientChunk(rootDir, importLine, minify) {
|
|
40201
|
-
if (!importLine) return "";
|
|
40202
|
-
const cached = chunkCache.get(importLine);
|
|
40203
|
-
if (cached && existsSync7(join11(rootDir, ".tw", cached))) return cached;
|
|
40204
|
-
const jsDir = join11(rootDir, ".tw", "js");
|
|
40205
|
-
mkdirSync4(jsDir, { recursive: true });
|
|
40206
|
-
const names = importNames(importLine);
|
|
40207
|
-
const entryLine = rewriteRuntimeSpecifier(importLine);
|
|
40208
|
-
const entry = [
|
|
40209
|
-
entryLine + ";",
|
|
40210
|
-
"globalThis.__twClient = globalThis.__twClient || {};",
|
|
40211
|
-
...names.map((n) => "globalThis.__twClient." + n + " = " + n + ";")
|
|
40212
|
-
].join("\n");
|
|
40213
|
-
const stamp = contentHash2(importLine);
|
|
40214
|
-
const entryPath = join11(rootDir, ".tw", ".chunk-entry-" + stamp + ".js");
|
|
40215
|
-
const tmpOut = join11(rootDir, ".tw", ".chunk-tmp-" + stamp + ".js");
|
|
40216
|
-
writeFileSync4(entryPath, entry);
|
|
40217
|
-
const bin = findEsbuildBin(rootDir);
|
|
40218
|
-
const args = [entryPath, "--bundle", "--format=iife", "--target=es2019", "--outfile=" + tmpOut];
|
|
40219
|
-
if (minify) args.push("--minify");
|
|
40220
|
-
const r = spawnSync2(bin, args, { cwd: rootDir });
|
|
40221
|
-
try {
|
|
40222
|
-
unlinkSync2(entryPath);
|
|
40223
|
-
} catch {
|
|
40224
|
-
}
|
|
40225
|
-
if (r.status !== 0) {
|
|
40226
|
-
const errText = (r.stderr && r.stderr.toString ? r.stderr.toString() : "") || String(r.error || "unknown");
|
|
40227
|
-
console.error(" [tw] client chunk failed (" + importLine + "): " + errText.split("\n")[0]);
|
|
40228
|
-
try {
|
|
40229
|
-
unlinkSync2(tmpOut);
|
|
40230
|
-
} catch {
|
|
40231
|
-
}
|
|
40232
|
-
return "";
|
|
40233
|
-
}
|
|
40234
|
-
const code = readFileSync6(tmpOut, "utf-8");
|
|
40235
|
-
const url = "js/c-" + contentHash2(code) + ".js";
|
|
40236
|
-
writeFileSync4(join11(rootDir, ".tw", url), code);
|
|
40237
|
-
try {
|
|
40238
|
-
unlinkSync2(tmpOut);
|
|
40239
|
-
} catch {
|
|
40240
|
-
}
|
|
40241
|
-
chunkCache.set(importLine, url);
|
|
40242
|
-
return url;
|
|
40243
|
-
}
|
|
40244
|
-
function buildPageScope(rootDir, ownImportLines) {
|
|
40245
|
-
if (!ownImportLines || ownImportLines.length === 0) return "";
|
|
40246
|
-
const names = ownImportLines.flatMap((l) => importNames(l));
|
|
40247
|
-
if (names.length === 0) return "";
|
|
40248
|
-
mkdirSync4(join11(rootDir, ".tw", "js"), { recursive: true });
|
|
40249
|
-
const body = names.map((n) => "p." + n + " = g.__twClient." + n + ";").join("\n");
|
|
40250
|
-
const code = "(function(g){g.__twClient=g.__twClient||{};var p={};\n" + body + "\ng.__twClient.$page=p;})(globalThis);\n";
|
|
40251
|
-
const url = "js/p-" + contentHash2(names.slice().sort().join("|")) + ".js";
|
|
40252
|
-
writeFileSync4(join11(rootDir, ".tw", url), code);
|
|
40253
|
-
return url;
|
|
40254
|
-
}
|
|
40255
|
-
var IMPORT_RE, BUILTIN_CLIENT_OK_SPECIFIERS, RUNTIME_CLIENT_OK_SPECIFIERS, chunkCache;
|
|
40256
|
-
var init_client_bundle = __esm({
|
|
40257
|
-
"tw/commands/client-bundle.ts"() {
|
|
40258
|
-
IMPORT_RE = /^[ \t]*import\s+([^\n]+?)\s+from\s+["'']([^"'']+)["''][; \t]*$/gm;
|
|
40259
|
-
BUILTIN_CLIENT_OK_SPECIFIERS = ["@tw/optImage", "@tw/RouterLink"];
|
|
40260
|
-
RUNTIME_CLIENT_OK_SPECIFIERS = ["@tw/runtime"];
|
|
40261
|
-
chunkCache = /* @__PURE__ */ new Map();
|
|
40262
|
-
}
|
|
40263
|
-
});
|
|
40264
|
-
|
|
40265
|
-
// ../../packages/server/tw/routing/revalidate.ts
|
|
40266
|
-
function setActivePipeline(p) {
|
|
40267
|
-
activePipeline = p;
|
|
40268
|
-
}
|
|
40269
|
-
function getActivePipeline() {
|
|
40270
|
-
return activePipeline;
|
|
40271
|
-
}
|
|
40272
|
-
function revalidatePath(pathname, opts) {
|
|
40273
|
-
return activePipeline ? activePipeline.revalidatePath(pathname, opts) : 0;
|
|
40274
|
-
}
|
|
40275
|
-
function registerTwmTagInvalidator(fn) {
|
|
40276
|
-
twmTagInvalidator = fn;
|
|
40277
|
-
}
|
|
40278
|
-
function revalidateTag(tag) {
|
|
40279
|
-
let dropped = 0;
|
|
40280
|
-
if (activePipeline) dropped += activePipeline.revalidateTag(tag);
|
|
40281
|
-
if (twmTagInvalidator) dropped += twmTagInvalidator(tag);
|
|
40282
|
-
return dropped;
|
|
40283
|
-
}
|
|
40284
|
-
var activePipeline, twmTagInvalidator;
|
|
40285
|
-
var init_revalidate = __esm({
|
|
40286
|
-
"../../packages/server/tw/routing/revalidate.ts"() {
|
|
40287
|
-
activePipeline = null;
|
|
40288
|
-
twmTagInvalidator = null;
|
|
40289
|
-
}
|
|
40290
|
-
});
|
|
40291
|
-
|
|
40292
|
-
// ../../packages/server/tw/routing/signal-stream.ts
|
|
40293
|
-
function getSignalHub() {
|
|
40294
|
-
if (!hub) hub = new SignalHub();
|
|
40295
|
-
return hub;
|
|
40296
|
-
}
|
|
40297
|
-
function setSignal(name, value, opts) {
|
|
40298
|
-
let session = opts?.session;
|
|
40299
|
-
if (!session && opts?.request) {
|
|
40300
|
-
try {
|
|
40301
|
-
const cookies = opts.request.cookies;
|
|
40302
|
-
if (cookies && typeof cookies.get === "function") {
|
|
40303
|
-
session = cookies.get("tw_session") ?? void 0;
|
|
40304
|
-
} else {
|
|
40305
|
-
const header = opts.request.headers?.get?.("cookie") ?? "";
|
|
40306
|
-
const m = /(?:^|;\s*)tw_session=([^;]+)/.exec(header);
|
|
40307
|
-
if (m) session = decodeURIComponent(m[1].trim().replace(/^"|"$/g, ""));
|
|
40308
|
-
}
|
|
40309
|
-
} catch {
|
|
40310
|
-
}
|
|
40311
|
-
}
|
|
40312
|
-
return getSignalHub().setSignal(name, value, session ? { session } : void 0);
|
|
40313
|
-
}
|
|
40314
|
-
var BATCH_MS, HISTORY_LIMIT, SignalHub, hub;
|
|
40315
|
-
var init_signal_stream = __esm({
|
|
40316
|
-
"../../packages/server/tw/routing/signal-stream.ts"() {
|
|
40317
|
-
BATCH_MS = 100;
|
|
40318
|
-
HISTORY_LIMIT = 1e3;
|
|
40319
|
-
SignalHub = class {
|
|
40320
|
-
clients = /* @__PURE__ */ new Map();
|
|
40321
|
-
nextClientId = 1;
|
|
40322
|
-
seq = 0;
|
|
40323
|
-
values = /* @__PURE__ */ new Map();
|
|
40324
|
-
kinds = /* @__PURE__ */ new Map();
|
|
40325
|
-
history = [];
|
|
40326
|
-
pending = /* @__PURE__ */ new Map();
|
|
40327
|
-
pendingSession;
|
|
40328
|
-
timer = null;
|
|
40329
|
-
/**
|
|
40330
|
-
* Server-side mutation (route handlers): queue an update, batch-flush.
|
|
40331
|
-
* With `session` (derived from the request's tw_session cookie) private
|
|
40332
|
-
* signals are delivered only to clients authenticated as that session.
|
|
40333
|
-
*/
|
|
40334
|
-
setSignal(name, value, opts) {
|
|
40335
|
-
const kind = this.kinds.get(name) ?? "public";
|
|
40336
|
-
this.values.set(name, value);
|
|
40337
|
-
this.kinds.set(name, kind);
|
|
40338
|
-
this.pending.set(name, value);
|
|
40339
|
-
if (opts?.session) this.pendingSession = opts.session;
|
|
40340
|
-
if (this.timer == null) {
|
|
40341
|
-
this.timer = setTimeout(() => this.flush(), BATCH_MS);
|
|
40342
|
-
}
|
|
40343
|
-
return { queued: true, kind };
|
|
40344
|
-
}
|
|
40345
|
-
/** Latest known values (also used for fresh snapshots). */
|
|
40346
|
-
snapshot() {
|
|
40347
|
-
return { seq: this.seq, snapshot: Object.fromEntries(this.values) };
|
|
40348
|
-
}
|
|
40349
|
-
register(declared, session) {
|
|
40350
|
-
const id = this.nextClientId++;
|
|
40351
|
-
for (const [name, kind] of declared) {
|
|
40352
|
-
if (!this.kinds.has(name)) this.kinds.set(name, kind);
|
|
40353
|
-
}
|
|
40354
|
-
const client = {
|
|
40355
|
-
id,
|
|
40356
|
-
declared,
|
|
40357
|
-
session,
|
|
40358
|
-
send: () => {
|
|
40359
|
-
},
|
|
40360
|
-
close: () => this.clients.delete(id)
|
|
40361
|
-
};
|
|
40362
|
-
this.clients.set(id, client);
|
|
40363
|
-
return client;
|
|
40364
|
-
}
|
|
40365
|
-
/** Build the replay/snapshot preamble for a connecting client. */
|
|
40366
|
-
connectPayloads(since, declared, session) {
|
|
40367
|
-
const out = [];
|
|
40368
|
-
const hasCoverage = since <= this.seq && this.history.length > 0 && since >= this.history[0].seq - 1;
|
|
40369
|
-
if (hasCoverage) {
|
|
40370
|
-
for (const entry of this.history) {
|
|
40371
|
-
if (entry.seq > since && this.sessionMatches(session, entry) && this.deliversTo(declared, entry.names)) {
|
|
40372
|
-
out.push(entry.payload);
|
|
40373
|
-
}
|
|
40374
|
-
}
|
|
40375
|
-
} else if (this.values.size > 0) {
|
|
40376
|
-
const snap = {};
|
|
40377
|
-
for (const [name, value] of this.values) {
|
|
40378
|
-
if (this.sessionMatches(session, { names: [name], session: this.valueSessions.get(name) }) && this.deliversTo(declared, [name])) snap[name] = value;
|
|
40379
|
-
}
|
|
40380
|
-
if (Object.keys(snap).length > 0) {
|
|
40381
|
-
out.push(this.frame({ v: 1, seq: this.seq, snapshot: snap }));
|
|
40382
|
-
}
|
|
40383
|
-
}
|
|
40384
|
-
return out;
|
|
40385
|
-
}
|
|
40386
|
-
/** latest-known session per signal (for snapshot filtering) */
|
|
40387
|
-
valueSessions = /* @__PURE__ */ new Map();
|
|
40388
|
-
sessionMatches(clientSession, entry) {
|
|
40389
|
-
if (!entry.session) return true;
|
|
40390
|
-
return entry.session === clientSession;
|
|
40391
|
-
}
|
|
40392
|
-
deliversTo(declared, names) {
|
|
40393
|
-
return names.some((n) => {
|
|
40394
|
-
const kind = this.kinds.get(n) ?? "public";
|
|
40395
|
-
return kind === "public" || declared.has(n);
|
|
40396
|
-
});
|
|
40397
|
-
}
|
|
40398
|
-
flush() {
|
|
40399
|
-
this.timer = null;
|
|
40400
|
-
if (this.pending.size === 0) return;
|
|
40401
|
-
const updates = [...this.pending.entries()];
|
|
40402
|
-
const session = this.pendingSession;
|
|
40403
|
-
this.pending.clear();
|
|
40404
|
-
this.pendingSession = void 0;
|
|
40405
|
-
this.seq++;
|
|
40406
|
-
const names = updates.map(([n]) => n);
|
|
40407
|
-
for (const [n] of updates) this.valueSessions.set(n, session);
|
|
40408
|
-
const payload = this.frame({ v: 1, seq: this.seq, updates });
|
|
40409
|
-
this.history.push({ seq: this.seq, payload, names, session });
|
|
40410
|
-
if (this.history.length > HISTORY_LIMIT) this.history.shift();
|
|
40411
|
-
for (const client of this.clients.values()) {
|
|
40412
|
-
if (this.sessionMatches(client.session, { names, session }) && this.deliversTo(client.declared, names)) client.send(payload);
|
|
40413
|
-
}
|
|
40414
|
-
}
|
|
40415
|
-
frame(obj) {
|
|
40416
|
-
return JSON.stringify(obj).replace(/</g, "\\u003c");
|
|
40417
|
-
}
|
|
40418
|
-
};
|
|
40419
|
-
hub = null;
|
|
40420
|
-
}
|
|
40421
|
-
});
|
|
40422
|
-
|
|
40423
40664
|
// ../../packages/server/tw/routing/twm-rules.ts
|
|
40424
40665
|
var twm_rules_exports = {};
|
|
40425
40666
|
__export(twm_rules_exports, {
|
|
@@ -40538,19 +40779,35 @@ function verifyJwt_hs256(token, secret) {
|
|
|
40538
40779
|
return true;
|
|
40539
40780
|
}
|
|
40540
40781
|
function matchPath(pattern, pathname) {
|
|
40541
|
-
|
|
40542
|
-
|
|
40543
|
-
|
|
40544
|
-
|
|
40782
|
+
const norm = (p) => {
|
|
40783
|
+
let x = p;
|
|
40784
|
+
try {
|
|
40785
|
+
x = decodeURIComponent(x);
|
|
40786
|
+
} catch {
|
|
40787
|
+
}
|
|
40788
|
+
if (x.length > 1 && x.endsWith("/")) x = x.slice(0, -1);
|
|
40789
|
+
return x;
|
|
40790
|
+
};
|
|
40791
|
+
const pat = norm(pattern);
|
|
40792
|
+
const path = norm(pathname);
|
|
40793
|
+
if (pat === "/**") return true;
|
|
40794
|
+
if (pat === "/*") return path === "" || path === "/";
|
|
40795
|
+
if (pat.endsWith("/**")) {
|
|
40796
|
+
const prefix = pat.slice(0, -3);
|
|
40797
|
+
return path === prefix || path.startsWith(prefix + "/");
|
|
40545
40798
|
}
|
|
40546
|
-
if (
|
|
40547
|
-
const prefix =
|
|
40548
|
-
|
|
40799
|
+
if (pat.endsWith("/*")) {
|
|
40800
|
+
const prefix = pat.slice(0, -2);
|
|
40801
|
+
if (!path.startsWith(prefix + "/")) return path === prefix;
|
|
40802
|
+
const rest = path.slice(prefix.length + 1);
|
|
40803
|
+
return !rest.includes("/");
|
|
40549
40804
|
}
|
|
40550
|
-
return
|
|
40805
|
+
return path === pat;
|
|
40551
40806
|
}
|
|
40552
40807
|
function ruleBlocked(rule, request, pathname) {
|
|
40553
40808
|
const headers = request.headers;
|
|
40809
|
+
const hasCondition = rule.userAgent || rule.path || rule.auth || rule.rateLimit || rule.origin || rule.methods && rule.methods.length > 0;
|
|
40810
|
+
if (!hasCondition) return true;
|
|
40554
40811
|
if (rule.userAgent) {
|
|
40555
40812
|
const ua = (headers.get("user-agent") ?? "").toLowerCase();
|
|
40556
40813
|
if (!ua) {
|
|
@@ -40870,6 +41127,631 @@ var init_twm_rules = __esm({
|
|
|
40870
41127
|
}
|
|
40871
41128
|
});
|
|
40872
41129
|
|
|
41130
|
+
// ../../packages/shared/tw/node-import.ts
|
|
41131
|
+
var node_import_exports = {};
|
|
41132
|
+
__export(node_import_exports, {
|
|
41133
|
+
twImportTs: () => twImportTs
|
|
41134
|
+
});
|
|
41135
|
+
import { join as join10 } from "node:path";
|
|
41136
|
+
import { tmpdir } from "node:os";
|
|
41137
|
+
import { pathToFileURL as pathToFileURL2 } from "node:url";
|
|
41138
|
+
async function twImportTs(tsPath) {
|
|
41139
|
+
if (!globalThis.__TW_NODE) {
|
|
41140
|
+
return import(tsPath);
|
|
41141
|
+
}
|
|
41142
|
+
const esbuildMain = globalThis.__TW_ESBUILD_MODULE;
|
|
41143
|
+
const esbuild = await (esbuildMain ? import(esbuildMain) : import("esbuild"));
|
|
41144
|
+
const safe = tsPath.replace(/[^a-zA-Z0-9]/g, "_").slice(-120);
|
|
41145
|
+
const cachePath = join10(tmpdir(), "tw-" + safe + ".mjs");
|
|
41146
|
+
await esbuild.build({
|
|
41147
|
+
entryPoints: [tsPath],
|
|
41148
|
+
bundle: true,
|
|
41149
|
+
format: "esm",
|
|
41150
|
+
platform: "node",
|
|
41151
|
+
target: "node18",
|
|
41152
|
+
outfile: cachePath,
|
|
41153
|
+
logLevel: "silent"
|
|
41154
|
+
});
|
|
41155
|
+
return import(pathToFileURL2(cachePath).href);
|
|
41156
|
+
}
|
|
41157
|
+
var init_node_import = __esm({
|
|
41158
|
+
"../../packages/shared/tw/node-import.ts"() {
|
|
41159
|
+
}
|
|
41160
|
+
});
|
|
41161
|
+
|
|
41162
|
+
// tw/commands/client-bundle.ts
|
|
41163
|
+
var client_bundle_exports = {};
|
|
41164
|
+
__export(client_bundle_exports, {
|
|
41165
|
+
BUILTIN_CLIENT_OK_SPECIFIERS: () => BUILTIN_CLIENT_OK_SPECIFIERS,
|
|
41166
|
+
RUNTIME_CLIENT_OK_SPECIFIERS: () => RUNTIME_CLIENT_OK_SPECIFIERS,
|
|
41167
|
+
buildClientChunk: () => buildClientChunk,
|
|
41168
|
+
buildPageScope: () => buildPageScope,
|
|
41169
|
+
collectClientImportsFromSource: () => collectClientImportsFromSource,
|
|
41170
|
+
findServerOnlyViolations: () => findServerOnlyViolations,
|
|
41171
|
+
importNames: () => importNames,
|
|
41172
|
+
isClientImportSpecifier: () => isClientImportSpecifier
|
|
41173
|
+
});
|
|
41174
|
+
import { join as join11, resolve as resolve3 } from "node:path";
|
|
41175
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, unlinkSync as unlinkSync2 } from "node:fs";
|
|
41176
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
41177
|
+
function isClientImportSpecifier(spec) {
|
|
41178
|
+
if (spec.startsWith("@./") || spec.startsWith("./") || spec.startsWith("../")) return false;
|
|
41179
|
+
if (/\.(tw|tss|twm|ts|js)$/.test(spec)) return false;
|
|
41180
|
+
if (spec.startsWith("@tw/")) {
|
|
41181
|
+
return RUNTIME_CLIENT_OK_SPECIFIERS.includes(spec);
|
|
41182
|
+
}
|
|
41183
|
+
return true;
|
|
41184
|
+
}
|
|
41185
|
+
function collectClientImportsFromSource(src) {
|
|
41186
|
+
const lines = [];
|
|
41187
|
+
let m;
|
|
41188
|
+
IMPORT_RE.lastIndex = 0;
|
|
41189
|
+
while (m = IMPORT_RE.exec(src)) {
|
|
41190
|
+
if (isClientImportSpecifier(m[2])) lines.push(m[0].trim().replace(/;$/, ""));
|
|
41191
|
+
}
|
|
41192
|
+
return lines;
|
|
41193
|
+
}
|
|
41194
|
+
function importNames(line) {
|
|
41195
|
+
const clause = line.match(/^import\s+([\s\S]+?)\s+from/);
|
|
41196
|
+
if (!clause) return [];
|
|
41197
|
+
const c = clause[1].trim();
|
|
41198
|
+
const names = [];
|
|
41199
|
+
if (c.startsWith("*")) {
|
|
41200
|
+
const as = c.split(/\s+as\s+/);
|
|
41201
|
+
if (as[1]) names.push(as[1].trim());
|
|
41202
|
+
} else if (c.startsWith("{")) {
|
|
41203
|
+
const braces = c.match(/\{([\s\S]*)\}/);
|
|
41204
|
+
if (braces) {
|
|
41205
|
+
for (const part of braces[1].split(",")) {
|
|
41206
|
+
const p = part.trim();
|
|
41207
|
+
if (!p) continue;
|
|
41208
|
+
const seg = p.split(/\s+as\s+/);
|
|
41209
|
+
names.push(seg[seg.length - 1].trim());
|
|
41210
|
+
}
|
|
41211
|
+
}
|
|
41212
|
+
} else {
|
|
41213
|
+
const def = c.match(/^[A-Za-z_$][\w$]*/);
|
|
41214
|
+
if (def) names.push(def[0]);
|
|
41215
|
+
}
|
|
41216
|
+
return names;
|
|
41217
|
+
}
|
|
41218
|
+
function findEsbuildBin(rootDir) {
|
|
41219
|
+
const candidates = [
|
|
41220
|
+
join11(rootDir, "node_modules", ".bin", "esbuild"),
|
|
41221
|
+
resolve3(rootDir, "..", "node_modules", ".bin", "esbuild"),
|
|
41222
|
+
// CLI repo layout (dev / source runs)
|
|
41223
|
+
resolve3(new URL(".", import.meta.url).pathname, "..", "..", "..", "..", "node_modules", ".bin", "esbuild")
|
|
41224
|
+
];
|
|
41225
|
+
for (const c of candidates) {
|
|
41226
|
+
try {
|
|
41227
|
+
if (existsSync7(c)) return c;
|
|
41228
|
+
} catch {
|
|
41229
|
+
}
|
|
41230
|
+
}
|
|
41231
|
+
const bd = globalThis.__TW_BUNDLE_DIR;
|
|
41232
|
+
if (bd) {
|
|
41233
|
+
const b = resolve3(bd, "..", "..", "..", "node_modules", ".bin", "esbuild");
|
|
41234
|
+
try {
|
|
41235
|
+
if (existsSync7(b)) return b;
|
|
41236
|
+
} catch {
|
|
41237
|
+
}
|
|
41238
|
+
}
|
|
41239
|
+
return "esbuild";
|
|
41240
|
+
}
|
|
41241
|
+
function findServerOnlyViolations(src) {
|
|
41242
|
+
const out = [];
|
|
41243
|
+
let m;
|
|
41244
|
+
IMPORT_RE.lastIndex = 0;
|
|
41245
|
+
while (m = IMPORT_RE.exec(src)) {
|
|
41246
|
+
const spec = m[2];
|
|
41247
|
+
if (spec === "lib" || spec.startsWith("lib/")) {
|
|
41248
|
+
out.push(spec);
|
|
41249
|
+
continue;
|
|
41250
|
+
}
|
|
41251
|
+
if (isClientImportSpecifier(spec)) continue;
|
|
41252
|
+
if (spec.startsWith("@tw/")) {
|
|
41253
|
+
if (BUILTIN_CLIENT_OK_SPECIFIERS.includes(spec)) continue;
|
|
41254
|
+
if (RUNTIME_CLIENT_OK_SPECIFIERS.includes(spec)) continue;
|
|
41255
|
+
out.push(spec);
|
|
41256
|
+
continue;
|
|
41257
|
+
}
|
|
41258
|
+
if (spec.startsWith("@./") || spec.startsWith("./") || spec.startsWith("../")) {
|
|
41259
|
+
if (/\.(tw|tss)$/.test(spec)) continue;
|
|
41260
|
+
out.push(spec);
|
|
41261
|
+
continue;
|
|
41262
|
+
}
|
|
41263
|
+
if (spec.startsWith("lib/")) out.push(spec);
|
|
41264
|
+
}
|
|
41265
|
+
return out;
|
|
41266
|
+
}
|
|
41267
|
+
function contentHash2(s) {
|
|
41268
|
+
let h = 5381;
|
|
41269
|
+
for (let i = 0; i < s.length; i++) h = (h * 33 ^ s.charCodeAt(i)) >>> 0;
|
|
41270
|
+
return h.toString(36);
|
|
41271
|
+
}
|
|
41272
|
+
function vendoredRuntimeClient() {
|
|
41273
|
+
const fromSrc = new URL("../../dist/tw-runtime-client.mjs", import.meta.url).pathname;
|
|
41274
|
+
if (existsSync7(fromSrc)) return fromSrc;
|
|
41275
|
+
const fromBundle = new URL("./tw-runtime-client.mjs", import.meta.url).pathname;
|
|
41276
|
+
if (existsSync7(fromBundle)) return fromBundle;
|
|
41277
|
+
return null;
|
|
41278
|
+
}
|
|
41279
|
+
function rewriteRuntimeSpecifier(importLine) {
|
|
41280
|
+
const vendored = vendoredRuntimeClient();
|
|
41281
|
+
if (!vendored) return importLine;
|
|
41282
|
+
const m = importLine.match(/from\s+["'](@tw\/runtime)["']/);
|
|
41283
|
+
if (!m) return importLine;
|
|
41284
|
+
return importLine.replace(m[0], `from ${JSON.stringify(vendored)}`);
|
|
41285
|
+
}
|
|
41286
|
+
function buildClientChunk(rootDir, importLine, minify) {
|
|
41287
|
+
if (!importLine) return "";
|
|
41288
|
+
const cached = chunkCache.get(importLine);
|
|
41289
|
+
if (cached && existsSync7(join11(rootDir, ".tw", cached))) return cached;
|
|
41290
|
+
const jsDir = join11(rootDir, ".tw", "js");
|
|
41291
|
+
mkdirSync4(jsDir, { recursive: true });
|
|
41292
|
+
const names = importNames(importLine);
|
|
41293
|
+
const entryLine = rewriteRuntimeSpecifier(importLine);
|
|
41294
|
+
const entry = [
|
|
41295
|
+
entryLine + ";",
|
|
41296
|
+
"globalThis.__twClient = globalThis.__twClient || {};",
|
|
41297
|
+
...names.map((n) => "globalThis.__twClient." + n + " = " + n + ";")
|
|
41298
|
+
].join("\n");
|
|
41299
|
+
const stamp = contentHash2(importLine);
|
|
41300
|
+
const entryPath = join11(rootDir, ".tw", ".chunk-entry-" + stamp + ".js");
|
|
41301
|
+
const tmpOut = join11(rootDir, ".tw", ".chunk-tmp-" + stamp + ".js");
|
|
41302
|
+
writeFileSync4(entryPath, entry);
|
|
41303
|
+
const bin = findEsbuildBin(rootDir);
|
|
41304
|
+
const args = [entryPath, "--bundle", "--format=iife", "--target=es2019", "--outfile=" + tmpOut];
|
|
41305
|
+
if (minify) args.push("--minify");
|
|
41306
|
+
const r = spawnSync2(bin, args, { cwd: rootDir });
|
|
41307
|
+
try {
|
|
41308
|
+
unlinkSync2(entryPath);
|
|
41309
|
+
} catch {
|
|
41310
|
+
}
|
|
41311
|
+
if (r.status !== 0) {
|
|
41312
|
+
const errText = (r.stderr && r.stderr.toString ? r.stderr.toString() : "") || String(r.error || "unknown");
|
|
41313
|
+
console.error(" [tw] client chunk failed (" + importLine + "): " + errText.split("\n")[0]);
|
|
41314
|
+
try {
|
|
41315
|
+
unlinkSync2(tmpOut);
|
|
41316
|
+
} catch {
|
|
41317
|
+
}
|
|
41318
|
+
return "";
|
|
41319
|
+
}
|
|
41320
|
+
const code = readFileSync6(tmpOut, "utf-8");
|
|
41321
|
+
const url = "js/c-" + contentHash2(code) + ".js";
|
|
41322
|
+
writeFileSync4(join11(rootDir, ".tw", url), code);
|
|
41323
|
+
try {
|
|
41324
|
+
unlinkSync2(tmpOut);
|
|
41325
|
+
} catch {
|
|
41326
|
+
}
|
|
41327
|
+
chunkCache.set(importLine, url);
|
|
41328
|
+
return url;
|
|
41329
|
+
}
|
|
41330
|
+
function buildPageScope(rootDir, ownImportLines) {
|
|
41331
|
+
if (!ownImportLines || ownImportLines.length === 0) return "";
|
|
41332
|
+
const names = ownImportLines.flatMap((l) => importNames(l));
|
|
41333
|
+
if (names.length === 0) return "";
|
|
41334
|
+
mkdirSync4(join11(rootDir, ".tw", "js"), { recursive: true });
|
|
41335
|
+
const body = names.map((n) => "p." + n + " = g.__twClient." + n + ";").join("\n");
|
|
41336
|
+
const code = "(function(g){g.__twClient=g.__twClient||{};var p={};\n" + body + "\ng.__twClient.$page=p;})(globalThis);\n";
|
|
41337
|
+
const url = "js/p-" + contentHash2(names.slice().sort().join("|")) + ".js";
|
|
41338
|
+
writeFileSync4(join11(rootDir, ".tw", url), code);
|
|
41339
|
+
return url;
|
|
41340
|
+
}
|
|
41341
|
+
var IMPORT_RE, BUILTIN_CLIENT_OK_SPECIFIERS, RUNTIME_CLIENT_OK_SPECIFIERS, chunkCache;
|
|
41342
|
+
var init_client_bundle = __esm({
|
|
41343
|
+
"tw/commands/client-bundle.ts"() {
|
|
41344
|
+
IMPORT_RE = /^[ \t]*import\s+([^\n]+?)\s+from\s+["'']([^"'']+)["''][; \t]*$/gm;
|
|
41345
|
+
BUILTIN_CLIENT_OK_SPECIFIERS = ["@tw/optImage", "@tw/RouterLink"];
|
|
41346
|
+
RUNTIME_CLIENT_OK_SPECIFIERS = ["@tw/runtime"];
|
|
41347
|
+
chunkCache = /* @__PURE__ */ new Map();
|
|
41348
|
+
}
|
|
41349
|
+
});
|
|
41350
|
+
|
|
41351
|
+
// ../../packages/server/tw/routing/revalidate.ts
|
|
41352
|
+
function setActivePipeline(p) {
|
|
41353
|
+
activePipeline = p;
|
|
41354
|
+
}
|
|
41355
|
+
function getActivePipeline() {
|
|
41356
|
+
return activePipeline;
|
|
41357
|
+
}
|
|
41358
|
+
function revalidatePath(pathname, opts) {
|
|
41359
|
+
return activePipeline ? activePipeline.revalidatePath(pathname, opts) : 0;
|
|
41360
|
+
}
|
|
41361
|
+
function registerTwmTagInvalidator(fn) {
|
|
41362
|
+
twmTagInvalidator = fn;
|
|
41363
|
+
}
|
|
41364
|
+
function revalidateTag(tag) {
|
|
41365
|
+
let dropped = 0;
|
|
41366
|
+
if (activePipeline) dropped += activePipeline.revalidateTag(tag);
|
|
41367
|
+
if (twmTagInvalidator) dropped += twmTagInvalidator(tag);
|
|
41368
|
+
return dropped;
|
|
41369
|
+
}
|
|
41370
|
+
var activePipeline, twmTagInvalidator;
|
|
41371
|
+
var init_revalidate = __esm({
|
|
41372
|
+
"../../packages/server/tw/routing/revalidate.ts"() {
|
|
41373
|
+
activePipeline = null;
|
|
41374
|
+
twmTagInvalidator = null;
|
|
41375
|
+
}
|
|
41376
|
+
});
|
|
41377
|
+
|
|
41378
|
+
// ../../packages/server/tw/routing/signal-stream.ts
|
|
41379
|
+
var signal_stream_exports = {};
|
|
41380
|
+
__export(signal_stream_exports, {
|
|
41381
|
+
SignalHub: () => SignalHub,
|
|
41382
|
+
createSignalStream: () => createSignalStream,
|
|
41383
|
+
getSignalHub: () => getSignalHub,
|
|
41384
|
+
setSignal: () => setSignal,
|
|
41385
|
+
sseFrameWithId: () => sseFrameWithId,
|
|
41386
|
+
writeSignalFromClient: () => writeSignalFromClient
|
|
41387
|
+
});
|
|
41388
|
+
function getSignalHub() {
|
|
41389
|
+
if (!hub) hub = new SignalHub();
|
|
41390
|
+
return hub;
|
|
41391
|
+
}
|
|
41392
|
+
function setSignal(name, value, opts) {
|
|
41393
|
+
let session = opts?.session;
|
|
41394
|
+
if (!session && opts?.request) {
|
|
41395
|
+
try {
|
|
41396
|
+
const cookies = opts.request.cookies;
|
|
41397
|
+
if (cookies && typeof cookies.get === "function") {
|
|
41398
|
+
session = cookies.get("tw_session") ?? void 0;
|
|
41399
|
+
} else {
|
|
41400
|
+
const header = opts.request.headers?.get?.("cookie") ?? "";
|
|
41401
|
+
const m = /(?:^|;\s*)tw_session=([^;]+)/.exec(header);
|
|
41402
|
+
if (m) session = decodeURIComponent(m[1].trim().replace(/^"|"$/g, ""));
|
|
41403
|
+
}
|
|
41404
|
+
} catch {
|
|
41405
|
+
}
|
|
41406
|
+
}
|
|
41407
|
+
return getSignalHub().setSignal(name, value, session ? { session } : void 0);
|
|
41408
|
+
}
|
|
41409
|
+
function sseFrameWithId(payload) {
|
|
41410
|
+
try {
|
|
41411
|
+
const seq = JSON.parse(payload).seq;
|
|
41412
|
+
if (typeof seq === "number") return "id: " + seq + "\ndata: " + payload + "\n\n";
|
|
41413
|
+
} catch {
|
|
41414
|
+
}
|
|
41415
|
+
return "data: " + payload + "\n\n";
|
|
41416
|
+
}
|
|
41417
|
+
function createSignalStream(request, url, opts) {
|
|
41418
|
+
const hub2 = getSignalHub();
|
|
41419
|
+
if (opts && !hub2.__cfg) {
|
|
41420
|
+
hub2.__cfg = true;
|
|
41421
|
+
try {
|
|
41422
|
+
hub2.configure({ maxClients: opts.maxClients, maxHistory: opts.maxHistory });
|
|
41423
|
+
} catch {
|
|
41424
|
+
}
|
|
41425
|
+
}
|
|
41426
|
+
if (url.searchParams.get("stats")) {
|
|
41427
|
+
return new Response(JSON.stringify(hub2.getStats(), null, 2), {
|
|
41428
|
+
status: 200,
|
|
41429
|
+
headers: { "Content-Type": "application/json; charset=utf-8", "Cache-Control": "no-store" }
|
|
41430
|
+
});
|
|
41431
|
+
}
|
|
41432
|
+
if (!hub2.canAcceptClient()) {
|
|
41433
|
+
return new Response("Too many signal-stream connections", {
|
|
41434
|
+
status: 503,
|
|
41435
|
+
headers: { "Content-Type": "text/plain", "Retry-After": "5" }
|
|
41436
|
+
});
|
|
41437
|
+
}
|
|
41438
|
+
const names = (url.searchParams.get("s") ?? "").split(",").map((x) => x.trim()).filter(Boolean);
|
|
41439
|
+
let since = Number(url.searchParams.get("since") ?? "0") || 0;
|
|
41440
|
+
if (!since) {
|
|
41441
|
+
const lei = Number(request.headers.get("last-event-id") ?? "0") || 0;
|
|
41442
|
+
if (lei) since = lei;
|
|
41443
|
+
}
|
|
41444
|
+
const declared = /* @__PURE__ */ new Map();
|
|
41445
|
+
for (const n of names) declared.set(n, "public");
|
|
41446
|
+
let session;
|
|
41447
|
+
try {
|
|
41448
|
+
const header = request.headers.get("cookie") ?? "";
|
|
41449
|
+
const m = /(?:^|;\s*)tw_session=([^;]+)/.exec(header);
|
|
41450
|
+
if (m) session = decodeURIComponent(m[1].trim().replace(/^"|"$/g, ""));
|
|
41451
|
+
} catch {
|
|
41452
|
+
}
|
|
41453
|
+
const enc = new TextEncoder();
|
|
41454
|
+
const sse = (payload) => enc.encode(sseFrameWithId(payload));
|
|
41455
|
+
let registered = null;
|
|
41456
|
+
let keepAlive = null;
|
|
41457
|
+
const stream = new ReadableStream({
|
|
41458
|
+
start(controller) {
|
|
41459
|
+
const client = hub2.register(declared, session);
|
|
41460
|
+
registered = client;
|
|
41461
|
+
const cleanup = () => {
|
|
41462
|
+
if (keepAlive) clearInterval(keepAlive);
|
|
41463
|
+
hub2.unregister(client.id);
|
|
41464
|
+
try {
|
|
41465
|
+
controller.close();
|
|
41466
|
+
} catch {
|
|
41467
|
+
}
|
|
41468
|
+
};
|
|
41469
|
+
client.send = (payload) => {
|
|
41470
|
+
try {
|
|
41471
|
+
controller.enqueue(sse(payload));
|
|
41472
|
+
} catch {
|
|
41473
|
+
cleanup();
|
|
41474
|
+
}
|
|
41475
|
+
};
|
|
41476
|
+
client.close = cleanup;
|
|
41477
|
+
for (const payload of hub2.connectPayloads(since, declared, session)) {
|
|
41478
|
+
try {
|
|
41479
|
+
controller.enqueue(sse(payload));
|
|
41480
|
+
} catch {
|
|
41481
|
+
break;
|
|
41482
|
+
}
|
|
41483
|
+
}
|
|
41484
|
+
keepAlive = setInterval(() => {
|
|
41485
|
+
try {
|
|
41486
|
+
controller.enqueue(enc.encode(": keep-alive\n\n"));
|
|
41487
|
+
} catch {
|
|
41488
|
+
cleanup();
|
|
41489
|
+
}
|
|
41490
|
+
}, 25e3);
|
|
41491
|
+
},
|
|
41492
|
+
// v2: when the browser drops the connection (tab close, network
|
|
41493
|
+
// loss) cancel fires -- remove THIS hub entry immediately instead
|
|
41494
|
+
// of waiting for the next failed send.
|
|
41495
|
+
cancel() {
|
|
41496
|
+
if (keepAlive) clearInterval(keepAlive);
|
|
41497
|
+
try {
|
|
41498
|
+
registered?.close?.();
|
|
41499
|
+
} catch {
|
|
41500
|
+
}
|
|
41501
|
+
}
|
|
41502
|
+
});
|
|
41503
|
+
return new Response(stream, {
|
|
41504
|
+
status: 200,
|
|
41505
|
+
headers: {
|
|
41506
|
+
"Content-Type": "text/event-stream; charset=utf-8",
|
|
41507
|
+
"Cache-Control": "no-store",
|
|
41508
|
+
"X-Accel-Buffering": "no",
|
|
41509
|
+
Connection: "keep-alive"
|
|
41510
|
+
}
|
|
41511
|
+
});
|
|
41512
|
+
}
|
|
41513
|
+
async function writeSignalFromClient(request, opts) {
|
|
41514
|
+
if (!opts?.clientWrites) {
|
|
41515
|
+
return new Response(JSON.stringify({ error: "client signal writes are disabled" }), {
|
|
41516
|
+
status: 403,
|
|
41517
|
+
headers: { "Content-Type": "application/json" }
|
|
41518
|
+
});
|
|
41519
|
+
}
|
|
41520
|
+
let body = null;
|
|
41521
|
+
try {
|
|
41522
|
+
body = await request.json();
|
|
41523
|
+
} catch {
|
|
41524
|
+
}
|
|
41525
|
+
const name = body?.name;
|
|
41526
|
+
if (typeof name !== "string" || !("value" in (body ?? {}))) {
|
|
41527
|
+
return new Response(JSON.stringify({ error: "body must be { name, value }" }), {
|
|
41528
|
+
status: 400,
|
|
41529
|
+
headers: { "Content-Type": "application/json" }
|
|
41530
|
+
});
|
|
41531
|
+
}
|
|
41532
|
+
const hub2 = getSignalHub();
|
|
41533
|
+
const kind = (hub2.getStats().signals ?? {})[name];
|
|
41534
|
+
if (kind && kind !== "public") {
|
|
41535
|
+
return new Response(JSON.stringify({ error: "only public signals are client-writable" }), {
|
|
41536
|
+
status: 403,
|
|
41537
|
+
headers: { "Content-Type": "application/json" }
|
|
41538
|
+
});
|
|
41539
|
+
}
|
|
41540
|
+
const res = hub2.setSignal(name, body.value);
|
|
41541
|
+
if (!res.queued) {
|
|
41542
|
+
return new Response(JSON.stringify({ error: res.error ?? "rejected" }), {
|
|
41543
|
+
status: 400,
|
|
41544
|
+
headers: { "Content-Type": "application/json" }
|
|
41545
|
+
});
|
|
41546
|
+
}
|
|
41547
|
+
return new Response(JSON.stringify({ ok: true, kind: res.kind }), {
|
|
41548
|
+
status: 202,
|
|
41549
|
+
headers: { "Content-Type": "application/json" }
|
|
41550
|
+
});
|
|
41551
|
+
}
|
|
41552
|
+
var BATCH_MS, HISTORY_LIMIT, MAX_CLIENTS, MAX_VALUE_BYTES, MAX_NAME_LEN, NAME_RE, SignalHub, hub;
|
|
41553
|
+
var init_signal_stream = __esm({
|
|
41554
|
+
"../../packages/server/tw/routing/signal-stream.ts"() {
|
|
41555
|
+
BATCH_MS = 100;
|
|
41556
|
+
HISTORY_LIMIT = 1e3;
|
|
41557
|
+
MAX_CLIENTS = 500;
|
|
41558
|
+
MAX_VALUE_BYTES = 64 * 1024;
|
|
41559
|
+
MAX_NAME_LEN = 64;
|
|
41560
|
+
NAME_RE = /^[A-Za-z0-9_$.-]{1,64}$/;
|
|
41561
|
+
SignalHub = class {
|
|
41562
|
+
clients = /* @__PURE__ */ new Map();
|
|
41563
|
+
nextClientId = 1;
|
|
41564
|
+
seq = 0;
|
|
41565
|
+
values = /* @__PURE__ */ new Map();
|
|
41566
|
+
kinds = /* @__PURE__ */ new Map();
|
|
41567
|
+
history = [];
|
|
41568
|
+
pending = /* @__PURE__ */ new Map();
|
|
41569
|
+
pendingSession;
|
|
41570
|
+
timer = null;
|
|
41571
|
+
// v2 stats + caps
|
|
41572
|
+
maxClients = MAX_CLIENTS;
|
|
41573
|
+
createdAt = Date.now();
|
|
41574
|
+
totalUpdates = 0;
|
|
41575
|
+
batchesFlushed = 0;
|
|
41576
|
+
rejectedCount = 0;
|
|
41577
|
+
/** v2: configure caps (from tw.config.ts signalStream options). */
|
|
41578
|
+
configure(opts) {
|
|
41579
|
+
if (opts?.maxClients && opts.maxClients > 0) this.maxClients = opts.maxClients;
|
|
41580
|
+
if (opts?.maxHistory && opts.maxHistory > 0) this.historyLimit = opts.maxHistory;
|
|
41581
|
+
}
|
|
41582
|
+
historyLimit = HISTORY_LIMIT;
|
|
41583
|
+
/** v2: observability for /_tw/stream?stats=1 and window.__tw stats. */
|
|
41584
|
+
getStats() {
|
|
41585
|
+
const signals = {};
|
|
41586
|
+
for (const [n, k] of this.kinds) signals[n] = k;
|
|
41587
|
+
return {
|
|
41588
|
+
clients: this.clients.size,
|
|
41589
|
+
clientCap: this.maxClients,
|
|
41590
|
+
seq: this.seq,
|
|
41591
|
+
signals,
|
|
41592
|
+
valueCount: this.values.size,
|
|
41593
|
+
history: this.history.length,
|
|
41594
|
+
historyCap: this.historyLimit,
|
|
41595
|
+
pending: this.pending.size,
|
|
41596
|
+
totalUpdates: this.totalUpdates,
|
|
41597
|
+
batchesFlushed: this.batchesFlushed,
|
|
41598
|
+
rejected: this.rejectedCount,
|
|
41599
|
+
uptimeMs: Date.now() - this.createdAt
|
|
41600
|
+
};
|
|
41601
|
+
}
|
|
41602
|
+
/** v2: graceful shutdown -- close every connected stream now. */
|
|
41603
|
+
closeAll() {
|
|
41604
|
+
for (const c of [...this.clients.values()]) {
|
|
41605
|
+
try {
|
|
41606
|
+
c.close();
|
|
41607
|
+
} catch {
|
|
41608
|
+
}
|
|
41609
|
+
}
|
|
41610
|
+
this.clients.clear();
|
|
41611
|
+
if (this.timer != null) {
|
|
41612
|
+
clearTimeout(this.timer);
|
|
41613
|
+
this.timer = null;
|
|
41614
|
+
}
|
|
41615
|
+
this.pending.clear();
|
|
41616
|
+
}
|
|
41617
|
+
/** v2: connection cap check BEFORE registering a stream client. */
|
|
41618
|
+
canAcceptClient() {
|
|
41619
|
+
return this.clients.size < this.maxClients;
|
|
41620
|
+
}
|
|
41621
|
+
/** v2: validate a setSignal name/value without throwing. */
|
|
41622
|
+
validateSignal(name, value) {
|
|
41623
|
+
if (typeof name !== "string" || name.length === 0 || name.length > MAX_NAME_LEN) {
|
|
41624
|
+
return "signal name must be 1-" + MAX_NAME_LEN + " chars";
|
|
41625
|
+
}
|
|
41626
|
+
if (!NAME_RE.test(name)) return "signal name has invalid characters";
|
|
41627
|
+
if (value === void 0) return "signal value is undefined";
|
|
41628
|
+
try {
|
|
41629
|
+
const size = typeof value === "string" ? value.length : JSON.stringify(value)?.length ?? 0;
|
|
41630
|
+
if (size > MAX_VALUE_BYTES) return "signal value exceeds " + MAX_VALUE_BYTES + " bytes";
|
|
41631
|
+
} catch {
|
|
41632
|
+
return "signal value is not serializable";
|
|
41633
|
+
}
|
|
41634
|
+
return null;
|
|
41635
|
+
}
|
|
41636
|
+
/**
|
|
41637
|
+
* Server-side mutation (route handlers): queue an update, batch-flush.
|
|
41638
|
+
* With `session` (derived from the request's tw_session cookie) private
|
|
41639
|
+
* signals are delivered only to clients authenticated as that session.
|
|
41640
|
+
*/
|
|
41641
|
+
setSignal(name, value, opts) {
|
|
41642
|
+
const invalid = this.validateSignal(name, value);
|
|
41643
|
+
if (invalid) {
|
|
41644
|
+
this.rejectedCount++;
|
|
41645
|
+
return { queued: false, kind: this.kinds.get(name), error: invalid };
|
|
41646
|
+
}
|
|
41647
|
+
const kind = this.kinds.get(name) ?? "public";
|
|
41648
|
+
this.values.set(name, value);
|
|
41649
|
+
this.kinds.set(name, kind);
|
|
41650
|
+
this.pending.set(name, value);
|
|
41651
|
+
if (opts?.session) this.pendingSession = opts.session;
|
|
41652
|
+
if (this.timer == null) {
|
|
41653
|
+
this.timer = setTimeout(() => this.flush(), BATCH_MS);
|
|
41654
|
+
}
|
|
41655
|
+
return { queued: true, kind };
|
|
41656
|
+
}
|
|
41657
|
+
/** Latest known values (also used for fresh snapshots). */
|
|
41658
|
+
snapshot() {
|
|
41659
|
+
return { seq: this.seq, snapshot: Object.fromEntries(this.values) };
|
|
41660
|
+
}
|
|
41661
|
+
register(declared, session) {
|
|
41662
|
+
const id = this.nextClientId++;
|
|
41663
|
+
for (const [name, kind] of declared) {
|
|
41664
|
+
if (!this.kinds.has(name)) this.kinds.set(name, kind);
|
|
41665
|
+
}
|
|
41666
|
+
const client = {
|
|
41667
|
+
id,
|
|
41668
|
+
declared,
|
|
41669
|
+
session,
|
|
41670
|
+
send: () => {
|
|
41671
|
+
},
|
|
41672
|
+
close: () => this.clients.delete(id)
|
|
41673
|
+
};
|
|
41674
|
+
this.clients.set(id, client);
|
|
41675
|
+
return client;
|
|
41676
|
+
}
|
|
41677
|
+
/** v2: remove a stream client (its connection died / was cancelled).
|
|
41678
|
+
* The old handler overwrote client.close() with a closure that only
|
|
41679
|
+
* closed the controller -- the Map entry leaked forever, so every
|
|
41680
|
+
* disconnected browser still occupied a slot (and got flushed frames).
|
|
41681
|
+
*/
|
|
41682
|
+
unregister(id) {
|
|
41683
|
+
this.clients.delete(id);
|
|
41684
|
+
}
|
|
41685
|
+
/** Build the replay/snapshot preamble for a connecting client. */
|
|
41686
|
+
connectPayloads(since, declared, session) {
|
|
41687
|
+
const out = [];
|
|
41688
|
+
if (since <= 0 && this.values.size > 0) {
|
|
41689
|
+
const snap = {};
|
|
41690
|
+
for (const [name, value] of this.values) {
|
|
41691
|
+
if (this.sessionMatches(session, { names: [name], session: this.valueSessions.get(name) }) && this.deliversTo(declared, [name])) snap[name] = value;
|
|
41692
|
+
}
|
|
41693
|
+
if (Object.keys(snap).length > 0) {
|
|
41694
|
+
out.push(this.frame({ v: 1, seq: this.seq, snapshot: snap }));
|
|
41695
|
+
}
|
|
41696
|
+
return out;
|
|
41697
|
+
}
|
|
41698
|
+
const hasCoverage = since <= this.seq && this.history.length > 0 && since >= this.history[0].seq - 1;
|
|
41699
|
+
if (hasCoverage) {
|
|
41700
|
+
for (const entry of this.history) {
|
|
41701
|
+
if (entry.seq > since && this.sessionMatches(session, entry) && this.deliversTo(declared, entry.names)) {
|
|
41702
|
+
out.push(entry.payload);
|
|
41703
|
+
}
|
|
41704
|
+
}
|
|
41705
|
+
} else if (this.values.size > 0) {
|
|
41706
|
+
const snap = {};
|
|
41707
|
+
for (const [name, value] of this.values) {
|
|
41708
|
+
if (this.sessionMatches(session, { names: [name], session: this.valueSessions.get(name) }) && this.deliversTo(declared, [name])) snap[name] = value;
|
|
41709
|
+
}
|
|
41710
|
+
if (Object.keys(snap).length > 0) {
|
|
41711
|
+
out.push(this.frame({ v: 1, seq: this.seq, snapshot: snap }));
|
|
41712
|
+
}
|
|
41713
|
+
}
|
|
41714
|
+
return out;
|
|
41715
|
+
}
|
|
41716
|
+
/** latest-known session per signal (for snapshot filtering) */
|
|
41717
|
+
valueSessions = /* @__PURE__ */ new Map();
|
|
41718
|
+
sessionMatches(clientSession, entry) {
|
|
41719
|
+
if (!entry.session) return true;
|
|
41720
|
+
return entry.session === clientSession;
|
|
41721
|
+
}
|
|
41722
|
+
deliversTo(declared, names) {
|
|
41723
|
+
return names.some((n) => {
|
|
41724
|
+
const kind = this.kinds.get(n) ?? "public";
|
|
41725
|
+
return kind === "public" || declared.has(n);
|
|
41726
|
+
});
|
|
41727
|
+
}
|
|
41728
|
+
flush() {
|
|
41729
|
+
this.timer = null;
|
|
41730
|
+
if (this.pending.size === 0) return;
|
|
41731
|
+
const updates = [...this.pending.entries()];
|
|
41732
|
+
const session = this.pendingSession;
|
|
41733
|
+
this.pending.clear();
|
|
41734
|
+
this.pendingSession = void 0;
|
|
41735
|
+
this.seq++;
|
|
41736
|
+
const names = updates.map(([n]) => n);
|
|
41737
|
+
for (const [n] of updates) this.valueSessions.set(n, session);
|
|
41738
|
+
const payload = this.frame({ v: 1, seq: this.seq, updates });
|
|
41739
|
+
this.history.push({ seq: this.seq, payload, names, session });
|
|
41740
|
+
this.totalUpdates += updates.length;
|
|
41741
|
+
this.batchesFlushed++;
|
|
41742
|
+
if (this.history.length > this.historyLimit) this.history.shift();
|
|
41743
|
+
for (const client of this.clients.values()) {
|
|
41744
|
+
if (this.sessionMatches(client.session, { names, session }) && this.deliversTo(client.declared, names)) client.send(payload);
|
|
41745
|
+
}
|
|
41746
|
+
}
|
|
41747
|
+
frame(obj) {
|
|
41748
|
+
return JSON.stringify(obj).replace(/</g, "\\u003c");
|
|
41749
|
+
}
|
|
41750
|
+
};
|
|
41751
|
+
hub = null;
|
|
41752
|
+
}
|
|
41753
|
+
});
|
|
41754
|
+
|
|
40873
41755
|
// ../../packages/server/tw/websocket-manager.ts
|
|
40874
41756
|
var DEFAULT_MAX_ROOMS, DEFAULT_MAX_ROOM_SIZE, WebSocketManager;
|
|
40875
41757
|
var init_websocket_manager = __esm({
|
|
@@ -41096,7 +41978,8 @@ function extractRenderMode(page) {
|
|
|
41096
41978
|
try {
|
|
41097
41979
|
const fs = __require("node:fs");
|
|
41098
41980
|
const source = fs.readFileSync(page.absolutePath, "utf-8");
|
|
41099
|
-
const
|
|
41981
|
+
const { maskSourceStringsAndComments: maskSourceStringsAndComments2 } = (init_tw(), __toCommonJS(tw_exports));
|
|
41982
|
+
const m = maskSourceStringsAndComments2(source).match(/render\s+(static|ssr|island|edge)/);
|
|
41100
41983
|
if (m) {
|
|
41101
41984
|
return m[1];
|
|
41102
41985
|
}
|
|
@@ -41543,6 +42426,7 @@ var init_render_pipeline = __esm({
|
|
|
41543
42426
|
init_twm_loader();
|
|
41544
42427
|
init_scanner();
|
|
41545
42428
|
init_layout_chain();
|
|
42429
|
+
init_signal_stream();
|
|
41546
42430
|
init_helpers();
|
|
41547
42431
|
init_tw6();
|
|
41548
42432
|
init_twm_loader();
|
|
@@ -41696,6 +42580,17 @@ var init_render_pipeline = __esm({
|
|
|
41696
42580
|
try {
|
|
41697
42581
|
const pageState = { ...stateVars ?? {}, ...match.params ?? {}, params: match.params ?? {} };
|
|
41698
42582
|
const requestVarKeys = new Set(Object.keys({ ...stateVars ?? {}, ...match.params ?? {} }));
|
|
42583
|
+
try {
|
|
42584
|
+
const hub2 = getSignalHub();
|
|
42585
|
+
const snap = hub2.snapshot();
|
|
42586
|
+
if (snap && snap.snapshot && typeof snap.snapshot === "object") {
|
|
42587
|
+
for (const [k, v] of Object.entries(snap.snapshot)) {
|
|
42588
|
+
pageState[k] = v;
|
|
42589
|
+
requestVarKeys.add(k);
|
|
42590
|
+
}
|
|
42591
|
+
}
|
|
42592
|
+
} catch {
|
|
42593
|
+
}
|
|
41699
42594
|
const pageCompiled = this.compileFile(ctx.page, pageState);
|
|
41700
42595
|
{
|
|
41701
42596
|
const seed = pageCompiled.stateSeed;
|
|
@@ -41741,24 +42636,30 @@ var init_render_pipeline = __esm({
|
|
|
41741
42636
|
let layoutTitle;
|
|
41742
42637
|
const liftedParts = [];
|
|
41743
42638
|
{
|
|
41744
|
-
|
|
42639
|
+
const collectedTitles = [];
|
|
42640
|
+
for (const headMatch of bodyHTML.matchAll(/<head(?:\s[^>]*)?>([\s\S]*?)<\/head\s*>/gi)) {
|
|
41745
42641
|
const headInner = headMatch[1];
|
|
41746
42642
|
const tMatch = headInner.match(/<title>([^<]*)<\/title>/i);
|
|
41747
|
-
if (tMatch)
|
|
41748
|
-
|
|
42643
|
+
if (tMatch) collectedTitles.push(tMatch[1].trim().replace(/&(amp|lt|gt|quot);/g, (m, k) => k === "amp" ? String.fromCharCode(38) : k === "lt" ? String.fromCharCode(60) : k === "gt" ? String.fromCharCode(62) : String.fromCharCode(34)));
|
|
42644
|
+
else collectedTitles.push("");
|
|
42645
|
+
const kids = headInner.replace(/<title>[^<]*<\/title>/gi, "").replace(/<meta[^>]+charset[^>]*>/gi, "").replace(/<meta[^>]+viewport[^>]*>/gi, "").replace(/<meta[^>]+generator[^>]*>/gi, "").trim();
|
|
41749
42646
|
if (kids) liftedParts.push(kids);
|
|
41750
42647
|
}
|
|
41751
|
-
bodyHTML = bodyHTML.replace(/<head[^>]
|
|
42648
|
+
bodyHTML = bodyHTML.replace(/<head(?:\s[^>]*)?>[\s\S]*?<\/head\s*>/gi, "");
|
|
42649
|
+
const templateTitle = collectedTitles.find((t2) => t2.includes("__TW_PAGETITLE__"));
|
|
42650
|
+
layoutTitle = templateTitle ?? collectedTitles.filter(Boolean).pop();
|
|
41752
42651
|
bodyHTML = bodyHTML.replace(/<\/?(?:html|body)[^>]*>/gi, "");
|
|
41753
42652
|
bodyHTML = bodyHTML.replace(/<!DOCTYPE[^>]*>/gi, "");
|
|
41754
42653
|
}
|
|
41755
42654
|
const liftedHead = liftedParts.join("\n");
|
|
41756
|
-
|
|
42655
|
+
const liveRenderMode = this.extractRenderMode(ctx.page);
|
|
42656
|
+
if (ctx.error && (liveRenderMode === "ssr" || liveRenderMode === "stream")) {
|
|
41757
42657
|
const errorCompiled = this.compileFile(ctx.error);
|
|
41758
42658
|
allCSS = errorCompiled.css + "\n" + allCSS;
|
|
41759
42659
|
bodyHTML = `<div data-error-boundary>${bodyHTML}</div>`;
|
|
41760
42660
|
}
|
|
41761
|
-
|
|
42661
|
+
const needsSkeleton = bodyHTML.includes("data-tw-suspense") || bodyHTML.includes("data-tw-ppr");
|
|
42662
|
+
if (ctx.loading && needsSkeleton) {
|
|
41762
42663
|
const loadingCompiled = this.compileFile(ctx.loading);
|
|
41763
42664
|
allCSS = loadingCompiled.css + "\n" + allCSS;
|
|
41764
42665
|
let lh = String(loadingCompiled.html || "");
|
|
@@ -41770,7 +42671,7 @@ ${bodyHTML}`;
|
|
|
41770
42671
|
const pageTitle = this.extractTitle(ctx.page) || "TW Page";
|
|
41771
42672
|
let title = pageTitle;
|
|
41772
42673
|
if (layoutTitle !== void 0) {
|
|
41773
|
-
title = layoutTitle.replace(/\{\s*page\.title\s*\}/g, pageTitle);
|
|
42674
|
+
title = layoutTitle.replace(/__TW_PAGETITLE__/g, pageTitle).replace(/\{\s*page\.title\s*\}/g, pageTitle);
|
|
41774
42675
|
if (!title.trim()) title = pageTitle;
|
|
41775
42676
|
}
|
|
41776
42677
|
const metaTags = this.buildMetaTags(ctx.page);
|
|
@@ -41784,13 +42685,15 @@ ${bodyHTML}`;
|
|
|
41784
42685
|
for (const hp of headCandidates) {
|
|
41785
42686
|
const headCompiled = this.compileFile({ absolutePath: hp, type: "head" });
|
|
41786
42687
|
const m = headCompiled.html.match(/<body>([\s\S]*)<\/body>/);
|
|
41787
|
-
if (m && m[1].trim()) headExtra += m[1].trim() + "\n";
|
|
42688
|
+
if (m && m[1].trim()) headExtra += m[1].trim().replace(/\\([{}])/g, "$1") + "\n";
|
|
41788
42689
|
}
|
|
41789
42690
|
if (liftedHead) headExtra += liftedHead + "\n";
|
|
41790
42691
|
} catch {
|
|
41791
42692
|
}
|
|
41792
42693
|
let fullHTML = this.assembleHTML(bodyHTML, allCSS, allJS, title, metaTags + headExtra);
|
|
41793
|
-
|
|
42694
|
+
const pageRenderMode = this.extractRenderMode(ctx.page);
|
|
42695
|
+
const needsClientRuntime = /data-tw-event|data-tw-i|__TW_SIGNALS__|data-tw-suspense|data-tw-ppr/.test(fullHTML) || pageRenderMode === "csr" || pageRenderMode === "stream" || pageRenderMode === "ppr" || pageRenderMode === "signalStream";
|
|
42696
|
+
if (needsClientRuntime && !/__tw_runtime\.js/.test(fullHTML)) {
|
|
41794
42697
|
let stateSeed = "{}";
|
|
41795
42698
|
try {
|
|
41796
42699
|
stateSeed = JSON.stringify(pageState ?? {});
|
|
@@ -41909,7 +42812,8 @@ ${bodyHTML}`;
|
|
|
41909
42812
|
throw new Error(`Route file not found: ${file.absolutePath}`);
|
|
41910
42813
|
}
|
|
41911
42814
|
const source = readFileSync7(file.absolutePath, "utf-8");
|
|
41912
|
-
const
|
|
42815
|
+
const srcWithSentinel = source.replace(/\{\s*page\.title\s*\}/g, "__TW_PAGETITLE__");
|
|
42816
|
+
const result = compileSync(srcWithSentinel, {
|
|
41913
42817
|
filePath: file.absolutePath,
|
|
41914
42818
|
optimize: true,
|
|
41915
42819
|
diagnostics: false,
|
|
@@ -41982,8 +42886,14 @@ ${bodyHTML}`;
|
|
|
41982
42886
|
extractTitle(file) {
|
|
41983
42887
|
try {
|
|
41984
42888
|
const source = readFileSync7(file.absolutePath, "utf-8");
|
|
41985
|
-
const
|
|
41986
|
-
|
|
42889
|
+
const masked = maskSourceStringsAndComments(source);
|
|
42890
|
+
const m = masked.match(/page\s*\{[^}]*title\s/);
|
|
42891
|
+
if (!m || m.index === void 0) return null;
|
|
42892
|
+
const start = m.index + m[0].length;
|
|
42893
|
+
if (source[start] !== '"') return null;
|
|
42894
|
+
const end = source.indexOf('"', start + 1);
|
|
42895
|
+
if (end === -1 || end === start + 1) return null;
|
|
42896
|
+
return source.slice(start + 1, end).replace(/\\([{}"])/g, "$1");
|
|
41987
42897
|
} catch {
|
|
41988
42898
|
return null;
|
|
41989
42899
|
}
|
|
@@ -41997,7 +42907,9 @@ ${bodyHTML}`;
|
|
|
41997
42907
|
extractRenderMode(file) {
|
|
41998
42908
|
try {
|
|
41999
42909
|
const src = readFileSync7(file.absolutePath, "utf8");
|
|
42000
|
-
const m = /render\s+(static|ssr|island|edge|csr|stream|ppr)\b/.exec(
|
|
42910
|
+
const m = /render\s+(static|ssr|island|edge|csr|stream|ppr)\b/.exec(
|
|
42911
|
+
maskSourceStringsAndComments(src)
|
|
42912
|
+
);
|
|
42001
42913
|
return m ? m[1] : "static";
|
|
42002
42914
|
} catch {
|
|
42003
42915
|
return "static";
|
|
@@ -42030,7 +42942,8 @@ ${bodyHTML}`;
|
|
|
42030
42942
|
* render-cache never reads `stale`.
|
|
42031
42943
|
*/
|
|
42032
42944
|
stampCacheHeaders(headers, entry, now, verdict) {
|
|
42033
|
-
const out = { ...headers ?? {}
|
|
42945
|
+
const out = { ...headers ?? {} };
|
|
42946
|
+
if (process.env.TW_DEBUG_CACHE || process.env.NODE_ENV === "test") out["x-tw-cache"] = verdict;
|
|
42034
42947
|
const c = entry?.cache;
|
|
42035
42948
|
if (c) {
|
|
42036
42949
|
const ageSec = entry?.createdAt ? Math.max(0, Math.floor((now - entry.createdAt) / 1e3)) : 0;
|
|
@@ -42045,7 +42958,7 @@ ${bodyHTML}`;
|
|
|
42045
42958
|
extractRevalidate(file) {
|
|
42046
42959
|
try {
|
|
42047
42960
|
const source = readFileSync7(file.absolutePath, "utf-8");
|
|
42048
|
-
const m = source.match(/page\s*\{[^}]*revalidate\s+(\d+)/);
|
|
42961
|
+
const m = maskSourceStringsAndComments(source).match(/page\s*\{[^}]*revalidate\s+(\d+)/);
|
|
42049
42962
|
if (m) {
|
|
42050
42963
|
const n = Number(m[1]);
|
|
42051
42964
|
return Number.isFinite(n) && n > 0 ? n : null;
|
|
@@ -42091,14 +43004,32 @@ ${bodyHTML}`;
|
|
|
42091
43004
|
buildMetaTags(file) {
|
|
42092
43005
|
try {
|
|
42093
43006
|
const source = readFileSync7(file.absolutePath, "utf-8");
|
|
42094
|
-
const
|
|
42095
|
-
|
|
42096
|
-
|
|
43007
|
+
const masked = maskSourceStringsAndComments(source);
|
|
43008
|
+
const fm = masked.match(/page\s*\{/);
|
|
43009
|
+
if (!fm || fm.index === void 0) return "";
|
|
43010
|
+
const bodyStart = fm.index + fm[0].length;
|
|
43011
|
+
const bodyEnd = masked.indexOf("}", bodyStart);
|
|
43012
|
+
if (bodyEnd === -1) return "";
|
|
43013
|
+
const body = source.slice(bodyStart, bodyEnd);
|
|
42097
43014
|
const AMP = String.fromCharCode(38);
|
|
42098
43015
|
const esc = (v) => v.split(AMP).join(AMP + "amp;").split(String.fromCharCode(34)).join(AMP + "quot;").split("<").join(AMP + "lt;");
|
|
42099
43016
|
const pick = (key) => {
|
|
42100
|
-
const
|
|
42101
|
-
|
|
43017
|
+
const i = body.indexOf(key + ' "');
|
|
43018
|
+
if (i === -1) return null;
|
|
43019
|
+
const rest = body.slice(i + key.length + 2);
|
|
43020
|
+
let end = -1;
|
|
43021
|
+
for (let j = 0; j < rest.length; j++) {
|
|
43022
|
+
if (rest[j] === "\\") {
|
|
43023
|
+
j++;
|
|
43024
|
+
continue;
|
|
43025
|
+
}
|
|
43026
|
+
if (rest[j] === '"') {
|
|
43027
|
+
end = j;
|
|
43028
|
+
break;
|
|
43029
|
+
}
|
|
43030
|
+
}
|
|
43031
|
+
if (end === -1) return null;
|
|
43032
|
+
return rest.slice(0, end).replace(/\\([{}"])/g, "$1");
|
|
42102
43033
|
};
|
|
42103
43034
|
let tags = "";
|
|
42104
43035
|
const description = pick("description");
|
|
@@ -43546,7 +44477,6 @@ function securityHeadersMiddleware() {
|
|
|
43546
44477
|
return (req, res, next) => {
|
|
43547
44478
|
res.headers.set("X-Content-Type-Options", "nosniff");
|
|
43548
44479
|
res.headers.set("X-Frame-Options", "SAMEORIGIN");
|
|
43549
|
-
res.headers.set("X-XSS-Protection", "1; mode=block");
|
|
43550
44480
|
res.headers.set("X-Permitted-Cross-Domain-Policies", "none");
|
|
43551
44481
|
res.headers.set("Referrer-Policy", "strict-origin-when-cross-origin");
|
|
43552
44482
|
res.headers.set("Cross-Origin-Opener-Policy", "same-origin");
|
|
@@ -43723,13 +44653,15 @@ function compressionMiddleware() {
|
|
|
43723
44653
|
await next();
|
|
43724
44654
|
};
|
|
43725
44655
|
}
|
|
43726
|
-
function securityHeadersMiddleware2() {
|
|
44656
|
+
function securityHeadersMiddleware2(opts) {
|
|
43727
44657
|
return async (ctx, next) => {
|
|
43728
44658
|
ctx.headers["x-content-type-options"] = "nosniff";
|
|
43729
44659
|
ctx.headers["x-frame-options"] = "DENY";
|
|
43730
|
-
ctx.headers["x-xss-protection"] = "1; mode=block";
|
|
43731
44660
|
ctx.headers["referrer-policy"] = "strict-origin-when-cross-origin";
|
|
43732
44661
|
ctx.headers["permissions-policy"] = "camera=(), microphone=(), geolocation=()";
|
|
44662
|
+
if (opts?.csp !== false && !ctx.headers["content-security-policy"]) {
|
|
44663
|
+
ctx.headers["content-security-policy"] = typeof opts?.csp === "string" ? opts.csp : "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self'; media-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'";
|
|
44664
|
+
}
|
|
43733
44665
|
await next();
|
|
43734
44666
|
};
|
|
43735
44667
|
}
|
|
@@ -43812,6 +44744,161 @@ var init_middleware2 = __esm({
|
|
|
43812
44744
|
}
|
|
43813
44745
|
});
|
|
43814
44746
|
|
|
44747
|
+
// ../../packages/server/tw/compression.ts
|
|
44748
|
+
function shouldCompress(contentType, options = {}) {
|
|
44749
|
+
const opts = { ...DEFAULT_OPTIONS4, ...options };
|
|
44750
|
+
const ct = contentType.split(";")[0].trim();
|
|
44751
|
+
return opts.contentTypes.includes(ct);
|
|
44752
|
+
}
|
|
44753
|
+
function isLargeEnough(size, options = {}) {
|
|
44754
|
+
const opts = { ...DEFAULT_OPTIONS4, ...options };
|
|
44755
|
+
return size >= opts.threshold;
|
|
44756
|
+
}
|
|
44757
|
+
async function compress(data, encoding = "gzip", options = {}) {
|
|
44758
|
+
const opts = { ...DEFAULT_OPTIONS4, ...options };
|
|
44759
|
+
const input = typeof data === "string" ? new TextEncoder().encode(data) : data;
|
|
44760
|
+
if (input.length < opts.threshold) {
|
|
44761
|
+
return input;
|
|
44762
|
+
}
|
|
44763
|
+
try {
|
|
44764
|
+
if (typeof Bun !== "undefined" && Bun.gzipSync) {
|
|
44765
|
+
if (encoding === "gzip") return Bun.gzipSync(input, { level: opts.level });
|
|
44766
|
+
if (encoding === "deflate" && Bun.deflateSync) return Bun.deflateSync(input, { level: opts.level });
|
|
44767
|
+
}
|
|
44768
|
+
} catch {
|
|
44769
|
+
}
|
|
44770
|
+
try {
|
|
44771
|
+
const zlib2 = await import("node:zlib");
|
|
44772
|
+
if (encoding === "gzip") {
|
|
44773
|
+
return zlib2.gzipSync(input, { level: opts.level });
|
|
44774
|
+
}
|
|
44775
|
+
if (encoding === "deflate") {
|
|
44776
|
+
return zlib2.deflateSync(input, { level: opts.level });
|
|
44777
|
+
}
|
|
44778
|
+
if (encoding === "brotli" && zlib2.brotliCompressSync) {
|
|
44779
|
+
return zlib2.brotliCompressSync(input, {
|
|
44780
|
+
params: {
|
|
44781
|
+
[zlib2.constants.BROTLI_PARAM_QUALITY]: opts.level
|
|
44782
|
+
}
|
|
44783
|
+
});
|
|
44784
|
+
}
|
|
44785
|
+
} catch {
|
|
44786
|
+
}
|
|
44787
|
+
return input;
|
|
44788
|
+
}
|
|
44789
|
+
async function decompress(data, encoding = "gzip") {
|
|
44790
|
+
try {
|
|
44791
|
+
if (typeof Bun !== "undefined" && Bun.gunzipSync) {
|
|
44792
|
+
if (encoding === "gzip") return Bun.gunzipSync(data);
|
|
44793
|
+
if (encoding === "deflate" && Bun.inflateSync) return Bun.inflateSync(data);
|
|
44794
|
+
}
|
|
44795
|
+
} catch {
|
|
44796
|
+
}
|
|
44797
|
+
try {
|
|
44798
|
+
const zlib2 = await import("node:zlib");
|
|
44799
|
+
if (encoding === "gzip") return zlib2.gunzipSync(data);
|
|
44800
|
+
if (encoding === "deflate") return zlib2.inflateSync(data);
|
|
44801
|
+
if (encoding === "brotli" && zlib2.brotliDecompressSync) return zlib2.brotliDecompressSync(data);
|
|
44802
|
+
} catch {
|
|
44803
|
+
}
|
|
44804
|
+
return data;
|
|
44805
|
+
}
|
|
44806
|
+
function negotiateEncoding(acceptEncoding) {
|
|
44807
|
+
const encodings = acceptEncoding.split(",").map((e) => e.trim().split(";")[0].trim()).filter(Boolean);
|
|
44808
|
+
if (encodings.includes("br")) return "brotli";
|
|
44809
|
+
if (encodings.includes("gzip")) return "gzip";
|
|
44810
|
+
if (encodings.includes("deflate")) return "deflate";
|
|
44811
|
+
return null;
|
|
44812
|
+
}
|
|
44813
|
+
function createCompressionHeaders(encoding, originalSize, compressedSize) {
|
|
44814
|
+
return {
|
|
44815
|
+
"Content-Encoding": encoding === "brotli" ? "br" : encoding,
|
|
44816
|
+
"Vary": "Accept-Encoding",
|
|
44817
|
+
"X-Original-Size": String(originalSize),
|
|
44818
|
+
"X-Compressed-Size": String(compressedSize),
|
|
44819
|
+
"X-Compression-Ratio": originalSize > 0 ? `${((1 - compressedSize / originalSize) * 100).toFixed(1)}%` : "0%"
|
|
44820
|
+
};
|
|
44821
|
+
}
|
|
44822
|
+
async function compressResponse(body, contentType, acceptEncoding, options = {}) {
|
|
44823
|
+
const input = typeof body === "string" ? new TextEncoder().encode(body) : body;
|
|
44824
|
+
const opts = { ...DEFAULT_OPTIONS4, ...options };
|
|
44825
|
+
if (!shouldCompress(contentType, opts) || !isLargeEnough(input.length, opts)) {
|
|
44826
|
+
return { body: input, headers: {}, compressed: false };
|
|
44827
|
+
}
|
|
44828
|
+
const encoding = negotiateEncoding(acceptEncoding);
|
|
44829
|
+
if (!encoding) {
|
|
44830
|
+
return { body: input, headers: {}, compressed: false };
|
|
44831
|
+
}
|
|
44832
|
+
try {
|
|
44833
|
+
const compressed = await compress(input, encoding, opts);
|
|
44834
|
+
const headers = createCompressionHeaders(encoding, input.length, compressed.length);
|
|
44835
|
+
return { body: compressed, headers, compressed: true };
|
|
44836
|
+
} catch {
|
|
44837
|
+
return { body: input, headers: {}, compressed: false };
|
|
44838
|
+
}
|
|
44839
|
+
}
|
|
44840
|
+
var DEFAULT_OPTIONS4, StreamingCompressor;
|
|
44841
|
+
var init_compression = __esm({
|
|
44842
|
+
"../../packages/server/tw/compression.ts"() {
|
|
44843
|
+
DEFAULT_OPTIONS4 = {
|
|
44844
|
+
level: 6,
|
|
44845
|
+
threshold: 1024,
|
|
44846
|
+
// Don't compress < 1KB
|
|
44847
|
+
brotli: true,
|
|
44848
|
+
contentTypes: [
|
|
44849
|
+
"text/html",
|
|
44850
|
+
"text/css",
|
|
44851
|
+
"text/plain",
|
|
44852
|
+
"text/javascript",
|
|
44853
|
+
"application/javascript",
|
|
44854
|
+
"application/json",
|
|
44855
|
+
"application/xml",
|
|
44856
|
+
"application/xhtml+xml",
|
|
44857
|
+
"image/svg+xml",
|
|
44858
|
+
"font/woff",
|
|
44859
|
+
"font/woff2",
|
|
44860
|
+
"application/wasm"
|
|
44861
|
+
]
|
|
44862
|
+
};
|
|
44863
|
+
StreamingCompressor = class {
|
|
44864
|
+
chunks = [];
|
|
44865
|
+
encoding;
|
|
44866
|
+
options;
|
|
44867
|
+
constructor(encoding = "gzip", options = {}) {
|
|
44868
|
+
this.encoding = encoding;
|
|
44869
|
+
this.options = { ...DEFAULT_OPTIONS4, ...options };
|
|
44870
|
+
}
|
|
44871
|
+
/**
|
|
44872
|
+
* Add a chunk of data to be compressed.
|
|
44873
|
+
*/
|
|
44874
|
+
push(data) {
|
|
44875
|
+
const input = typeof data === "string" ? new TextEncoder().encode(data) : data;
|
|
44876
|
+
this.chunks.push(input);
|
|
44877
|
+
}
|
|
44878
|
+
/**
|
|
44879
|
+
* Flush all data and return compressed result.
|
|
44880
|
+
*/
|
|
44881
|
+
async flush() {
|
|
44882
|
+
const totalLength = this.chunks.reduce((sum, c) => sum + c.length, 0);
|
|
44883
|
+
const combined = new Uint8Array(totalLength);
|
|
44884
|
+
let offset = 0;
|
|
44885
|
+
for (const chunk of this.chunks) {
|
|
44886
|
+
combined.set(chunk, offset);
|
|
44887
|
+
offset += chunk.length;
|
|
44888
|
+
}
|
|
44889
|
+
this.chunks = [];
|
|
44890
|
+
return compress(combined, this.encoding, this.options);
|
|
44891
|
+
}
|
|
44892
|
+
/**
|
|
44893
|
+
* Get the total uncompressed size so far.
|
|
44894
|
+
*/
|
|
44895
|
+
get uncompressedSize() {
|
|
44896
|
+
return this.chunks.reduce((sum, c) => sum + c.length, 0);
|
|
44897
|
+
}
|
|
44898
|
+
};
|
|
44899
|
+
}
|
|
44900
|
+
});
|
|
44901
|
+
|
|
43815
44902
|
// ../../packages/server/tw/static/index.ts
|
|
43816
44903
|
import { existsSync as existsSync11, statSync as statSync6 } from "node:fs";
|
|
43817
44904
|
import { join as join15, extname as extname7, normalize as normalize2, resolve as resolve4, sep } from "node:path";
|
|
@@ -44021,7 +45108,7 @@ var init_policy = __esm({
|
|
|
44021
45108
|
"Content-Security-Policy": this.build(),
|
|
44022
45109
|
"X-Content-Type-Options": "nosniff",
|
|
44023
45110
|
"X-Frame-Options": "DENY",
|
|
44024
|
-
|
|
45111
|
+
// BUG 48: x-xss-protection removed (deprecated, harmful in old browsers)
|
|
44025
45112
|
"Referrer-Policy": "strict-origin-when-cross-origin",
|
|
44026
45113
|
"Permissions-Policy": "geolocation=(), microphone=(), camera=(), payment=(), usb=(), magnetometer=(), gyroscope=(), accelerometer=()",
|
|
44027
45114
|
"Cross-Origin-Opener-Policy": "same-origin",
|
|
@@ -48655,9 +49742,10 @@ function devSecurityHeaders() {
|
|
|
48655
49742
|
removePoweredBy: true
|
|
48656
49743
|
});
|
|
48657
49744
|
}
|
|
48658
|
-
var DEFAULTS2, SecurityHeaders2;
|
|
49745
|
+
var BASELINE_CSP, DEFAULTS2, SecurityHeaders2;
|
|
48659
49746
|
var init_security_headers = __esm({
|
|
48660
49747
|
"../../packages/security/tw/headers/security-headers.ts"() {
|
|
49748
|
+
BASELINE_CSP = "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self'; media-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'";
|
|
48661
49749
|
DEFAULTS2 = {
|
|
48662
49750
|
hstsMaxAge: 31536e3,
|
|
48663
49751
|
// 1 year
|
|
@@ -48666,7 +49754,9 @@ var init_security_headers = __esm({
|
|
|
48666
49754
|
frameOptions: "SAMEORIGIN",
|
|
48667
49755
|
contentTypeOptions: true,
|
|
48668
49756
|
referrerPolicy: "strict-origin-when-cross-origin",
|
|
48669
|
-
|
|
49757
|
+
// v1.0.8 round 5 (BUG 48): x-xss-protection deprecated since 2019 --
|
|
49758
|
+
// OFF by default now; opt back in with security.xssProtection.
|
|
49759
|
+
xssProtection: void 0,
|
|
48670
49760
|
removePoweredBy: true,
|
|
48671
49761
|
coep: "unsafe-none",
|
|
48672
49762
|
coop: "same-origin",
|
|
@@ -48722,6 +49812,9 @@ var init_security_headers = __esm({
|
|
|
48722
49812
|
if (this.config.serverHeader) {
|
|
48723
49813
|
headers["Server"] = this.config.serverHeader;
|
|
48724
49814
|
}
|
|
49815
|
+
if (this.config.csp !== false && !headers["Content-Security-Policy"]) {
|
|
49816
|
+
headers["Content-Security-Policy"] = typeof this.config.csp === "string" ? this.config.csp : BASELINE_CSP;
|
|
49817
|
+
}
|
|
48725
49818
|
return headers;
|
|
48726
49819
|
}
|
|
48727
49820
|
/** Returns the security headers object. */
|
|
@@ -52932,161 +54025,6 @@ var init_ssr = __esm({
|
|
|
52932
54025
|
}
|
|
52933
54026
|
});
|
|
52934
54027
|
|
|
52935
|
-
// ../../packages/server/tw/compression.ts
|
|
52936
|
-
function shouldCompress(contentType, options = {}) {
|
|
52937
|
-
const opts = { ...DEFAULT_OPTIONS4, ...options };
|
|
52938
|
-
const ct = contentType.split(";")[0].trim();
|
|
52939
|
-
return opts.contentTypes.includes(ct);
|
|
52940
|
-
}
|
|
52941
|
-
function isLargeEnough(size, options = {}) {
|
|
52942
|
-
const opts = { ...DEFAULT_OPTIONS4, ...options };
|
|
52943
|
-
return size >= opts.threshold;
|
|
52944
|
-
}
|
|
52945
|
-
async function compress(data, encoding = "gzip", options = {}) {
|
|
52946
|
-
const opts = { ...DEFAULT_OPTIONS4, ...options };
|
|
52947
|
-
const input = typeof data === "string" ? new TextEncoder().encode(data) : data;
|
|
52948
|
-
if (input.length < opts.threshold) {
|
|
52949
|
-
return input;
|
|
52950
|
-
}
|
|
52951
|
-
try {
|
|
52952
|
-
if (typeof Bun !== "undefined" && Bun.gzipSync) {
|
|
52953
|
-
if (encoding === "gzip") return Bun.gzipSync(input, { level: opts.level });
|
|
52954
|
-
if (encoding === "deflate" && Bun.deflateSync) return Bun.deflateSync(input, { level: opts.level });
|
|
52955
|
-
}
|
|
52956
|
-
} catch {
|
|
52957
|
-
}
|
|
52958
|
-
try {
|
|
52959
|
-
const zlib2 = await import("node:zlib");
|
|
52960
|
-
if (encoding === "gzip") {
|
|
52961
|
-
return zlib2.gzipSync(input, { level: opts.level });
|
|
52962
|
-
}
|
|
52963
|
-
if (encoding === "deflate") {
|
|
52964
|
-
return zlib2.deflateSync(input, { level: opts.level });
|
|
52965
|
-
}
|
|
52966
|
-
if (encoding === "brotli" && zlib2.brotliCompressSync) {
|
|
52967
|
-
return zlib2.brotliCompressSync(input, {
|
|
52968
|
-
params: {
|
|
52969
|
-
[zlib2.constants.BROTLI_PARAM_QUALITY]: opts.level
|
|
52970
|
-
}
|
|
52971
|
-
});
|
|
52972
|
-
}
|
|
52973
|
-
} catch {
|
|
52974
|
-
}
|
|
52975
|
-
return input;
|
|
52976
|
-
}
|
|
52977
|
-
async function decompress(data, encoding = "gzip") {
|
|
52978
|
-
try {
|
|
52979
|
-
if (typeof Bun !== "undefined" && Bun.gunzipSync) {
|
|
52980
|
-
if (encoding === "gzip") return Bun.gunzipSync(data);
|
|
52981
|
-
if (encoding === "deflate" && Bun.inflateSync) return Bun.inflateSync(data);
|
|
52982
|
-
}
|
|
52983
|
-
} catch {
|
|
52984
|
-
}
|
|
52985
|
-
try {
|
|
52986
|
-
const zlib2 = await import("node:zlib");
|
|
52987
|
-
if (encoding === "gzip") return zlib2.gunzipSync(data);
|
|
52988
|
-
if (encoding === "deflate") return zlib2.inflateSync(data);
|
|
52989
|
-
if (encoding === "brotli" && zlib2.brotliDecompressSync) return zlib2.brotliDecompressSync(data);
|
|
52990
|
-
} catch {
|
|
52991
|
-
}
|
|
52992
|
-
return data;
|
|
52993
|
-
}
|
|
52994
|
-
function negotiateEncoding(acceptEncoding) {
|
|
52995
|
-
const encodings = acceptEncoding.split(",").map((e) => e.trim().split(";")[0].trim()).filter(Boolean);
|
|
52996
|
-
if (encodings.includes("br")) return "brotli";
|
|
52997
|
-
if (encodings.includes("gzip")) return "gzip";
|
|
52998
|
-
if (encodings.includes("deflate")) return "deflate";
|
|
52999
|
-
return null;
|
|
53000
|
-
}
|
|
53001
|
-
function createCompressionHeaders(encoding, originalSize, compressedSize) {
|
|
53002
|
-
return {
|
|
53003
|
-
"Content-Encoding": encoding === "brotli" ? "br" : encoding,
|
|
53004
|
-
"Vary": "Accept-Encoding",
|
|
53005
|
-
"X-Original-Size": String(originalSize),
|
|
53006
|
-
"X-Compressed-Size": String(compressedSize),
|
|
53007
|
-
"X-Compression-Ratio": originalSize > 0 ? `${((1 - compressedSize / originalSize) * 100).toFixed(1)}%` : "0%"
|
|
53008
|
-
};
|
|
53009
|
-
}
|
|
53010
|
-
async function compressResponse(body, contentType, acceptEncoding, options = {}) {
|
|
53011
|
-
const input = typeof body === "string" ? new TextEncoder().encode(body) : body;
|
|
53012
|
-
const opts = { ...DEFAULT_OPTIONS4, ...options };
|
|
53013
|
-
if (!shouldCompress(contentType, opts) || !isLargeEnough(input.length, opts)) {
|
|
53014
|
-
return { body: input, headers: {}, compressed: false };
|
|
53015
|
-
}
|
|
53016
|
-
const encoding = negotiateEncoding(acceptEncoding);
|
|
53017
|
-
if (!encoding) {
|
|
53018
|
-
return { body: input, headers: {}, compressed: false };
|
|
53019
|
-
}
|
|
53020
|
-
try {
|
|
53021
|
-
const compressed = await compress(input, encoding, opts);
|
|
53022
|
-
const headers = createCompressionHeaders(encoding, input.length, compressed.length);
|
|
53023
|
-
return { body: compressed, headers, compressed: true };
|
|
53024
|
-
} catch {
|
|
53025
|
-
return { body: input, headers: {}, compressed: false };
|
|
53026
|
-
}
|
|
53027
|
-
}
|
|
53028
|
-
var DEFAULT_OPTIONS4, StreamingCompressor;
|
|
53029
|
-
var init_compression = __esm({
|
|
53030
|
-
"../../packages/server/tw/compression.ts"() {
|
|
53031
|
-
DEFAULT_OPTIONS4 = {
|
|
53032
|
-
level: 6,
|
|
53033
|
-
threshold: 1024,
|
|
53034
|
-
// Don't compress < 1KB
|
|
53035
|
-
brotli: true,
|
|
53036
|
-
contentTypes: [
|
|
53037
|
-
"text/html",
|
|
53038
|
-
"text/css",
|
|
53039
|
-
"text/plain",
|
|
53040
|
-
"text/javascript",
|
|
53041
|
-
"application/javascript",
|
|
53042
|
-
"application/json",
|
|
53043
|
-
"application/xml",
|
|
53044
|
-
"application/xhtml+xml",
|
|
53045
|
-
"image/svg+xml",
|
|
53046
|
-
"font/woff",
|
|
53047
|
-
"font/woff2",
|
|
53048
|
-
"application/wasm"
|
|
53049
|
-
]
|
|
53050
|
-
};
|
|
53051
|
-
StreamingCompressor = class {
|
|
53052
|
-
chunks = [];
|
|
53053
|
-
encoding;
|
|
53054
|
-
options;
|
|
53055
|
-
constructor(encoding = "gzip", options = {}) {
|
|
53056
|
-
this.encoding = encoding;
|
|
53057
|
-
this.options = { ...DEFAULT_OPTIONS4, ...options };
|
|
53058
|
-
}
|
|
53059
|
-
/**
|
|
53060
|
-
* Add a chunk of data to be compressed.
|
|
53061
|
-
*/
|
|
53062
|
-
push(data) {
|
|
53063
|
-
const input = typeof data === "string" ? new TextEncoder().encode(data) : data;
|
|
53064
|
-
this.chunks.push(input);
|
|
53065
|
-
}
|
|
53066
|
-
/**
|
|
53067
|
-
* Flush all data and return compressed result.
|
|
53068
|
-
*/
|
|
53069
|
-
async flush() {
|
|
53070
|
-
const totalLength = this.chunks.reduce((sum, c) => sum + c.length, 0);
|
|
53071
|
-
const combined = new Uint8Array(totalLength);
|
|
53072
|
-
let offset = 0;
|
|
53073
|
-
for (const chunk of this.chunks) {
|
|
53074
|
-
combined.set(chunk, offset);
|
|
53075
|
-
offset += chunk.length;
|
|
53076
|
-
}
|
|
53077
|
-
this.chunks = [];
|
|
53078
|
-
return compress(combined, this.encoding, this.options);
|
|
53079
|
-
}
|
|
53080
|
-
/**
|
|
53081
|
-
* Get the total uncompressed size so far.
|
|
53082
|
-
*/
|
|
53083
|
-
get uncompressedSize() {
|
|
53084
|
-
return this.chunks.reduce((sum, c) => sum + c.length, 0);
|
|
53085
|
-
}
|
|
53086
|
-
};
|
|
53087
|
-
}
|
|
53088
|
-
});
|
|
53089
|
-
|
|
53090
54028
|
// ../../packages/server/tw/edge.ts
|
|
53091
54029
|
function createEdgeHandler(config) {
|
|
53092
54030
|
const cache = /* @__PURE__ */ new Map();
|
|
@@ -53507,6 +54445,13 @@ function generateSecurityHeaders(options = {}) {
|
|
|
53507
54445
|
const headers = {};
|
|
53508
54446
|
if (opts.csp) {
|
|
53509
54447
|
headers["Content-Security-Policy"] = buildCSPHeader(opts.csp);
|
|
54448
|
+
try {
|
|
54449
|
+
const scriptSrc = Array.isArray(opts.csp["script-src"]) ? opts.csp["script-src"].join(" ") : String(opts.csp["script-src"] ?? "");
|
|
54450
|
+
if (scriptSrc && !scriptSrc.includes("'unsafe-eval'") && !scriptSrc.includes("'strict-dynamic'")) {
|
|
54451
|
+
console.warn("[tw] security: configured CSP has no 'unsafe-eval' in script-src -- the island hydration runtime needs it (add script-src: ['self', ''unsafe-eval''] or migrate handlers).");
|
|
54452
|
+
}
|
|
54453
|
+
} catch {
|
|
54454
|
+
}
|
|
53510
54455
|
}
|
|
53511
54456
|
if (opts.hsts) {
|
|
53512
54457
|
const hstsOpts = typeof opts.hsts === "object" ? opts.hsts : { maxAge: 31536e3, includeSubDomains: true };
|
|
@@ -53798,8 +54743,11 @@ var init_tw6 = __esm({
|
|
|
53798
54743
|
init_tw2();
|
|
53799
54744
|
init_websocket_manager();
|
|
53800
54745
|
init_routing2();
|
|
54746
|
+
init_twm_rules();
|
|
53801
54747
|
init_router();
|
|
53802
54748
|
init_middleware2();
|
|
54749
|
+
init_compression();
|
|
54750
|
+
init_signal_stream();
|
|
53803
54751
|
init_static();
|
|
53804
54752
|
init_tw5();
|
|
53805
54753
|
init_ssr();
|
|
@@ -53848,6 +54796,7 @@ var init_tw6 = __esm({
|
|
|
53848
54796
|
}
|
|
53849
54797
|
ssr = null;
|
|
53850
54798
|
server = null;
|
|
54799
|
+
compressionMode = "off";
|
|
53851
54800
|
isRunning = false;
|
|
53852
54801
|
pluginManager = null;
|
|
53853
54802
|
imageHandler = null;
|
|
@@ -53864,6 +54813,40 @@ var init_tw6 = __esm({
|
|
|
53864
54813
|
const entries = Object.entries(cfgIn.redirects);
|
|
53865
54814
|
cfgIn.redirects = entries.map(([from, to]) => typeof to === "string" ? { from, to } : { from, ...to });
|
|
53866
54815
|
}
|
|
54816
|
+
if (Array.isArray(cfgIn.redirects)) {
|
|
54817
|
+
const REDIRECT_KNOWN = /* @__PURE__ */ new Set(["from", "to", "status", "source", "destination", "permanent"]);
|
|
54818
|
+
let redirectDropped = false;
|
|
54819
|
+
const normalizedRedirects = cfgIn.redirects.map((r) => {
|
|
54820
|
+
if (!r || typeof r !== "object") return r;
|
|
54821
|
+
let touched = false;
|
|
54822
|
+
const mapped = { ...r };
|
|
54823
|
+
if (mapped.from === void 0 && mapped.source !== void 0) {
|
|
54824
|
+
mapped.from = mapped.source;
|
|
54825
|
+
touched = true;
|
|
54826
|
+
}
|
|
54827
|
+
if (mapped.to === void 0 && mapped.destination !== void 0) {
|
|
54828
|
+
mapped.to = mapped.destination;
|
|
54829
|
+
touched = true;
|
|
54830
|
+
}
|
|
54831
|
+
if (mapped.status === void 0 && mapped.permanent !== void 0) {
|
|
54832
|
+
mapped.status = mapped.permanent ? 301 : 302;
|
|
54833
|
+
touched = true;
|
|
54834
|
+
}
|
|
54835
|
+
const unknown = Object.keys(mapped).filter((k) => !REDIRECT_KNOWN.has(k));
|
|
54836
|
+
if (unknown.length > 0) {
|
|
54837
|
+
console.warn("[tw] redirects: unrecognized field" + (unknown.length > 1 ? "s" : "") + " " + unknown.join(", ") + " in entry { from: " + JSON.stringify(mapped.from) + " } -- accepted fields: from, to, status");
|
|
54838
|
+
}
|
|
54839
|
+
if (typeof mapped.from !== "string" || typeof mapped.to !== "string") {
|
|
54840
|
+
console.warn("[tw] redirects: entry missing from/to after normalization -- skipped (" + JSON.stringify(r).slice(0, 80) + ")");
|
|
54841
|
+
redirectDropped = true;
|
|
54842
|
+
return null;
|
|
54843
|
+
}
|
|
54844
|
+
return touched ? mapped : r;
|
|
54845
|
+
}).filter((x) => x !== null || (redirectDropped = true));
|
|
54846
|
+
if (redirectDropped || normalizedRedirects.some((x, i) => x !== cfgIn.redirects[i])) {
|
|
54847
|
+
cfgIn.redirects = normalizedRedirects.filter(Boolean);
|
|
54848
|
+
}
|
|
54849
|
+
}
|
|
53867
54850
|
if (cfgIn.headers && !Array.isArray(cfgIn.headers)) {
|
|
53868
54851
|
const entries = Object.entries(cfgIn.headers);
|
|
53869
54852
|
cfgIn.headers = entries.map(([source, headers]) => ({ source, headers }));
|
|
@@ -53881,6 +54864,12 @@ var init_tw6 = __esm({
|
|
|
53881
54864
|
}
|
|
53882
54865
|
}
|
|
53883
54866
|
this.pluginManager = this.options.plugins ?? null;
|
|
54867
|
+
{
|
|
54868
|
+
const raw = this.options.compression ?? true;
|
|
54869
|
+
if (raw === false || raw === "none" || raw === "off" || raw === "false") this.compressionMode = "off";
|
|
54870
|
+
else if (raw === "gzip") this.compressionMode = "gzip";
|
|
54871
|
+
else this.compressionMode = "brotli";
|
|
54872
|
+
}
|
|
53884
54873
|
try {
|
|
53885
54874
|
this.imageHandler = createImageHandler({
|
|
53886
54875
|
rootDir: this.options.rootDir,
|
|
@@ -53990,31 +54979,72 @@ var init_tw6 = __esm({
|
|
|
53990
54979
|
if (existsSync13(middlewarePath)) {
|
|
53991
54980
|
this.middlewarePath = middlewarePath;
|
|
53992
54981
|
console.log(" Middleware: middleware.twm loaded");
|
|
54982
|
+
try {
|
|
54983
|
+
const src = readFileSync9(middlewarePath, "utf-8");
|
|
54984
|
+
const rules = parseRules(src, "middleware.twm");
|
|
54985
|
+
for (const r of rules) {
|
|
54986
|
+
const conds = [];
|
|
54987
|
+
if (r.userAgent) conds.push("user_agent");
|
|
54988
|
+
if (r.path) conds.push("path");
|
|
54989
|
+
if (r.auth) conds.push("auth");
|
|
54990
|
+
if (r.rateLimit) conds.push("rate_limit");
|
|
54991
|
+
if (r.origin) conds.push("origin");
|
|
54992
|
+
if (r.methods && r.methods.length > 0) conds.push("methods");
|
|
54993
|
+
const label = conds.length > 0 ? conds.join(" + ") : "no conditions -> every matching request gets this response";
|
|
54994
|
+
console.log(` \u2022 "${r.name}" [${label}]`);
|
|
54995
|
+
}
|
|
54996
|
+
} catch {
|
|
54997
|
+
}
|
|
53993
54998
|
}
|
|
53994
54999
|
return this;
|
|
53995
55000
|
}
|
|
53996
55001
|
async start() {
|
|
53997
55002
|
if (this.isRunning) return;
|
|
53998
|
-
const
|
|
53999
|
-
|
|
54000
|
-
|
|
54001
|
-
|
|
54002
|
-
|
|
54003
|
-
|
|
54004
|
-
|
|
54005
|
-
|
|
54006
|
-
|
|
54007
|
-
|
|
54008
|
-
|
|
55003
|
+
const host = this.options.host ?? "0.0.0.0";
|
|
55004
|
+
const wanted = this.options.port ?? 8e3;
|
|
55005
|
+
let port = wanted;
|
|
55006
|
+
let bound = null;
|
|
55007
|
+
let lastErr = null;
|
|
55008
|
+
for (let p = wanted; p < wanted + 10; p++) {
|
|
55009
|
+
try {
|
|
55010
|
+
bound = (typeof Bun !== "undefined" ? Bun : null)?.serve({
|
|
55011
|
+
port: p,
|
|
55012
|
+
hostname: host,
|
|
55013
|
+
fetch: async (request) => {
|
|
55014
|
+
const res = await this.handleRequest(request);
|
|
55015
|
+
let withHeaders = res;
|
|
55016
|
+
if (this.securityHeaders) {
|
|
55017
|
+
try {
|
|
55018
|
+
withHeaders = this.securityHeaders.apply(res);
|
|
55019
|
+
} catch {
|
|
55020
|
+
withHeaders = res;
|
|
55021
|
+
}
|
|
55022
|
+
}
|
|
55023
|
+
return this.compressResponse(withHeaders, request);
|
|
55024
|
+
},
|
|
55025
|
+
error: (err3) => {
|
|
55026
|
+
console.error("Server error:", err3);
|
|
55027
|
+
return new Response("Internal Server Error", { status: 500 });
|
|
54009
55028
|
}
|
|
54010
|
-
}
|
|
54011
|
-
|
|
54012
|
-
|
|
54013
|
-
|
|
54014
|
-
|
|
54015
|
-
|
|
55029
|
+
});
|
|
55030
|
+
port = p;
|
|
55031
|
+
break;
|
|
55032
|
+
} catch (e) {
|
|
55033
|
+
lastErr = e;
|
|
55034
|
+
const msg = String(e?.message ?? e);
|
|
55035
|
+
if (!/address already in use|EADDRINUSE|Failed to start server/i.test(msg)) throw e;
|
|
55036
|
+
if (p < wanted + 9) console.log(` Port ${p} busy \u2014 trying ${p + 1}...`);
|
|
54016
55037
|
}
|
|
54017
|
-
}
|
|
55038
|
+
}
|
|
55039
|
+
if (!bound) {
|
|
55040
|
+
console.error(`
|
|
55041
|
+
ERROR: could not bind ${host} on ports ${wanted}\u2013${wanted + 9}.`);
|
|
55042
|
+
console.error(` ${lastErr?.message ?? lastErr}
|
|
55043
|
+
`);
|
|
55044
|
+
throw lastErr ?? new Error("could not start server");
|
|
55045
|
+
}
|
|
55046
|
+
this.server = bound;
|
|
55047
|
+
this.options.port = port;
|
|
54018
55048
|
this.isRunning = true;
|
|
54019
55049
|
console.log(`
|
|
54020
55050
|
TW Server running at http://${host}:${port}
|
|
@@ -54028,6 +55058,10 @@ var init_tw6 = __esm({
|
|
|
54028
55058
|
// finishes ongoing requests, then closes
|
|
54029
55059
|
async gracefulShutdown(timeout = 5e3) {
|
|
54030
55060
|
this.isRunning = false;
|
|
55061
|
+
try {
|
|
55062
|
+
getSignalHub().closeAll();
|
|
55063
|
+
} catch {
|
|
55064
|
+
}
|
|
54031
55065
|
if (this.server) {
|
|
54032
55066
|
this.server.stop?.(false);
|
|
54033
55067
|
const start = Date.now();
|
|
@@ -54053,6 +55087,35 @@ var init_tw6 = __esm({
|
|
|
54053
55087
|
* - return { headers: {...} } -> headers merged into the final response
|
|
54054
55088
|
* - return null/undefined -> continue
|
|
54055
55089
|
*/
|
|
55090
|
+
/** BUG 7 (v1.0.8): real response compression. `compression` in
|
|
55091
|
+
* tw.config.ts was silently ignored -- the middleware was a no-op and
|
|
55092
|
+
* nothing on the response path ever set Content-Encoding. */
|
|
55093
|
+
async compressResponse(res, request) {
|
|
55094
|
+
if (this.compressionMode === "off") return res;
|
|
55095
|
+
try {
|
|
55096
|
+
if (res.headers.get("content-encoding")) return res;
|
|
55097
|
+
const ct = res.headers.get("content-type") || "";
|
|
55098
|
+
if (ct.includes("text/event-stream")) return res;
|
|
55099
|
+
if (res.__twRawStream) return res;
|
|
55100
|
+
const buf = new Uint8Array(await res.arrayBuffer());
|
|
55101
|
+
if (!isLargeEnough(buf.length)) return new Response(buf, { status: res.status, headers: res.headers });
|
|
55102
|
+
const accept = request.headers.get("accept-encoding") || "";
|
|
55103
|
+
let enc = negotiateEncoding(accept);
|
|
55104
|
+
if (this.compressionMode === "gzip" && enc === "brotli") enc = "gzip";
|
|
55105
|
+
if (!enc) return new Response(buf, { status: res.status, headers: res.headers });
|
|
55106
|
+
const out = await compress(buf, enc);
|
|
55107
|
+
if (out.length >= buf.length) return new Response(buf, { status: res.status, headers: res.headers });
|
|
55108
|
+
const h = new Headers(res.headers);
|
|
55109
|
+
h.set("Content-Encoding", enc === "brotli" ? "br" : enc);
|
|
55110
|
+
h.set("Vary", "Accept-Encoding");
|
|
55111
|
+
h.delete("Content-Length");
|
|
55112
|
+
return new Response(out, { status: res.status, headers: h });
|
|
55113
|
+
} catch {
|
|
55114
|
+
return res;
|
|
55115
|
+
}
|
|
55116
|
+
}
|
|
55117
|
+
/** v1.0.8 round 5 (BUG 39): public now -- serverless adapters (Vercel
|
|
55118
|
+
* functions, edge runtimes) drive the server through this entry. */
|
|
54056
55119
|
async handleRequest(request) {
|
|
54057
55120
|
try {
|
|
54058
55121
|
const u = new URL(request.url);
|
|
@@ -54225,69 +55288,12 @@ var init_tw6 = __esm({
|
|
|
54225
55288
|
* `data: {"v":1,"seq":N,"updates":[...]}` frames.
|
|
54226
55289
|
*/
|
|
54227
55290
|
handleSignalStream(request, url) {
|
|
54228
|
-
const
|
|
54229
|
-
|
|
54230
|
-
|
|
54231
|
-
|
|
54232
|
-
|
|
54233
|
-
|
|
54234
|
-
try {
|
|
54235
|
-
const header = request.headers.get("cookie") ?? "";
|
|
54236
|
-
const m = /(?:^|;\s*)tw_session=([^;]+)/.exec(header);
|
|
54237
|
-
if (m) session = decodeURIComponent(m[1].trim().replace(/^"|"$/g, ""));
|
|
54238
|
-
} catch {
|
|
54239
|
-
}
|
|
54240
|
-
const enc = new TextEncoder();
|
|
54241
|
-
const sse = (payload) => enc.encode("data: " + payload + "\n\n");
|
|
54242
|
-
const stream = new ReadableStream({
|
|
54243
|
-
start(controller) {
|
|
54244
|
-
const client = hub2.register(declared, session);
|
|
54245
|
-
client.send = (payload) => {
|
|
54246
|
-
try {
|
|
54247
|
-
controller.enqueue(sse(payload));
|
|
54248
|
-
} catch {
|
|
54249
|
-
client.close();
|
|
54250
|
-
}
|
|
54251
|
-
};
|
|
54252
|
-
client.close = () => {
|
|
54253
|
-
try {
|
|
54254
|
-
controller.close();
|
|
54255
|
-
} catch {
|
|
54256
|
-
}
|
|
54257
|
-
hub2["clients"];
|
|
54258
|
-
};
|
|
54259
|
-
for (const payload of hub2.connectPayloads(since, declared, session)) {
|
|
54260
|
-
try {
|
|
54261
|
-
controller.enqueue(sse(payload));
|
|
54262
|
-
} catch {
|
|
54263
|
-
break;
|
|
54264
|
-
}
|
|
54265
|
-
}
|
|
54266
|
-
const keepAlive = setInterval(() => {
|
|
54267
|
-
try {
|
|
54268
|
-
controller.enqueue(enc.encode(": keep-alive\n\n"));
|
|
54269
|
-
} catch {
|
|
54270
|
-
clearInterval(keepAlive);
|
|
54271
|
-
}
|
|
54272
|
-
}, 25e3);
|
|
54273
|
-
client.close = () => {
|
|
54274
|
-
clearInterval(keepAlive);
|
|
54275
|
-
try {
|
|
54276
|
-
controller.close();
|
|
54277
|
-
} catch {
|
|
54278
|
-
}
|
|
54279
|
-
};
|
|
54280
|
-
}
|
|
54281
|
-
});
|
|
54282
|
-
return new Response(stream, {
|
|
54283
|
-
status: 200,
|
|
54284
|
-
headers: {
|
|
54285
|
-
"Content-Type": "text/event-stream; charset=utf-8",
|
|
54286
|
-
"Cache-Control": "no-store",
|
|
54287
|
-
"X-Accel-Buffering": "no",
|
|
54288
|
-
Connection: "keep-alive"
|
|
54289
|
-
}
|
|
54290
|
-
});
|
|
55291
|
+
const cfg = this.options.config?.signalStream;
|
|
55292
|
+
return createSignalStream(request, url, cfg ? { maxClients: cfg.maxClients, maxHistory: cfg.maxHistory } : void 0);
|
|
55293
|
+
}
|
|
55294
|
+
async handleClientSignalWrite(request) {
|
|
55295
|
+
const cfg = this.options.config?.signalStream;
|
|
55296
|
+
return writeSignalFromClient(request, { clientWrites: cfg?.clientWrites });
|
|
54291
55297
|
}
|
|
54292
55298
|
/**
|
|
54293
55299
|
* PPR hole fill: render the page fresh and return only the requested
|
|
@@ -54361,15 +55367,33 @@ var init_tw6 = __esm({
|
|
|
54361
55367
|
controller.close();
|
|
54362
55368
|
}
|
|
54363
55369
|
});
|
|
54364
|
-
|
|
55370
|
+
const streamRes = new Response(stream, {
|
|
54365
55371
|
status: 200,
|
|
54366
55372
|
headers: { "Content-Type": "text/html; charset=utf-8" }
|
|
54367
55373
|
});
|
|
55374
|
+
streamRes.__twRawStream = true;
|
|
55375
|
+
return streamRes;
|
|
54368
55376
|
}
|
|
54369
55377
|
async handleRequestInner(request) {
|
|
54370
55378
|
const url = new URL(request.url);
|
|
54371
55379
|
const method = request.method;
|
|
54372
|
-
|
|
55380
|
+
let rawPath = url.pathname;
|
|
55381
|
+
try {
|
|
55382
|
+
const decoded = decodeURIComponent(rawPath);
|
|
55383
|
+
if (decoded !== rawPath) rawPath = decoded;
|
|
55384
|
+
} catch {
|
|
55385
|
+
}
|
|
55386
|
+
let canonical = normalizePathname(rawPath);
|
|
55387
|
+
if (canonical.length > 1 && canonical.endsWith("/")) {
|
|
55388
|
+
canonical = canonical.slice(0, -1);
|
|
55389
|
+
}
|
|
55390
|
+
if (canonical !== rawPath) {
|
|
55391
|
+
return new Response(null, {
|
|
55392
|
+
status: 308,
|
|
55393
|
+
headers: { Location: encodeURI(canonical) + url.search }
|
|
55394
|
+
});
|
|
55395
|
+
}
|
|
55396
|
+
const pathname = canonical;
|
|
54373
55397
|
if (pathname === "/_tw/img" || pathname.startsWith("/_tw/img/")) {
|
|
54374
55398
|
const imgRes = await this.imageHandler.handle(request);
|
|
54375
55399
|
if (imgRes) return imgRes;
|
|
@@ -54377,6 +55401,9 @@ var init_tw6 = __esm({
|
|
|
54377
55401
|
if (pathname === "/_tw/stream") {
|
|
54378
55402
|
return this.handleSignalStream(request, url);
|
|
54379
55403
|
}
|
|
55404
|
+
if (pathname === "/_tw/signal" && method === "POST") {
|
|
55405
|
+
return this.handleClientSignalWrite(request);
|
|
55406
|
+
}
|
|
54380
55407
|
if (pathname === "/_tw/ppr") {
|
|
54381
55408
|
return this.handlePPRHoleFill(url);
|
|
54382
55409
|
}
|
|
@@ -54541,13 +55568,39 @@ __export(twm_loader_exports, {
|
|
|
54541
55568
|
loadTWMModule: () => loadTWMModule,
|
|
54542
55569
|
revalidateRoute: () => revalidateRoute,
|
|
54543
55570
|
shouldMatchMiddleware: () => shouldMatchMiddleware,
|
|
54544
|
-
stripCommentsSafe: () => stripCommentsSafe
|
|
55571
|
+
stripCommentsSafe: () => stripCommentsSafe,
|
|
55572
|
+
validateTWMSyntax: () => validateTWMSyntax
|
|
54545
55573
|
});
|
|
54546
55574
|
import { readFileSync as readFileSync10, existsSync as existsSync14 } from "node:fs";
|
|
54547
55575
|
import { join as join18 } from "node:path";
|
|
54548
55576
|
function clearTWMCache() {
|
|
54549
55577
|
moduleCache.clear();
|
|
54550
55578
|
}
|
|
55579
|
+
function validateTWMSyntax(filePath) {
|
|
55580
|
+
let source;
|
|
55581
|
+
try {
|
|
55582
|
+
source = readFileSync10(filePath, "utf-8");
|
|
55583
|
+
} catch (err3) {
|
|
55584
|
+
return "cannot read: " + (err3?.message ?? err3);
|
|
55585
|
+
}
|
|
55586
|
+
if (isRuleDsl(source)) {
|
|
55587
|
+
try {
|
|
55588
|
+
parseRules(source, filePath);
|
|
55589
|
+
return null;
|
|
55590
|
+
} catch (err3) {
|
|
55591
|
+
return "rule DSL: " + (err3?.message ?? String(err3));
|
|
55592
|
+
}
|
|
55593
|
+
}
|
|
55594
|
+
try {
|
|
55595
|
+
const imports = extractImports(source);
|
|
55596
|
+
const jsCode = compileTwm(source);
|
|
55597
|
+
const codeWithoutImports = stripImports(jsCode);
|
|
55598
|
+
new Function("__tw_unused", codeWithoutImports);
|
|
55599
|
+
return null;
|
|
55600
|
+
} catch (err3) {
|
|
55601
|
+
return (err3?.message ?? String(err3)).replace(/\n/g, " ").slice(0, 200);
|
|
55602
|
+
}
|
|
55603
|
+
}
|
|
54551
55604
|
function stripCommentsSafe(src) {
|
|
54552
55605
|
let out = "";
|
|
54553
55606
|
let i = 0;
|
|
@@ -54785,7 +55838,7 @@ async function loadTWMModule(filePath, rootDir) {
|
|
|
54785
55838
|
console.error("[tw] " + e.message + " (" + filePath + ":" + cm[1] + ")");
|
|
54786
55839
|
}
|
|
54787
55840
|
}
|
|
54788
|
-
const moduleCode = codeWithoutImports + "\nreturn { get: typeof get !== 'undefined' ? get : null, post: typeof post !== 'undefined' ? post : null, put: typeof put !== 'undefined' ? put : null, patch: typeof patch !== 'undefined' ? patch : null, deleteFn: typeof deleteFn !== 'undefined' ? deleteFn : null, GET: typeof GET !== 'undefined' ? GET : null, POST: typeof POST !== 'undefined' ? POST : null, PUT: typeof PUT !== 'undefined' ? PUT : null, PATCH: typeof PATCH !== 'undefined' ? PATCH : null, DELETE: typeof DELETE !== 'undefined' ? DELETE : null, middleware: typeof middleware !== 'undefined' ? middleware : null, generate: typeof generate !== 'undefined' ? generate : null, config: typeof config !== 'undefined' ? config : null, action: typeof action !== 'undefined' ? action : null, revalidate: typeof revalidate !== 'undefined' ? revalidate : null, " + [...actionNames].map((n) => `${n}: typeof ${n} !== 'undefined' ? ${n} : null`).join(", ") + " };";
|
|
55841
|
+
const moduleCode = codeWithoutImports + "\nreturn { get: typeof get !== 'undefined' ? get : null, post: typeof post !== 'undefined' ? post : null, put: typeof put !== 'undefined' ? put : null, patch: typeof patch !== 'undefined' ? patch : null, deleteFn: typeof deleteFn !== 'undefined' ? deleteFn : null, GET: typeof GET !== 'undefined' ? GET : null, POST: typeof POST !== 'undefined' ? POST : null, PUT: typeof PUT !== 'undefined' ? PUT : null, PATCH: typeof PATCH !== 'undefined' ? PATCH : null, DELETE: typeof DELETE !== 'undefined' ? DELETE : null, options: typeof options !== 'undefined' ? options : null, OPTIONS: typeof OPTIONS !== 'undefined' ? OPTIONS : null, middleware: typeof middleware !== 'undefined' ? middleware : null, generate: typeof generate !== 'undefined' ? generate : null, config: typeof config !== 'undefined' ? config : null, action: typeof action !== 'undefined' ? action : null, revalidate: typeof revalidate !== 'undefined' ? revalidate : null, " + [...actionNames].map((n) => `${n}: typeof ${n} !== 'undefined' ? ${n} : null`).join(", ") + " };";
|
|
54789
55842
|
const injectedKeys = Object.keys(injected);
|
|
54790
55843
|
let module = {};
|
|
54791
55844
|
try {
|
|
@@ -54800,6 +55853,8 @@ async function loadTWMModule(filePath, rootDir) {
|
|
|
54800
55853
|
put: H.put || H.PUT || void 0,
|
|
54801
55854
|
delete: H.deleteFn || H.DELETE || void 0,
|
|
54802
55855
|
patch: H.patch || H.PATCH || void 0,
|
|
55856
|
+
// v1.0.8 round 4 (BUG 33): route-level OPTIONS handlers
|
|
55857
|
+
options: H.options || H.OPTIONS || void 0,
|
|
54803
55858
|
middleware: H.middleware || void 0,
|
|
54804
55859
|
generate: H.generate || void 0,
|
|
54805
55860
|
config: H.config || void 0,
|
|
@@ -54931,9 +55986,26 @@ async function executeRouteHandler(filePath, method, request, rootDir) {
|
|
|
54931
55986
|
}
|
|
54932
55987
|
} else if (typeof request?.json === "function") {
|
|
54933
55988
|
try {
|
|
54934
|
-
|
|
55989
|
+
const rawBody2 = await request.text();
|
|
55990
|
+
if (rawBody2.length > MAX_BODY_BYTES) {
|
|
55991
|
+
return { status: 413, json: { ok: false, error: "Payload too large" } };
|
|
55992
|
+
}
|
|
55993
|
+
if (!rawBody2) {
|
|
55994
|
+
parsedBody = {};
|
|
55995
|
+
} else if (__ct.includes("application/x-www-form-urlencoded")) {
|
|
55996
|
+
parsedBody = Object.fromEntries(new URLSearchParams(rawBody2).entries());
|
|
55997
|
+
} else if (__ct.startsWith("text/")) {
|
|
55998
|
+
parsedBody = rawBody2;
|
|
55999
|
+
} else if (__ct.includes("json") || __ct === "") {
|
|
56000
|
+
try {
|
|
56001
|
+
parsedBody = JSON.parse(rawBody2);
|
|
56002
|
+
} catch (e) {
|
|
56003
|
+
return { status: 400, json: { ok: false, error: "Invalid JSON body: " + (e?.message ?? "parse error") } };
|
|
56004
|
+
}
|
|
56005
|
+
} else {
|
|
56006
|
+
return { status: 415, json: { ok: false, error: "Unsupported Content-Type: " + (__ct || "(none)") } };
|
|
56007
|
+
}
|
|
54935
56008
|
} catch {
|
|
54936
|
-
parsedBody = {};
|
|
54937
56009
|
}
|
|
54938
56010
|
} else if (request?.body && typeof request.body === "object") {
|
|
54939
56011
|
parsedBody = request.body;
|
|
@@ -54941,7 +56013,18 @@ async function executeRouteHandler(filePath, method, request, rootDir) {
|
|
|
54941
56013
|
let query2 = {};
|
|
54942
56014
|
try {
|
|
54943
56015
|
const u = new URL(request.url);
|
|
54944
|
-
|
|
56016
|
+
for (const [rawKey, value] of u.searchParams.entries()) {
|
|
56017
|
+
const isPhpArray = rawKey.endsWith("[]");
|
|
56018
|
+
const key = isPhpArray ? rawKey.slice(0, -2) : rawKey;
|
|
56019
|
+
const existing = query2[key];
|
|
56020
|
+
if (existing === void 0) {
|
|
56021
|
+
query2[key] = isPhpArray ? [value] : value;
|
|
56022
|
+
} else if (Array.isArray(existing)) {
|
|
56023
|
+
existing.push(value);
|
|
56024
|
+
} else {
|
|
56025
|
+
query2[key] = [existing, value];
|
|
56026
|
+
}
|
|
56027
|
+
}
|
|
54945
56028
|
} catch {
|
|
54946
56029
|
}
|
|
54947
56030
|
const headersObj = typeof request.headers?.entries === "function" ? Object.fromEntries(request.headers.entries()) : request.headers ?? {};
|
|
@@ -55070,9 +56153,17 @@ async function executeRouteHandler(filePath, method, request, rootDir) {
|
|
|
55070
56153
|
console.error("[tw] route handler error (" + filePath + "):", err3?.message ?? err3);
|
|
55071
56154
|
return { status: 500, json: { ok: false, error: "Internal Server Error" } };
|
|
55072
56155
|
}
|
|
55073
|
-
if (
|
|
55074
|
-
|
|
55075
|
-
|
|
56156
|
+
if (typeof result === "string") {
|
|
56157
|
+
result = { status: 200, text: result };
|
|
56158
|
+
} else if (!result || typeof result !== "object") {
|
|
56159
|
+
console.error("[tw] route handler returned " + JSON.stringify(result) + " (" + filePath + ")");
|
|
56160
|
+
return {
|
|
56161
|
+
status: 500,
|
|
56162
|
+
json: {
|
|
56163
|
+
ok: false,
|
|
56164
|
+
error: "Unsupported handler return shape: " + (result === null ? "null" : typeof result) + ". Return { status, json|text|html } (or a plain string for text/plain)."
|
|
56165
|
+
}
|
|
56166
|
+
};
|
|
55076
56167
|
}
|
|
55077
56168
|
const finalResult = {
|
|
55078
56169
|
status: typeof result.status === "number" ? result.status : 200,
|
|
@@ -55211,7 +56302,9 @@ function findCompilerPath2() {
|
|
|
55211
56302
|
return resolve6(process.cwd(), "packages/compiler/tw/index.ts");
|
|
55212
56303
|
}
|
|
55213
56304
|
function extractRenderMode2(src) {
|
|
55214
|
-
const m = /render\s+(static|ssr|island|edge|csr|stream|ppr|signalStream)\b/.exec(
|
|
56305
|
+
const m = /render\s+(static|ssr|island|edge|csr|stream|ppr|signalStream)\b/.exec(
|
|
56306
|
+
maskSourceStringsAndComments(src)
|
|
56307
|
+
);
|
|
55215
56308
|
return m ? m[1] : "static";
|
|
55216
56309
|
}
|
|
55217
56310
|
function csrifyHtml(html) {
|
|
@@ -55325,18 +56418,32 @@ function scanComponents2(rootDir) {
|
|
|
55325
56418
|
for (const d of privDirs) registerComponentsFrom2(d);
|
|
55326
56419
|
}
|
|
55327
56420
|
function buildMetaTagsFromSource(source) {
|
|
55328
|
-
const
|
|
55329
|
-
|
|
55330
|
-
|
|
56421
|
+
const masked = maskSourceStringsAndComments(source);
|
|
56422
|
+
const fm = masked.match(/page\s*\{/);
|
|
56423
|
+
if (!fm || fm.index === void 0) return "";
|
|
56424
|
+
const bodyStart = fm.index + fm[0].length;
|
|
56425
|
+
const bodyEnd = masked.indexOf("}", bodyStart);
|
|
56426
|
+
if (bodyEnd === -1) return "";
|
|
56427
|
+
const body = source.slice(bodyStart, bodyEnd);
|
|
55331
56428
|
const AMP = String.fromCharCode(38);
|
|
55332
56429
|
const esc = (v) => v.split(AMP).join(AMP + "amp;").split(String.fromCharCode(34)).join(AMP + "quot;").split("<").join(AMP + "lt;");
|
|
55333
56430
|
const pick = (key) => {
|
|
55334
56431
|
const i = body.indexOf(key + ' "');
|
|
55335
56432
|
if (i === -1) return null;
|
|
55336
56433
|
const rest = body.slice(i + key.length + 2);
|
|
55337
|
-
|
|
56434
|
+
let end = -1;
|
|
56435
|
+
for (let j = 0; j < rest.length; j++) {
|
|
56436
|
+
if (rest[j] === "\\") {
|
|
56437
|
+
j++;
|
|
56438
|
+
continue;
|
|
56439
|
+
}
|
|
56440
|
+
if (rest[j] === '"') {
|
|
56441
|
+
end = j;
|
|
56442
|
+
break;
|
|
56443
|
+
}
|
|
56444
|
+
}
|
|
55338
56445
|
if (end === -1) return null;
|
|
55339
|
-
return rest.slice(0, end);
|
|
56446
|
+
return rest.slice(0, end).replace(/\\([{}"])/g, "$1");
|
|
55340
56447
|
};
|
|
55341
56448
|
let tags = "";
|
|
55342
56449
|
const description = pick("description");
|
|
@@ -55411,11 +56518,18 @@ async function buildCommand() {
|
|
|
55411
56518
|
} catch {
|
|
55412
56519
|
}
|
|
55413
56520
|
let pageCount = 0;
|
|
56521
|
+
let requestRenderedCount = 0;
|
|
55414
56522
|
let apiCount = 0;
|
|
55415
56523
|
let errorCount = 0;
|
|
55416
56524
|
const isrRoutes = /* @__PURE__ */ new Map();
|
|
55417
56525
|
const pageFiles = [];
|
|
55418
56526
|
collectFiles(homeDir, "page.tw", pageFiles);
|
|
56527
|
+
for (const pf of pageFiles) {
|
|
56528
|
+
if (existsSync16(join20(dirname6(pf), "index.tw"))) {
|
|
56529
|
+
console.log(" \x1B[33m!\x1B[0m " + dirname6(pf).replace(homeDir, "").replace(/^\//, "") + "/ has BOTH page.tw and index.tw");
|
|
56530
|
+
console.log(" index.tw wins this route -- delete one of them");
|
|
56531
|
+
}
|
|
56532
|
+
}
|
|
55419
56533
|
begin("compile", pageFiles.length);
|
|
55420
56534
|
const htmlOutputs = [];
|
|
55421
56535
|
_compilerMod.clearCssCapture?.();
|
|
@@ -55433,11 +56547,20 @@ async function buildCommand() {
|
|
|
55433
56547
|
const pmod = await loadTWMModule2(paramsTwm, rootDir);
|
|
55434
56548
|
if (typeof pmod.generate === "function") {
|
|
55435
56549
|
const arr = pmod.generate();
|
|
55436
|
-
if (Array.isArray(arr) && arr.length > 0)
|
|
55437
|
-
const
|
|
55438
|
-
|
|
55439
|
-
|
|
55440
|
-
|
|
56550
|
+
if (Array.isArray(arr) && arr.length > 0) {
|
|
56551
|
+
const dynNames = pageFile.split("/").map((seg) => /^\[([^\]]+)\]$/.exec(seg)?.[1]).filter(Boolean);
|
|
56552
|
+
paramSets = arr.map((x) => {
|
|
56553
|
+
if (x !== null && typeof x !== "object") {
|
|
56554
|
+
const o2 = {};
|
|
56555
|
+
const key = dynNames[0] ?? "slug";
|
|
56556
|
+
o2[key] = String(x);
|
|
56557
|
+
return o2;
|
|
56558
|
+
}
|
|
56559
|
+
const o = {};
|
|
56560
|
+
for (const [k, v] of Object.entries(x)) o[k] = String(v);
|
|
56561
|
+
return o;
|
|
56562
|
+
});
|
|
56563
|
+
}
|
|
55441
56564
|
}
|
|
55442
56565
|
} catch (e) {
|
|
55443
56566
|
console.log(" Warning: params.twm failed: " + e.message);
|
|
@@ -55466,7 +56589,7 @@ async function buildCommand() {
|
|
|
55466
56589
|
errorCount++;
|
|
55467
56590
|
}
|
|
55468
56591
|
}
|
|
55469
|
-
const revM = resolved ? null : /page\s*\{[^}]*revalidate\s+(\d+)/.exec(pageSrc);
|
|
56592
|
+
const revM = resolved ? null : /page\s*\{[^}]*revalidate\s+(\d+)/.exec(maskSourceStringsAndComments(pageSrc));
|
|
55470
56593
|
if (resolved || revM) {
|
|
55471
56594
|
for (const ps of paramSets) {
|
|
55472
56595
|
const rp = routePath.split("/").map((seg) => {
|
|
@@ -55488,6 +56611,7 @@ async function buildCommand() {
|
|
|
55488
56611
|
}
|
|
55489
56612
|
const hasExplicitParams = paramSets.length > 1 || Object.keys(paramSets[0] ?? {}).length > 0;
|
|
55490
56613
|
if ((renderMode === "ssr" || renderMode === "stream") && !hasExplicitParams) {
|
|
56614
|
+
requestRenderedCount++;
|
|
55491
56615
|
console.log(" \x1B[36m~\x1B[0m " + routePath + " [" + renderMode + ": rendered at request time]");
|
|
55492
56616
|
try {
|
|
55493
56617
|
const src = readFileSync12(pageFile, "utf-8");
|
|
@@ -55498,6 +56622,29 @@ async function buildCommand() {
|
|
|
55498
56622
|
for (const d of errs.slice(0, 5)) console.log(" " + d.line + ":" + d.col + " " + d.message);
|
|
55499
56623
|
errorCount++;
|
|
55500
56624
|
}
|
|
56625
|
+
const warns = (r.diagnostics || []).filter((d) => d.severity !== "error");
|
|
56626
|
+
if (warns.length > 0) {
|
|
56627
|
+
console.log(" \x1B[33m" + warns.length + " warning" + (warns.length === 1 ? "" : "s") + "\x1B[0m");
|
|
56628
|
+
for (const d of warns.slice(0, 3)) {
|
|
56629
|
+
console.log(" " + d.line + ":" + d.col + " " + d.code + " " + d.message);
|
|
56630
|
+
}
|
|
56631
|
+
if (warns.length > 3) console.log(" ... " + (warns.length - 3) + " more (tw check)");
|
|
56632
|
+
}
|
|
56633
|
+
{
|
|
56634
|
+
const used = /* @__PURE__ */ new Set();
|
|
56635
|
+
collectComponentUsages(r.ast, used);
|
|
56636
|
+
const BUILTIN_ALIASES = /* @__PURE__ */ new Set(["Link", "RouterLink", "Image", "optImage", "Suspense"]);
|
|
56637
|
+
const unknownSsr = [...used].filter((t2) => !BUILTIN_ALIASES.has(t2) && !registeredComponentNames.has(t2));
|
|
56638
|
+
if (unknownSsr.length > 0) {
|
|
56639
|
+
const importSpecs = /* @__PURE__ */ new Map();
|
|
56640
|
+
for (const im of src.matchAll(/import\s+([A-Za-z_]\w*)\s+from\s+["']([^"']+)["']/g)) {
|
|
56641
|
+
importSpecs.set(im[1], im[2]);
|
|
56642
|
+
}
|
|
56643
|
+
const hints = unknownSsr.map((t2) => importSpecs.has(t2) ? `<${t2}> (imported from ${importSpecs.get(t2)} -- file not found)` : `<${t2}> (no components/${t2}.tw found)`);
|
|
56644
|
+
console.log(" \x1B[31m\u2717 " + routePath + ": unknown component" + (unknownSsr.length > 1 ? "s" : "") + " " + hints.join(", ") + "\x1B[0m");
|
|
56645
|
+
errorCount++;
|
|
56646
|
+
}
|
|
56647
|
+
}
|
|
55501
56648
|
} catch (e) {
|
|
55502
56649
|
console.log(" \x1B[33m! " + routePath + ": " + (e && e.message ? e.message : e) + "\x1B[0m");
|
|
55503
56650
|
errorCount++;
|
|
@@ -55516,7 +56663,12 @@ async function buildCommand() {
|
|
|
55516
56663
|
const BUILTIN_ALIASES = /* @__PURE__ */ new Set(["Link", "RouterLink", "Image", "optImage", "Suspense"]);
|
|
55517
56664
|
const unknown = [...used].filter((t2) => !BUILTIN_ALIASES.has(t2) && !registeredComponentNames.has(t2));
|
|
55518
56665
|
if (unknown.length > 0) {
|
|
55519
|
-
|
|
56666
|
+
const importSpecs = /* @__PURE__ */ new Map();
|
|
56667
|
+
for (const im of source.matchAll(/import\s+([A-Za-z_]\w*)\s+from\s+["']([^"']+)["']/g)) {
|
|
56668
|
+
importSpecs.set(im[1], im[2]);
|
|
56669
|
+
}
|
|
56670
|
+
const hints = unknown.map((t2) => importSpecs.has(t2) ? `<${t2}> (imported from ${importSpecs.get(t2)} -- file not found)` : `<${t2}> (no components/${t2}.tw found)`);
|
|
56671
|
+
console.error(" \x1B[31m\u2717 " + pageFile.replace(rootDir + "/", "") + ": unknown component" + (unknown.length > 1 ? "s" : "") + " " + hints.join(", ") + " -- create the file or fix the import\x1B[0m");
|
|
55520
56672
|
process.exit(1);
|
|
55521
56673
|
}
|
|
55522
56674
|
}
|
|
@@ -55550,7 +56702,10 @@ async function buildCommand() {
|
|
|
55550
56702
|
const headResult = _compileSync(headSource, { filePath: headSrc });
|
|
55551
56703
|
const headBody = headResult.html.match(/<body>([\s\S]*)<\/body>/);
|
|
55552
56704
|
if (headBody && headBody[1].trim()) {
|
|
55553
|
-
html = html.replace(
|
|
56705
|
+
html = html.replace(
|
|
56706
|
+
"</head>",
|
|
56707
|
+
" " + headBody[1].trim().replace(/\\([{}])/g, "$1") + "\n</head>"
|
|
56708
|
+
);
|
|
55554
56709
|
}
|
|
55555
56710
|
} catch (e) {
|
|
55556
56711
|
console.log(" Warning: head.tw failed: " + e.message);
|
|
@@ -55605,9 +56760,12 @@ async function buildCommand() {
|
|
|
55605
56760
|
if (pu) clientTag += `<script defer src="/${pu}"></script>
|
|
55606
56761
|
`;
|
|
55607
56762
|
}
|
|
55608
|
-
const
|
|
55609
|
-
|
|
55610
|
-
|
|
56763
|
+
const needsClient = interactive || pageMods.deps.length > 0 || pageMods.own.length > 0 || renderMode === "csr" || renderMode === "ppr" || renderMode === "stream" || renderMode === "signalStream";
|
|
56764
|
+
if (needsClient) {
|
|
56765
|
+
const runtimeTag = `<script defer src="/__tw_runtime.js"></script>`;
|
|
56766
|
+
html = html.replace("</body>", " " + clientTag + runtimeTag + "\n</body>");
|
|
56767
|
+
needsRuntime = true;
|
|
56768
|
+
}
|
|
55611
56769
|
}
|
|
55612
56770
|
if (renderMode === "csr") {
|
|
55613
56771
|
html = csrifyHtml(html);
|
|
@@ -55662,6 +56820,14 @@ async function buildCommand() {
|
|
|
55662
56820
|
} else {
|
|
55663
56821
|
console.log(" \x1B[32mOK\x1B[0m " + displayPath + " -> " + (outRoute === "" ? ".tw/index.html" : ".tw/" + outRoute + "/index.html"));
|
|
55664
56822
|
}
|
|
56823
|
+
const warnings = result.diagnostics.filter((d) => d.severity !== "error");
|
|
56824
|
+
if (warnings.length > 0) {
|
|
56825
|
+
console.log(" \x1B[33m" + warnings.length + " warning" + (warnings.length === 1 ? "" : "s") + "\x1B[0m");
|
|
56826
|
+
for (const d of warnings.slice(0, 3)) {
|
|
56827
|
+
console.log(" " + d.line + ":" + d.col + " " + d.code + " " + d.message);
|
|
56828
|
+
}
|
|
56829
|
+
if (warnings.length > 3) console.log(" ... " + (warnings.length - 3) + " more (tw check)");
|
|
56830
|
+
}
|
|
55665
56831
|
pageCount++;
|
|
55666
56832
|
} catch (err3) {
|
|
55667
56833
|
console.log(" \x1B[31mX " + routePath + ": " + err3.message + "\x1B[0m");
|
|
@@ -55680,8 +56846,16 @@ async function buildCommand() {
|
|
|
55680
56846
|
const outputDir = routeName === "" ? outDir : join20(outDir, routeName);
|
|
55681
56847
|
mkdirSync6(outputDir, { recursive: true });
|
|
55682
56848
|
copyFileSync2(twmFile, join20(outputDir, "route.twm"));
|
|
55683
|
-
|
|
55684
|
-
|
|
56849
|
+
const { validateTWMSyntax: validateTWMSyntax2 } = await Promise.resolve().then(() => (init_twm_loader(), twm_loader_exports));
|
|
56850
|
+
const syntaxError = validateTWMSyntax2(twmFile);
|
|
56851
|
+
if (syntaxError) {
|
|
56852
|
+
console.log(" \x1B[31mX\x1B[0m /" + routeName + " [api] -- .twm syntax error");
|
|
56853
|
+
console.log(" " + twmFile.replace(rootDir + "/", "") + ": " + syntaxError);
|
|
56854
|
+
errorCount++;
|
|
56855
|
+
} else {
|
|
56856
|
+
console.log(" \x1B[32mOK\x1B[0m /" + routeName + " [api]");
|
|
56857
|
+
apiCount++;
|
|
56858
|
+
}
|
|
55685
56859
|
}
|
|
55686
56860
|
if (existsSync16(libDir)) {
|
|
55687
56861
|
copyDir2(libDir, join20(outDir, "lib"));
|
|
@@ -55760,7 +56934,8 @@ async function buildCommand() {
|
|
|
55760
56934
|
}
|
|
55761
56935
|
}
|
|
55762
56936
|
console.log("\n \x1B[32mBuild complete!\x1B[0m");
|
|
55763
|
-
console.log(" Pages: " + pageCount + " compiled");
|
|
56937
|
+
console.log(" Pages: " + pageCount + " compiled" + (requestRenderedCount > 0 ? " (+" + requestRenderedCount + " rendered at request time)" : ""));
|
|
56938
|
+
if (pageCount === 0) console.warn(" [!] No static pages were compiled -- every page is SSR/API. Static hosts (Vercel/Cloudflare Pages output) will serve an empty site. Use `render static` or check your render modes.");
|
|
55764
56939
|
console.log(" APIs: " + apiCount + " routes");
|
|
55765
56940
|
if (isrRoutes.size > 0) {
|
|
55766
56941
|
const manifest = {};
|
|
@@ -57709,9 +58884,11 @@ var init_provider2 = __esm({
|
|
|
57709
58884
|
getExpressionCompletions(doc) {
|
|
57710
58885
|
const items = [];
|
|
57711
58886
|
const definedVars = /* @__PURE__ */ new Set();
|
|
57712
|
-
const
|
|
58887
|
+
const scriptMatches = [...doc.content.matchAll(/<script[^>]*>([\s\S]*?)<\/script>/gi)];
|
|
58888
|
+
const scriptMatch = scriptMatches.length ? [scriptMatches.map((m) => m[1]).join("\n")] : null;
|
|
58889
|
+
const scriptContent = scriptMatch ? scriptMatch[0] : "";
|
|
57713
58890
|
if (scriptMatch) {
|
|
57714
|
-
const sc =
|
|
58891
|
+
const sc = scriptContent;
|
|
57715
58892
|
const varRegex = /(?:const|let|var)\s+(\w+)/g;
|
|
57716
58893
|
let m;
|
|
57717
58894
|
while ((m = varRegex.exec(sc)) !== null) {
|
|
@@ -58286,8 +59463,10 @@ var init_symbols = __esm({
|
|
|
58286
59463
|
const doc = this.docManager.getDocument(uri);
|
|
58287
59464
|
if (!doc) return [];
|
|
58288
59465
|
const symbols = [];
|
|
58289
|
-
const
|
|
58290
|
-
|
|
59466
|
+
const scriptMatches = [...doc.content.matchAll(/<script[^>]*>([\s\S]*?)<\/script>/gi)];
|
|
59467
|
+
const scriptMatch = scriptMatches.length ? [scriptMatches.map((m) => m[1]).join("\n")] : null;
|
|
59468
|
+
const scriptContent = scriptMatch ? scriptMatch[0] : "";
|
|
59469
|
+
if (scriptMatch) symbols.push(...this.parseScriptSymbols(scriptContent, this.findScriptStart(doc)));
|
|
58291
59470
|
symbols.push(...this.parseTemplateSymbols(doc));
|
|
58292
59471
|
return symbols;
|
|
58293
59472
|
}
|
|
@@ -58600,6 +59779,11 @@ var init_provider14 = __esm({
|
|
|
58600
59779
|
});
|
|
58601
59780
|
|
|
58602
59781
|
// ../../packages/lsp/tw/code-lens/provider.ts
|
|
59782
|
+
function stripLineComments(src) {
|
|
59783
|
+
return src.split("\n").map(function(l) {
|
|
59784
|
+
return l.replace(/(^|\s)\/\/.*$/, "");
|
|
59785
|
+
}).join("\n");
|
|
59786
|
+
}
|
|
58603
59787
|
var CodeLensProvider;
|
|
58604
59788
|
var init_provider15 = __esm({
|
|
58605
59789
|
"../../packages/lsp/tw/code-lens/provider.ts"() {
|
|
@@ -58632,7 +59816,7 @@ var init_provider15 = __esm({
|
|
|
58632
59816
|
const esc = symbol.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
58633
59817
|
const regex = new RegExp(`\\b${esc}\\b`, "g");
|
|
58634
59818
|
for (const d of this.docManager.getAllDocuments()) {
|
|
58635
|
-
const m = d.content.match(regex);
|
|
59819
|
+
const m = stripLineComments(d.content).match(regex);
|
|
58636
59820
|
if (m) count += m.length - 1;
|
|
58637
59821
|
}
|
|
58638
59822
|
return Math.max(0, count);
|
|
@@ -58642,7 +59826,7 @@ var init_provider15 = __esm({
|
|
|
58642
59826
|
const esc = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
58643
59827
|
const regex = new RegExp(`<${esc}\\b`, "g");
|
|
58644
59828
|
for (const d of this.docManager.getAllDocuments()) {
|
|
58645
|
-
const m = d.content.match(regex);
|
|
59829
|
+
const m = stripLineComments(d.content).match(regex);
|
|
58646
59830
|
if (m) count += m.length;
|
|
58647
59831
|
}
|
|
58648
59832
|
return count;
|
|
@@ -59854,15 +61038,37 @@ ${name}/
|
|
|
59854
61038
|
async function createVercelJson(dir) {
|
|
59855
61039
|
const config = {
|
|
59856
61040
|
buildCommand: "npx tw build",
|
|
59857
|
-
|
|
59858
|
-
|
|
59859
|
-
|
|
61041
|
+
installCommand: "npm install",
|
|
61042
|
+
functions: {
|
|
61043
|
+
"api/tw-server.ts": { memory: 512 }
|
|
61044
|
+
},
|
|
61045
|
+
rewrites: [
|
|
61046
|
+
{ source: "/(.*)", destination: "/api/tw-server" }
|
|
61047
|
+
]
|
|
59860
61048
|
};
|
|
59861
61049
|
await writeFile(join(dir, "vercel.json"), JSON.stringify(config, null, 2));
|
|
59862
61050
|
console.log(" \u2713 vercel.json");
|
|
61051
|
+
const fnDir = join(dir, "api");
|
|
61052
|
+
await mkdir(fnDir, { recursive: true });
|
|
61053
|
+
await writeFile(join(fnDir, "tw-server.ts"), [
|
|
61054
|
+
"// TW Framework -- Vercel serverless entry (v1.0.8 round 5, BUG 39).",
|
|
61055
|
+
"// Runs the full TW server (SSR pages + .twm API routes) as a Vercel",
|
|
61056
|
+
"// function. vercel.json rewrites every route here.",
|
|
61057
|
+
'import { TWServer } from "tw-framework/server";',
|
|
61058
|
+
"",
|
|
61059
|
+
"const server = new TWServer({",
|
|
61060
|
+
" rootDir: process.cwd(),",
|
|
61061
|
+
"});",
|
|
61062
|
+
"",
|
|
61063
|
+
"export default async function handler(request: Request): Promise<Response> {",
|
|
61064
|
+
" return server.handleRequest(request);",
|
|
61065
|
+
"}",
|
|
61066
|
+
""
|
|
61067
|
+
].join("\n"));
|
|
61068
|
+
console.log(" \u2713 api/tw-server.ts");
|
|
59863
61069
|
}
|
|
59864
61070
|
async function createDefaultProject(dir, name) {
|
|
59865
|
-
const T = (s) => s;
|
|
61071
|
+
const T = (s) => s.split("${name}").join(name);
|
|
59866
61072
|
for (const d of [
|
|
59867
61073
|
["home"],
|
|
59868
61074
|
["home", "about"],
|
|
@@ -59885,8 +61091,6 @@ html {
|
|
|
59885
61091
|
head {
|
|
59886
61092
|
meta charset "utf-8"
|
|
59887
61093
|
meta name "viewport" content "width=device-width, initial-scale=1"
|
|
59888
|
-
meta name "description" content "{page.description}"
|
|
59889
|
-
title "{page.title} -- ${"$"}{name}"
|
|
59890
61094
|
}
|
|
59891
61095
|
body {
|
|
59892
61096
|
header.sitenav {
|
|
@@ -60222,6 +61426,10 @@ a:hover { td underline }
|
|
|
60222
61426
|
// This sample blocks known bot user agents everywhere. Guards are
|
|
60223
61427
|
// fail-closed: a matching request that fails the condition gets the
|
|
60224
61428
|
// rule's response, and no page code ever runs.
|
|
61429
|
+
//
|
|
61430
|
+
// Note: a rule with ONLY match + response (no condition blocks) applies
|
|
61431
|
+
// to EVERY matching request -- e.g. a site-wide maintenance page. tw serve
|
|
61432
|
+
// lists each rule at startup and flags those unconditional ones.
|
|
60225
61433
|
|
|
60226
61434
|
rule "block bots" {
|
|
60227
61435
|
match "/**"
|
|
@@ -60983,38 +62191,15 @@ function destroySession(req) {
|
|
|
60983
62191
|
const match = cookie.match(new RegExp(`${SESSION_COOKIE}=([^;]+)`));
|
|
60984
62192
|
if (match) sessions.delete(match[1]);
|
|
60985
62193
|
}
|
|
60986
|
-
function
|
|
60987
|
-
|
|
60988
|
-
|
|
60989
|
-
|
|
60990
|
-
|
|
60991
|
-
|
|
60992
|
-
|
|
60993
|
-
|
|
60994
|
-
const name = match[1];
|
|
60995
|
-
const body = match[2];
|
|
60996
|
-
const pathMatch = body.match(/match\s+"([^"]+)"/);
|
|
60997
|
-
const authMatch = body.match(/auth\s+(true|false)/);
|
|
60998
|
-
const redirectMatch = body.match(/redirect\s+"([^"]+)"/);
|
|
60999
|
-
if (pathMatch) {
|
|
61000
|
-
rules.push({
|
|
61001
|
-
name,
|
|
61002
|
-
match: pathMatch[1],
|
|
61003
|
-
auth: authMatch ? authMatch[1] === "true" : false,
|
|
61004
|
-
redirect: redirectMatch ? redirectMatch[1] : "/login"
|
|
61005
|
-
});
|
|
61006
|
-
}
|
|
61007
|
-
}
|
|
61008
|
-
return rules;
|
|
61009
|
-
}
|
|
61010
|
-
function matchGlob(path, pattern) {
|
|
61011
|
-
if (pattern === "/**") return true;
|
|
61012
|
-
if (pattern === path) return true;
|
|
61013
|
-
if (pattern.endsWith("/**")) {
|
|
61014
|
-
const prefix = pattern.slice(0, -3);
|
|
61015
|
-
return path === prefix || path.startsWith(prefix + "/");
|
|
62194
|
+
async function loadMiddlewareRules(mwPath) {
|
|
62195
|
+
try {
|
|
62196
|
+
const source = readFileSync11(mwPath, "utf-8");
|
|
62197
|
+
const { parseRules: parseRules2 } = await Promise.resolve().then(() => (init_twm_rules(), twm_rules_exports));
|
|
62198
|
+
return parseRules2(source);
|
|
62199
|
+
} catch (e) {
|
|
62200
|
+
console.log(" Middleware error: " + e.message);
|
|
62201
|
+
return [];
|
|
61016
62202
|
}
|
|
61017
|
-
return false;
|
|
61018
62203
|
}
|
|
61019
62204
|
function get404() {
|
|
61020
62205
|
return `<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>404 - Page Not Found</title><style>${DEFAULT_CSS}</style></head><body><div class="wrapper"><section class="hero"><div class="container"><h1>404</h1><p>Page not found</p><a class="btn" href="/">Go Home</a></div></section></div></body></html>`;
|
|
@@ -61093,7 +62278,8 @@ async function devCommand() {
|
|
|
61093
62278
|
if (existsSync15(stylePath)) parts.push(readFileSync11(stylePath, "utf-8"));
|
|
61094
62279
|
if (parts.length) css = parts.join("\n");
|
|
61095
62280
|
}
|
|
61096
|
-
const
|
|
62281
|
+
const mwPath = join19(rootDir, "middleware.twm");
|
|
62282
|
+
const middlewareRules = existsSync15(mwPath) ? await loadMiddlewareRules(mwPath) : [];
|
|
61097
62283
|
scanComponents(rootDir);
|
|
61098
62284
|
const compDir = join19(rootDir, "components");
|
|
61099
62285
|
if (existsSync15(compDir)) {
|
|
@@ -61168,13 +62354,13 @@ async function devCommand() {
|
|
|
61168
62354
|
}
|
|
61169
62355
|
async function runUserMiddleware(req) {
|
|
61170
62356
|
try {
|
|
61171
|
-
const
|
|
61172
|
-
if (!existsSync15(
|
|
62357
|
+
const mwPath2 = join19(rootDir, "middleware.twm");
|
|
62358
|
+
if (!existsSync15(mwPath2)) return null;
|
|
61173
62359
|
const { executeMiddleware: executeMiddleware2, loadTWMModule: loadTWMModule2, shouldMatchMiddleware: shouldMatchMiddleware2 } = await Promise.resolve().then(() => (init_twm_loader(), twm_loader_exports));
|
|
61174
|
-
const mod = await loadTWMModule2(
|
|
62360
|
+
const mod = await loadTWMModule2(mwPath2, rootDir);
|
|
61175
62361
|
const mwPathname = new URL(req.url).pathname;
|
|
61176
62362
|
if (!mod.config?.matcher || shouldMatchMiddleware2(mod.config.matcher, mwPathname)) {
|
|
61177
|
-
const mwResult = await executeMiddleware2(
|
|
62363
|
+
const mwResult = await executeMiddleware2(mwPath2, req, rootDir);
|
|
61178
62364
|
if (mwResult instanceof Response) return mwResult;
|
|
61179
62365
|
if (mwResult && typeof mwResult === "object" && mwResult.headers && !mwResult.status && !mwResult.json) {
|
|
61180
62366
|
return mwResult.headers;
|
|
@@ -61207,6 +62393,7 @@ async function devCommand() {
|
|
|
61207
62393
|
}
|
|
61208
62394
|
let devRedirects = [];
|
|
61209
62395
|
let devCfgHeaders = [];
|
|
62396
|
+
let devSigCfg = void 0;
|
|
61210
62397
|
try {
|
|
61211
62398
|
const cfgFile = join19(rootDir, "tw.config.ts");
|
|
61212
62399
|
if (existsSync15(cfgFile)) {
|
|
@@ -61219,27 +62406,38 @@ async function devCommand() {
|
|
|
61219
62406
|
if (cfg?.headers) {
|
|
61220
62407
|
devCfgHeaders = Array.isArray(cfg.headers) ? cfg.headers : Object.entries(cfg.headers).map(([source, headers]) => ({ source, headers }));
|
|
61221
62408
|
}
|
|
62409
|
+
if (cfg?.signalStream) devSigCfg = cfg.signalStream;
|
|
61222
62410
|
}
|
|
61223
62411
|
} catch {
|
|
61224
62412
|
}
|
|
61225
62413
|
const devPathMatch = (pattern, pathname) => pattern === pathname || pattern.endsWith("/**") && pathname.startsWith(pattern.slice(0, -2) + "/") || pattern.endsWith("/") && pathname.startsWith(pattern) || pattern === "/**" && pathname.startsWith("/");
|
|
61226
62414
|
async function devFetchInner(req) {
|
|
61227
62415
|
const url = new URL(req.url);
|
|
62416
|
+
try {
|
|
62417
|
+
let rawPath = url.pathname;
|
|
62418
|
+
const decoded = decodeURIComponent(rawPath);
|
|
62419
|
+
if (decoded !== rawPath) rawPath = decoded;
|
|
62420
|
+
let canonical = rawPath.replace(/\/{2,}/g, "/");
|
|
62421
|
+
if (canonical.length > 1 && canonical.endsWith("/")) canonical = canonical.slice(0, -1);
|
|
62422
|
+
if (canonical !== rawPath) {
|
|
62423
|
+
return new Response(null, { status: 308, headers: { Location: encodeURI(canonical) + url.search } });
|
|
62424
|
+
}
|
|
62425
|
+
} catch {
|
|
62426
|
+
}
|
|
61228
62427
|
const path = url.pathname;
|
|
61229
62428
|
const time = (/* @__PURE__ */ new Date()).toISOString().split("T")[1].split(".")[0];
|
|
61230
62429
|
rescanComponentsIfNeeded(rootDir);
|
|
61231
62430
|
console.log(" [" + time + "] " + req.method + " " + path);
|
|
61232
62431
|
const session = getSession(req);
|
|
61233
|
-
|
|
61234
|
-
|
|
61235
|
-
|
|
61236
|
-
|
|
61237
|
-
|
|
61238
|
-
|
|
61239
|
-
headers: { Location: rule.redirect }
|
|
61240
|
-
});
|
|
61241
|
-
}
|
|
62432
|
+
try {
|
|
62433
|
+
const { evaluateRules: evaluateRules2 } = await Promise.resolve().then(() => (init_twm_rules(), twm_rules_exports));
|
|
62434
|
+
const mwRes = evaluateRules2(middlewareRules, req);
|
|
62435
|
+
if (mwRes) {
|
|
62436
|
+
console.log(" -> middleware response " + mwRes.status + " for " + path);
|
|
62437
|
+
return mwRes;
|
|
61242
62438
|
}
|
|
62439
|
+
} catch (e) {
|
|
62440
|
+
console.log(" Middleware error: " + e.message);
|
|
61243
62441
|
}
|
|
61244
62442
|
if (path === "/__tw_runtime.js") {
|
|
61245
62443
|
const _fu = import.meta.url.replace("file://", "");
|
|
@@ -61449,13 +62647,20 @@ async function devCommand() {
|
|
|
61449
62647
|
if (/<\/body>/.test(html)) {
|
|
61450
62648
|
html = html.replace("</body>", devClientTag + ' <script src="/__tw_runtime.js"></script>\n</body>');
|
|
61451
62649
|
}
|
|
62650
|
+
const devGlobalHead = join19(homeDir, "head.tw");
|
|
62651
|
+
const devHeadSources = [];
|
|
62652
|
+
if (existsSync15(devGlobalHead)) devHeadSources.push(devGlobalHead);
|
|
61452
62653
|
const headTw = join19(dirname5(resolved.file), "head.tw");
|
|
61453
|
-
if (existsSync15(headTw))
|
|
62654
|
+
if (headTw !== devGlobalHead && existsSync15(headTw)) devHeadSources.push(headTw);
|
|
62655
|
+
for (const devHeadTw of devHeadSources) {
|
|
61454
62656
|
try {
|
|
61455
|
-
const headRes = compileSync3(readFileSync11(
|
|
62657
|
+
const headRes = compileSync3(readFileSync11(devHeadTw, "utf-8"), { filePath: devHeadTw });
|
|
61456
62658
|
const headBody = headRes.html.match(/<body>([\s\S]*)<\/body>/);
|
|
61457
62659
|
if (headBody && headBody[1].trim()) {
|
|
61458
|
-
html = html.replace(
|
|
62660
|
+
html = html.replace(
|
|
62661
|
+
"</head>",
|
|
62662
|
+
" " + headBody[1].trim().replace(/\\([{}])/g, "$1") + "\n</head>"
|
|
62663
|
+
);
|
|
61459
62664
|
}
|
|
61460
62665
|
} catch (e) {
|
|
61461
62666
|
console.log(" Warning: head.tw failed: " + e.message);
|
|
@@ -61498,56 +62703,108 @@ async function devCommand() {
|
|
|
61498
62703
|
headers: { "Content-Type": "text/html" }
|
|
61499
62704
|
});
|
|
61500
62705
|
}
|
|
61501
|
-
|
|
61502
|
-
|
|
61503
|
-
|
|
61504
|
-
|
|
61505
|
-
|
|
61506
|
-
|
|
61507
|
-
|
|
61508
|
-
|
|
61509
|
-
|
|
61510
|
-
|
|
61511
|
-
|
|
61512
|
-
|
|
61513
|
-
|
|
61514
|
-
|
|
61515
|
-
|
|
61516
|
-
if (
|
|
61517
|
-
|
|
61518
|
-
|
|
61519
|
-
|
|
61520
|
-
|
|
61521
|
-
}
|
|
61522
|
-
|
|
61523
|
-
|
|
61524
|
-
|
|
61525
|
-
|
|
61526
|
-
|
|
61527
|
-
|
|
61528
|
-
|
|
62706
|
+
async function devHandle(req) {
|
|
62707
|
+
{
|
|
62708
|
+
const u3 = new URL(req.url);
|
|
62709
|
+
if (u3.pathname === "/_tw/stream") {
|
|
62710
|
+
const { createSignalStream: createSignalStream2 } = await Promise.resolve().then(() => (init_signal_stream(), signal_stream_exports));
|
|
62711
|
+
return createSignalStream2(req, u3, devSigCfg ? { maxClients: devSigCfg.maxClients, maxHistory: devSigCfg.maxHistory } : void 0);
|
|
62712
|
+
}
|
|
62713
|
+
if (u3.pathname === "/_tw/signal" && req.method === "POST") {
|
|
62714
|
+
const { writeSignalFromClient: writeSignalFromClient2 } = await Promise.resolve().then(() => (init_signal_stream(), signal_stream_exports));
|
|
62715
|
+
return writeSignalFromClient2(req, { clientWrites: devSigCfg?.clientWrites });
|
|
62716
|
+
}
|
|
62717
|
+
}
|
|
62718
|
+
const reqUrl = new URL(req.url);
|
|
62719
|
+
for (const r of devRedirects) {
|
|
62720
|
+
if (!r?.from || !r?.to) continue;
|
|
62721
|
+
if (devPathMatch(r.from, reqUrl.pathname)) {
|
|
62722
|
+
const status = [301, 302, 307, 308].includes(r.status) ? r.status : 301;
|
|
62723
|
+
let loc = r.to.startsWith("http") ? r.to : new URL(r.to, req.url).toString();
|
|
62724
|
+
if (reqUrl.search && !loc.includes("?")) loc += reqUrl.search;
|
|
62725
|
+
return new Response(null, { status, headers: { Location: loc } });
|
|
62726
|
+
}
|
|
62727
|
+
}
|
|
62728
|
+
const mwHeaders = await runUserMiddleware(req);
|
|
62729
|
+
if (mwHeaders instanceof Response) return mwHeaders;
|
|
62730
|
+
const u = new URL(req.url);
|
|
62731
|
+
if (u.pathname === "/_tw/img" || u.pathname.startsWith("/_tw/img/")) {
|
|
62732
|
+
const imgRes = await devImageHandler?.handle(req);
|
|
62733
|
+
if (imgRes) return imgRes;
|
|
62734
|
+
}
|
|
62735
|
+
const response = await devFetchInner(req);
|
|
62736
|
+
if (mwHeaders && typeof mwHeaders === "object") {
|
|
62737
|
+
for (const key of Object.keys(mwHeaders)) {
|
|
62738
|
+
try {
|
|
62739
|
+
if (!response.headers.has(key)) response.headers.set(key, mwHeaders[key]);
|
|
62740
|
+
} catch {
|
|
61529
62741
|
}
|
|
61530
62742
|
}
|
|
61531
|
-
|
|
61532
|
-
|
|
61533
|
-
|
|
61534
|
-
|
|
61535
|
-
|
|
61536
|
-
|
|
61537
|
-
|
|
61538
|
-
|
|
62743
|
+
}
|
|
62744
|
+
const u2 = new URL(req.url);
|
|
62745
|
+
for (const h of devCfgHeaders) {
|
|
62746
|
+
if (h?.source && h?.headers && devPathMatch(h.source, u2.pathname)) {
|
|
62747
|
+
for (const key of Object.keys(h.headers)) {
|
|
62748
|
+
try {
|
|
62749
|
+
if (!response.headers.has(key)) response.headers.set(key, String(h.headers[key]).replace(/[\r\n]/g, ""));
|
|
62750
|
+
} catch {
|
|
61539
62751
|
}
|
|
61540
62752
|
}
|
|
61541
62753
|
}
|
|
61542
|
-
|
|
61543
|
-
|
|
61544
|
-
|
|
61545
|
-
|
|
61546
|
-
|
|
62754
|
+
}
|
|
62755
|
+
if (devSecurityHeaders2) {
|
|
62756
|
+
try {
|
|
62757
|
+
return devSecurityHeaders2.apply(response);
|
|
62758
|
+
} catch {
|
|
61547
62759
|
}
|
|
61548
|
-
return response;
|
|
61549
62760
|
}
|
|
61550
|
-
|
|
62761
|
+
return response;
|
|
62762
|
+
}
|
|
62763
|
+
if (typeof Bun !== "undefined" && typeof Bun.serve === "function") {
|
|
62764
|
+
Bun.serve({ port, hostname: host, fetch: devHandle });
|
|
62765
|
+
} else {
|
|
62766
|
+
const { createServer } = await import("node:http");
|
|
62767
|
+
const srv = createServer(async (nreq, nres) => {
|
|
62768
|
+
try {
|
|
62769
|
+
const hostHdr = String(nreq.headers.host ?? `${host}:${port}`);
|
|
62770
|
+
const chunks = [];
|
|
62771
|
+
for await (const c of nreq) chunks.push(Buffer.from(c));
|
|
62772
|
+
const raw = nreq.method === "GET" || nreq.method === "HEAD" ? void 0 : Buffer.concat(chunks);
|
|
62773
|
+
const request = new Request(`http://${hostHdr}${nreq.url}`, {
|
|
62774
|
+
method: nreq.method,
|
|
62775
|
+
headers: nreq.headers,
|
|
62776
|
+
body: raw && raw.length > 0 ? raw : void 0
|
|
62777
|
+
});
|
|
62778
|
+
const resp = await devHandle(request);
|
|
62779
|
+
const out = Buffer.from(await resp.arrayBuffer());
|
|
62780
|
+
const headers = {};
|
|
62781
|
+
resp.headers.forEach((v, k) => {
|
|
62782
|
+
headers[k] = v;
|
|
62783
|
+
});
|
|
62784
|
+
nres.writeHead(resp.status, headers);
|
|
62785
|
+
nres.end(out);
|
|
62786
|
+
} catch (e) {
|
|
62787
|
+
nres.writeHead(500, { "Content-Type": "text/plain" });
|
|
62788
|
+
nres.end("Internal dev server error: " + (e?.message ?? e));
|
|
62789
|
+
}
|
|
62790
|
+
});
|
|
62791
|
+
await new Promise((r) => srv.listen(port, host, () => r()));
|
|
62792
|
+
console.log(" [tw] runtime: node (Bun not found \u2014 using node:http)");
|
|
62793
|
+
}
|
|
62794
|
+
try {
|
|
62795
|
+
const { watch: watch2 } = await import("node:fs");
|
|
62796
|
+
const seen = /* @__PURE__ */ new Set();
|
|
62797
|
+
watch2(rootDir, { recursive: true }, (_t, f) => {
|
|
62798
|
+
const name = String(f);
|
|
62799
|
+
if (name.includes("node_modules") || name.includes(".git") || name.split("/").includes(".tw")) return;
|
|
62800
|
+
if (seen.has(name)) return;
|
|
62801
|
+
seen.add(name);
|
|
62802
|
+
const t2 = setTimeout(() => seen.delete(name), 400);
|
|
62803
|
+
if (typeof t2?.unref === "function") t2.unref();
|
|
62804
|
+
console.log(` [tw] ${name} changed -- reload the browser`);
|
|
62805
|
+
});
|
|
62806
|
+
} catch {
|
|
62807
|
+
}
|
|
61551
62808
|
console.log(" Server running! Open http://" + host + ":" + port + " in your browser.");
|
|
61552
62809
|
console.log(" Press Ctrl+C to stop.\n");
|
|
61553
62810
|
}
|
|
@@ -61560,6 +62817,25 @@ import { join as join22 } from "node:path";
|
|
|
61560
62817
|
import { existsSync as existsSync18 } from "node:fs";
|
|
61561
62818
|
async function serveCommand() {
|
|
61562
62819
|
const args = process.argv.slice(3);
|
|
62820
|
+
if (args.includes("-h") || args.includes("--help")) {
|
|
62821
|
+
console.log(`
|
|
62822
|
+
tw serve \u2014 start the production server
|
|
62823
|
+
|
|
62824
|
+
Usage:
|
|
62825
|
+
tw serve [--port <number>] [--host <address>]
|
|
62826
|
+
|
|
62827
|
+
Options:
|
|
62828
|
+
--port <number> port to listen on (default: 8000; tw.config.ts
|
|
62829
|
+
server.port and the PORT env var also work,
|
|
62830
|
+
in that order of precedence)
|
|
62831
|
+
--host <address> bind address (default: 0.0.0.0)
|
|
62832
|
+
-h, --help show this help
|
|
62833
|
+
|
|
62834
|
+
Requires a build first (tw build); serves .tw/ output with SSR,
|
|
62835
|
+
middleware and API routes.
|
|
62836
|
+
`);
|
|
62837
|
+
process.exit(0);
|
|
62838
|
+
}
|
|
61563
62839
|
let port = null;
|
|
61564
62840
|
let host = "0.0.0.0";
|
|
61565
62841
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -61574,8 +62850,8 @@ async function serveCommand() {
|
|
|
61574
62850
|
const envPath = join22(rootDir, ".env");
|
|
61575
62851
|
if (existsSync18(envPath)) {
|
|
61576
62852
|
try {
|
|
61577
|
-
const { readFileSync:
|
|
61578
|
-
for (const line of
|
|
62853
|
+
const { readFileSync: readFileSync20 } = await import("node:fs");
|
|
62854
|
+
for (const line of readFileSync20(envPath, "utf-8").split("\n")) {
|
|
61579
62855
|
const m = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)\s*$/.exec(line);
|
|
61580
62856
|
if (m && !(m[1] in process.env)) {
|
|
61581
62857
|
process.env[m[1]] = m[2].replace(/^["']|["']$/g, "");
|
|
@@ -61621,7 +62897,7 @@ async function serveCommand() {
|
|
|
61621
62897
|
staticDir: outDir,
|
|
61622
62898
|
config,
|
|
61623
62899
|
...config?.rateLimit ? { rateLimit: config.rateLimit } : {},
|
|
61624
|
-
...
|
|
62900
|
+
...config?.compression !== void 0 || config?.server?.compression !== void 0 ? { compression: config?.compression ?? config?.server?.compression } : {},
|
|
61625
62901
|
...pluginManager && pluginManager.size() > 0 ? { plugins: pluginManager } : {},
|
|
61626
62902
|
...config?.images ? { images: config.images } : {},
|
|
61627
62903
|
...config?.security ? { security: config.security } : {}
|
|
@@ -61900,7 +63176,7 @@ async function adapterCommand() {
|
|
|
61900
63176
|
// tw/commands/test.ts
|
|
61901
63177
|
import { join as join27 } from "node:path";
|
|
61902
63178
|
import { createRequire as _cr } from "node:module";
|
|
61903
|
-
import { existsSync as existsSync22, readdirSync as readdirSync9, statSync as statSync9 } from "node:fs";
|
|
63179
|
+
import { existsSync as existsSync22, readdirSync as readdirSync9, statSync as statSync9, readFileSync as readFileSync15 } from "node:fs";
|
|
61904
63180
|
var _require = _cr(import.meta.url);
|
|
61905
63181
|
function collectTests(dir, out) {
|
|
61906
63182
|
if (!existsSync22(dir)) return;
|
|
@@ -61956,6 +63232,17 @@ async function testCommand() {
|
|
|
61956
63232
|
}
|
|
61957
63233
|
}
|
|
61958
63234
|
console.log("\n tw test -- running " + files.length + " test file" + (files.length === 1 ? "" : "s") + "\n");
|
|
63235
|
+
for (const f of files) {
|
|
63236
|
+
try {
|
|
63237
|
+
const src = readFileSync15(f, "utf-8");
|
|
63238
|
+
const hasBunTest = src.includes("bun:test");
|
|
63239
|
+
const hasTestCall = /\b(test|it|describe)\s*\(/.test(src);
|
|
63240
|
+
if (!hasBunTest && !hasTestCall) {
|
|
63241
|
+
console.log(" \x1B[33m! " + f.replace(rootDir + "/", "") + ' defined no tests -- only bun:test format runs (import { test, expect } from "bun:test")\x1B[0m');
|
|
63242
|
+
}
|
|
63243
|
+
} catch {
|
|
63244
|
+
}
|
|
63245
|
+
}
|
|
61959
63246
|
if (files.length === 0) {
|
|
61960
63247
|
console.log(" No tests found. Create tests/your-name.test.ts:");
|
|
61961
63248
|
console.log(`
|
|
@@ -61987,8 +63274,9 @@ async function testCommand() {
|
|
|
61987
63274
|
}
|
|
61988
63275
|
|
|
61989
63276
|
// tw/commands/check.ts
|
|
61990
|
-
|
|
61991
|
-
import {
|
|
63277
|
+
init_tw();
|
|
63278
|
+
import { join as join28, dirname as dirname8 } from "node:path";
|
|
63279
|
+
import { existsSync as existsSync23, readFileSync as readFileSync16, readdirSync as readdirSync10, statSync as statSync10 } from "node:fs";
|
|
61992
63280
|
async function checkCommand() {
|
|
61993
63281
|
const args = process.argv.slice(3);
|
|
61994
63282
|
const rootDir = process.cwd();
|
|
@@ -62010,7 +63298,7 @@ async function checkCommand() {
|
|
|
62010
63298
|
for (const filePath of twFiles) {
|
|
62011
63299
|
const relativePath = filePath.replace(rootDir + "/", "");
|
|
62012
63300
|
try {
|
|
62013
|
-
const source =
|
|
63301
|
+
const source = readFileSync16(filePath, "utf-8");
|
|
62014
63302
|
const result = await compile2(source, {
|
|
62015
63303
|
filePath,
|
|
62016
63304
|
optimize: false,
|
|
@@ -62021,6 +63309,42 @@ async function checkCommand() {
|
|
|
62021
63309
|
const errors = diagnostics.filter((d) => d.severity === "error");
|
|
62022
63310
|
const warnings = diagnostics.filter((d) => d.severity === "warning");
|
|
62023
63311
|
const info = diagnostics.filter((d) => d.severity === "info");
|
|
63312
|
+
const checkSource = readFileSync16(filePath, "utf-8");
|
|
63313
|
+
const usedTags = /* @__PURE__ */ new Set();
|
|
63314
|
+
const collectUsed = (n) => {
|
|
63315
|
+
if (!n) return;
|
|
63316
|
+
if (n.type === "Element" || n.type === "Component") {
|
|
63317
|
+
const t2 = n.tag ?? n.name;
|
|
63318
|
+
if (typeof t2 === "string" && t2[0] === t2[0].toUpperCase()) usedTags.add(t2);
|
|
63319
|
+
}
|
|
63320
|
+
for (const c of n.children ?? []) collectUsed(c);
|
|
63321
|
+
if (n.type === "If" || n.type === "For" || n.type === "While") for (const c of n.body ?? []) collectUsed(c);
|
|
63322
|
+
};
|
|
63323
|
+
if (result.ast) {
|
|
63324
|
+
for (const n of result.ast.body ?? []) collectUsed(n);
|
|
63325
|
+
}
|
|
63326
|
+
const SAFE_TAGS = /* @__PURE__ */ new Set(["Link", "Suspense", "RouterLink", "Image", "optImage"]);
|
|
63327
|
+
const importedNames = /* @__PURE__ */ new Set();
|
|
63328
|
+
for (const im of checkSource.matchAll(/import\s+([A-Za-z_]\w*)\s+from/g)) importedNames.add(im[1]);
|
|
63329
|
+
for (const im of checkSource.matchAll(/import\s*\{([^}]+)\}/g)) {
|
|
63330
|
+
for (const part of im[1].split(",")) {
|
|
63331
|
+
const nm = part.split(" as ").pop().trim();
|
|
63332
|
+
if (nm) importedNames.add(nm);
|
|
63333
|
+
}
|
|
63334
|
+
}
|
|
63335
|
+
const unknownComponents = [];
|
|
63336
|
+
for (const tag of usedTags) {
|
|
63337
|
+
if (SAFE_TAGS.has(tag) || importedNames.has(tag)) continue;
|
|
63338
|
+
if (findComponentUp(dirname8(filePath), tag)) continue;
|
|
63339
|
+
unknownComponents.push(tag);
|
|
63340
|
+
}
|
|
63341
|
+
if (unknownComponents.length > 0) {
|
|
63342
|
+
console.debug(` ? ${relativePath} -- ${unknownComponents.length} unknown component(s)`);
|
|
63343
|
+
for (const t2 of unknownComponents) {
|
|
63344
|
+
console.debug(` ? TW098 unknown component <${t2}> -- no import resolves it and no components/${t2}.tw exists`);
|
|
63345
|
+
}
|
|
63346
|
+
totalWarnings += unknownComponents.length;
|
|
63347
|
+
}
|
|
62024
63348
|
totalErrors += errors.length;
|
|
62025
63349
|
totalWarnings += warnings.length;
|
|
62026
63350
|
totalInfo += info.length;
|
|
@@ -62043,26 +63367,55 @@ async function checkCommand() {
|
|
|
62043
63367
|
console.debug(" Compiler not found, running basic checks...\n");
|
|
62044
63368
|
for (const filePath of twFiles) {
|
|
62045
63369
|
const relativePath = filePath.replace(rootDir + "/", "");
|
|
62046
|
-
const source =
|
|
62047
|
-
const
|
|
62048
|
-
const
|
|
63370
|
+
const source = readFileSync16(filePath, "utf-8");
|
|
63371
|
+
const maskedSource = maskSourceStringsAndComments(source);
|
|
63372
|
+
const openBraces = (maskedSource.match(/\{/g) || []).length;
|
|
63373
|
+
const closeBraces = (maskedSource.match(/\}/g) || []).length;
|
|
62049
63374
|
if (openBraces !== closeBraces) {
|
|
62050
63375
|
totalErrors++;
|
|
62051
|
-
console.
|
|
63376
|
+
console.log(` X ${relativePath}: unbalanced braces (${openBraces} open, ${closeBraces} close)`);
|
|
62052
63377
|
} else {
|
|
62053
|
-
console.
|
|
63378
|
+
console.log(` OK ${relativePath} -- syntax OK`);
|
|
62054
63379
|
}
|
|
62055
63380
|
}
|
|
62056
63381
|
}
|
|
62057
|
-
console.
|
|
63382
|
+
console.log(`
|
|
62058
63383
|
Summary: ${totalErrors} errors, ${totalWarnings} warnings, ${totalInfo} info`);
|
|
63384
|
+
const argv = process.argv.slice(2);
|
|
63385
|
+
let maxWarnings = null;
|
|
63386
|
+
if (argv.includes("--strict")) maxWarnings = 0;
|
|
63387
|
+
const mwIdx = argv.indexOf("--max-warnings");
|
|
63388
|
+
if (mwIdx !== -1 && argv[mwIdx + 1] !== void 0 && /^\d+$/.test(argv[mwIdx + 1])) {
|
|
63389
|
+
maxWarnings = Number(argv[mwIdx + 1]);
|
|
63390
|
+
} else {
|
|
63391
|
+
const eq = argv.find((a) => a.startsWith("--max-warnings="));
|
|
63392
|
+
if (eq && /^\d+$/.test(eq.split("=")[1])) maxWarnings = Number(eq.split("=")[1]);
|
|
63393
|
+
}
|
|
62059
63394
|
if (totalErrors > 0) {
|
|
62060
|
-
console.
|
|
63395
|
+
console.log(" Status: FAILED\n");
|
|
63396
|
+
process.exit(1);
|
|
63397
|
+
} else if (maxWarnings !== null && totalWarnings > maxWarnings) {
|
|
63398
|
+
console.log(` Status: FAILED -- ${totalWarnings} warnings exceed --max-warnings=${maxWarnings}
|
|
63399
|
+
`);
|
|
62061
63400
|
process.exit(1);
|
|
62062
63401
|
} else {
|
|
62063
|
-
console.
|
|
63402
|
+
console.log(" Status: PASSED\n");
|
|
62064
63403
|
}
|
|
62065
63404
|
}
|
|
63405
|
+
function findComponentUp(dir, name) {
|
|
63406
|
+
let cur = dir;
|
|
63407
|
+
for (let i = 0; i < 12 && cur && cur !== "/"; i++) {
|
|
63408
|
+
try {
|
|
63409
|
+
const candidate = join28(cur, "components", name + ".tw");
|
|
63410
|
+
if (existsSync23(candidate)) return true;
|
|
63411
|
+
} catch {
|
|
63412
|
+
}
|
|
63413
|
+
const parent = dirname8(cur);
|
|
63414
|
+
if (parent === cur) break;
|
|
63415
|
+
cur = parent;
|
|
63416
|
+
}
|
|
63417
|
+
return false;
|
|
63418
|
+
}
|
|
62066
63419
|
function collectFiles2(dir, ext, results) {
|
|
62067
63420
|
const entries = readdirSync10(dir);
|
|
62068
63421
|
for (const entry of entries) {
|
|
@@ -62077,11 +63430,12 @@ function collectFiles2(dir, ext, results) {
|
|
|
62077
63430
|
}
|
|
62078
63431
|
|
|
62079
63432
|
// tw/commands/plugin.ts
|
|
63433
|
+
init_tw();
|
|
62080
63434
|
import { join as join29 } from "node:path";
|
|
62081
|
-
import { existsSync as existsSync24, readFileSync as
|
|
63435
|
+
import { existsSync as existsSync24, readFileSync as readFileSync17, writeFileSync as writeFileSync9, mkdirSync as mkdirSync9, readdirSync as readdirSync11 } from "node:fs";
|
|
62082
63436
|
function readConfigPlugins(cfgPath) {
|
|
62083
63437
|
if (!existsSync24(cfgPath)) return [];
|
|
62084
|
-
const src =
|
|
63438
|
+
const src = stripCommentsStringAware(readFileSync17(cfgPath, "utf-8"));
|
|
62085
63439
|
const m = /plugins\s*:\s*\[([^\]]*)\]/s.exec(src);
|
|
62086
63440
|
if (!m) return [];
|
|
62087
63441
|
const out = [];
|
|
@@ -62100,7 +63454,7 @@ function addConfigPlugin(cfgPath, name) {
|
|
|
62100
63454
|
`);
|
|
62101
63455
|
return true;
|
|
62102
63456
|
}
|
|
62103
|
-
const src =
|
|
63457
|
+
const src = stripCommentsStringAware(readFileSync17(cfgPath, "utf-8"));
|
|
62104
63458
|
if (new RegExp(`["'\`]${name}["'\`]`).test(src)) return false;
|
|
62105
63459
|
const m = /plugins\s*:\s*\[([^\]]*)\]/s.exec(src);
|
|
62106
63460
|
if (m) {
|
|
@@ -62117,7 +63471,7 @@ function addConfigPlugin(cfgPath, name) {
|
|
|
62117
63471
|
}
|
|
62118
63472
|
function removeConfigPlugin(cfgPath, name) {
|
|
62119
63473
|
if (!existsSync24(cfgPath)) return false;
|
|
62120
|
-
const src =
|
|
63474
|
+
const src = stripCommentsStringAware(readFileSync17(cfgPath, "utf-8"));
|
|
62121
63475
|
const m = /plugins\s*:\s*\[([^\]]*)\]/s.exec(src);
|
|
62122
63476
|
if (!m) return false;
|
|
62123
63477
|
const kept = m[1].split(",").map((e) => e.trim()).filter((e) => e && e !== `"${name}"` && e !== `'${name}'` && e !== "`" + name + "`");
|
|
@@ -62321,13 +63675,13 @@ async function lspCommand() {
|
|
|
62321
63675
|
}
|
|
62322
63676
|
|
|
62323
63677
|
// tw/help.ts
|
|
62324
|
-
import { readFileSync as
|
|
63678
|
+
import { readFileSync as readFileSync18 } from "node:fs";
|
|
62325
63679
|
import { join as join31 } from "node:path";
|
|
62326
63680
|
function cliVersion() {
|
|
62327
63681
|
try {
|
|
62328
63682
|
const bd = globalThis.__TW_BUNDLE_DIR;
|
|
62329
63683
|
const pj = bd ? join31(bd, "..", "package.json") : new URL("../../package.json", import.meta.url).pathname;
|
|
62330
|
-
return JSON.parse(
|
|
63684
|
+
return JSON.parse(readFileSync18(pj, "utf-8")).version ?? "0.0.0";
|
|
62331
63685
|
} catch {
|
|
62332
63686
|
return "0.0.0";
|
|
62333
63687
|
}
|
|
@@ -62400,13 +63754,13 @@ var helpText = `
|
|
|
62400
63754
|
|
|
62401
63755
|
// tw/commands/index.ts
|
|
62402
63756
|
await init_build();
|
|
62403
|
-
import { readFileSync as
|
|
63757
|
+
import { readFileSync as readFileSync19 } from "node:fs";
|
|
62404
63758
|
import { join as join32 } from "node:path";
|
|
62405
63759
|
var VERSION = (() => {
|
|
62406
63760
|
try {
|
|
62407
63761
|
const bd = globalThis.__TW_BUNDLE_DIR;
|
|
62408
63762
|
const pj = bd ? join32(bd, "..", "package.json") : new URL("../../package.json", import.meta.url).pathname;
|
|
62409
|
-
return JSON.parse(
|
|
63763
|
+
return JSON.parse(readFileSync19(pj, "utf-8")).version ?? "0.0.0";
|
|
62410
63764
|
} catch {
|
|
62411
63765
|
return "0.0.0";
|
|
62412
63766
|
}
|