@lolyjs/core 0.2.0-alpha.15 → 0.2.0-alpha.16
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/README.md +1016 -842
- package/dist/{bootstrap-DgvWWDim.d.mts → bootstrap-BfGTMUkj.d.mts} +12 -0
- package/dist/{bootstrap-DgvWWDim.d.ts → bootstrap-BfGTMUkj.d.ts} +12 -0
- package/dist/cli.cjs +4336 -3067
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +4315 -3046
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +2066 -549
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +9 -79
- package/dist/index.d.ts +9 -79
- package/dist/index.js +2066 -549
- package/dist/index.js.map +1 -1
- package/dist/index.types-BPX6IVAC.d.mts +198 -0
- package/dist/index.types-BPX6IVAC.d.ts +198 -0
- package/dist/react/cache.cjs.map +1 -1
- package/dist/react/cache.d.mts +22 -2
- package/dist/react/cache.d.ts +22 -2
- package/dist/react/cache.js.map +1 -1
- package/dist/react/components.cjs.map +1 -1
- package/dist/react/components.js.map +1 -1
- package/dist/react/hooks.cjs.map +1 -1
- package/dist/react/hooks.js.map +1 -1
- package/dist/react/sockets.cjs.map +1 -1
- package/dist/react/sockets.js.map +1 -1
- package/dist/runtime.cjs +359 -95
- package/dist/runtime.cjs.map +1 -1
- package/dist/runtime.d.mts +2 -2
- package/dist/runtime.d.ts +2 -2
- package/dist/runtime.js +359 -95
- package/dist/runtime.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -74,7 +74,7 @@ var init_globals = __esm({
|
|
|
74
74
|
|
|
75
75
|
// src/server.ts
|
|
76
76
|
import fs16 from "fs";
|
|
77
|
-
import
|
|
77
|
+
import path23 from "path";
|
|
78
78
|
|
|
79
79
|
// modules/server/utils/server-dir.ts
|
|
80
80
|
init_globals();
|
|
@@ -251,9 +251,26 @@ function loadServerHookForDir(currentDir) {
|
|
|
251
251
|
generateStaticParams: null
|
|
252
252
|
};
|
|
253
253
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
254
|
+
let middlewares = [];
|
|
255
|
+
const rawMiddlewares = mod?.[NAMING.BEFORE_MIDDLEWARES];
|
|
256
|
+
if (rawMiddlewares !== void 0) {
|
|
257
|
+
if (!Array.isArray(rawMiddlewares)) {
|
|
258
|
+
console.warn(
|
|
259
|
+
`[framework][server-hook] ${NAMING.BEFORE_MIDDLEWARES} must be an array in ${file}, ignoring invalid value`
|
|
260
|
+
);
|
|
261
|
+
} else {
|
|
262
|
+
for (let i = 0; i < rawMiddlewares.length; i++) {
|
|
263
|
+
const mw = rawMiddlewares[i];
|
|
264
|
+
if (typeof mw !== "function") {
|
|
265
|
+
console.warn(
|
|
266
|
+
`[framework][server-hook] Middleware at index ${i} in ${NAMING.BEFORE_MIDDLEWARES} is not a function in ${file}, skipping`
|
|
267
|
+
);
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
middlewares.push(mw);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
257
274
|
const serverHook = typeof mod?.[NAMING.GET_SERVER_DATA_FN] === "function" ? mod[NAMING.GET_SERVER_DATA_FN] : null;
|
|
258
275
|
const dynamic = mod?.[NAMING.RENDER_TYPE_CONST] === "force-static" || mod?.[NAMING.RENDER_TYPE_CONST] === "force-dynamic" ? mod.dynamic : "auto";
|
|
259
276
|
const generateStaticParams = typeof mod?.[NAMING.GENERATE_SSG_PARAMS] === "function" ? mod[NAMING.GENERATE_SSG_PARAMS] : null;
|
|
@@ -293,6 +310,50 @@ function loadLayoutServerHook(layoutFile) {
|
|
|
293
310
|
}
|
|
294
311
|
|
|
295
312
|
// modules/router/loader-pages.ts
|
|
313
|
+
function validateRoutes(routes, appDir) {
|
|
314
|
+
const routePatterns = /* @__PURE__ */ new Map();
|
|
315
|
+
const errors = [];
|
|
316
|
+
const warnings = [];
|
|
317
|
+
for (const route of routes) {
|
|
318
|
+
const existing = routePatterns.get(route.pattern) || [];
|
|
319
|
+
existing.push(route);
|
|
320
|
+
routePatterns.set(route.pattern, existing);
|
|
321
|
+
}
|
|
322
|
+
for (const [pattern, duplicateRoutes] of routePatterns.entries()) {
|
|
323
|
+
if (duplicateRoutes.length > 1) {
|
|
324
|
+
const files = duplicateRoutes.map(
|
|
325
|
+
(r) => r.pageFile ? path4.relative(appDir, r.pageFile) : "unknown"
|
|
326
|
+
).join(", ");
|
|
327
|
+
errors.push(
|
|
328
|
+
`Duplicate route pattern "${pattern}" found in multiple files:
|
|
329
|
+
${files}
|
|
330
|
+
\u{1F4A1} Suggestion: Ensure each route has a unique path pattern`
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
for (const route of routes) {
|
|
335
|
+
if (!route.pageFile || !fs4.existsSync(route.pageFile)) {
|
|
336
|
+
warnings.push(
|
|
337
|
+
`Route pattern "${route.pattern}" references a missing page file`
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
if (errors.length > 0) {
|
|
342
|
+
const errorMessage = [
|
|
343
|
+
"\u274C Route validation failed:",
|
|
344
|
+
"",
|
|
345
|
+
...errors,
|
|
346
|
+
"",
|
|
347
|
+
"\u{1F4A1} Please fix the errors above before starting the server."
|
|
348
|
+
].join("\n");
|
|
349
|
+
throw new Error(errorMessage);
|
|
350
|
+
}
|
|
351
|
+
if (warnings.length > 0 && process.env.NODE_ENV === "development") {
|
|
352
|
+
console.warn("\n\u26A0\uFE0F Route warnings:");
|
|
353
|
+
warnings.forEach((warning) => console.warn(` \u2022 ${warning}`));
|
|
354
|
+
console.warn("");
|
|
355
|
+
}
|
|
356
|
+
}
|
|
296
357
|
function loadRoutes(appDir) {
|
|
297
358
|
if (!fs4.existsSync(appDir)) {
|
|
298
359
|
return [];
|
|
@@ -347,6 +408,7 @@ function loadRoutes(appDir) {
|
|
|
347
408
|
}
|
|
348
409
|
}
|
|
349
410
|
walk(appDir);
|
|
411
|
+
validateRoutes(routes, appDir);
|
|
350
412
|
return routes;
|
|
351
413
|
}
|
|
352
414
|
|
|
@@ -710,9 +772,12 @@ function writeClientBoostrapManifest(projectRoot) {
|
|
|
710
772
|
lines.push("");
|
|
711
773
|
lines.push(`import { bootstrapClient } from "@lolyjs/core/runtime"`);
|
|
712
774
|
lines.push("");
|
|
713
|
-
lines.push(
|
|
714
|
-
|
|
715
|
-
);
|
|
775
|
+
lines.push(`try {`);
|
|
776
|
+
lines.push(` bootstrapClient(routes as ClientRouteLoaded[], notFoundRoute, errorRoute);`);
|
|
777
|
+
lines.push(`} catch (error) {`);
|
|
778
|
+
lines.push(` console.error("[bootstrap] Fatal error during bootstrap:", error);`);
|
|
779
|
+
lines.push(` throw error;`);
|
|
780
|
+
lines.push(`}`);
|
|
716
781
|
fs7.writeFileSync(manifestPath, lines.join("\n"), "utf-8");
|
|
717
782
|
}
|
|
718
783
|
function writeRoutesManifest({
|
|
@@ -1050,24 +1115,210 @@ function loadWssRoutes(appDir) {
|
|
|
1050
1115
|
}
|
|
1051
1116
|
|
|
1052
1117
|
// modules/router/route-loader.ts
|
|
1118
|
+
var SKIP_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
1119
|
+
"node_modules",
|
|
1120
|
+
".git",
|
|
1121
|
+
".loly",
|
|
1122
|
+
"dist",
|
|
1123
|
+
"build",
|
|
1124
|
+
".next",
|
|
1125
|
+
".cache",
|
|
1126
|
+
"coverage",
|
|
1127
|
+
".vscode",
|
|
1128
|
+
".idea",
|
|
1129
|
+
".turbo",
|
|
1130
|
+
".swc"
|
|
1131
|
+
]);
|
|
1132
|
+
function getRelevantFiles(appDir, projectRoot) {
|
|
1133
|
+
const files = [];
|
|
1134
|
+
const projectRootNormalized = path10.resolve(projectRoot);
|
|
1135
|
+
function walk(currentDir) {
|
|
1136
|
+
if (!fs10.existsSync(currentDir)) {
|
|
1137
|
+
return;
|
|
1138
|
+
}
|
|
1139
|
+
const entries = fs10.readdirSync(currentDir, { withFileTypes: true });
|
|
1140
|
+
for (const entry of entries) {
|
|
1141
|
+
const fullPath = path10.join(currentDir, entry.name);
|
|
1142
|
+
if (entry.isDirectory()) {
|
|
1143
|
+
if (SKIP_DIRECTORIES.has(entry.name)) {
|
|
1144
|
+
continue;
|
|
1145
|
+
}
|
|
1146
|
+
if (entry.name.startsWith(".")) {
|
|
1147
|
+
continue;
|
|
1148
|
+
}
|
|
1149
|
+
walk(fullPath);
|
|
1150
|
+
} else {
|
|
1151
|
+
const ext = path10.extname(entry.name);
|
|
1152
|
+
if ([".ts", ".tsx", ".js", ".jsx"].includes(ext)) {
|
|
1153
|
+
files.push(fullPath);
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
walk(projectRootNormalized);
|
|
1159
|
+
return files;
|
|
1160
|
+
}
|
|
1161
|
+
function hasFilesChanged(appDir, projectRoot, cachedStats) {
|
|
1162
|
+
const currentFiles = getRelevantFiles(appDir, projectRoot);
|
|
1163
|
+
const currentFilesSet = new Set(currentFiles);
|
|
1164
|
+
for (const [filePath] of cachedStats.entries()) {
|
|
1165
|
+
if (!currentFilesSet.has(filePath)) {
|
|
1166
|
+
return true;
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
for (const filePath of currentFiles) {
|
|
1170
|
+
if (!fs10.existsSync(filePath)) {
|
|
1171
|
+
continue;
|
|
1172
|
+
}
|
|
1173
|
+
const stats = fs10.statSync(filePath);
|
|
1174
|
+
const cachedStat = cachedStats.get(filePath);
|
|
1175
|
+
if (!cachedStat) {
|
|
1176
|
+
return true;
|
|
1177
|
+
}
|
|
1178
|
+
if (stats.mtimeMs !== cachedStat.mtime || stats.size !== cachedStat.size) {
|
|
1179
|
+
return true;
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
return false;
|
|
1183
|
+
}
|
|
1184
|
+
function buildFileStats(files) {
|
|
1185
|
+
const statsMap = /* @__PURE__ */ new Map();
|
|
1186
|
+
for (const filePath of files) {
|
|
1187
|
+
if (fs10.existsSync(filePath)) {
|
|
1188
|
+
const stats = fs10.statSync(filePath);
|
|
1189
|
+
statsMap.set(filePath, {
|
|
1190
|
+
mtime: stats.mtimeMs,
|
|
1191
|
+
size: stats.size
|
|
1192
|
+
});
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
return statsMap;
|
|
1196
|
+
}
|
|
1053
1197
|
var FilesystemRouteLoader = class {
|
|
1054
|
-
|
|
1198
|
+
// Maximum cache age in ms (1 second fallback)
|
|
1199
|
+
constructor(appDir, projectRoot = appDir) {
|
|
1055
1200
|
this.appDir = appDir;
|
|
1201
|
+
this.projectRoot = projectRoot;
|
|
1202
|
+
this.cache = null;
|
|
1203
|
+
this.cacheMaxAge = 1e3;
|
|
1204
|
+
if (this.projectRoot === this.appDir) {
|
|
1205
|
+
let current = path10.resolve(this.appDir);
|
|
1206
|
+
while (current !== path10.dirname(current)) {
|
|
1207
|
+
if (fs10.existsSync(path10.join(current, "package.json"))) {
|
|
1208
|
+
this.projectRoot = current;
|
|
1209
|
+
break;
|
|
1210
|
+
}
|
|
1211
|
+
current = path10.dirname(current);
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
/**
|
|
1216
|
+
* Invalidates the cache, forcing a reload on next access.
|
|
1217
|
+
*/
|
|
1218
|
+
invalidateCache() {
|
|
1219
|
+
this.cache = null;
|
|
1220
|
+
}
|
|
1221
|
+
/**
|
|
1222
|
+
* Checks if cache is still valid, invalidates if files changed.
|
|
1223
|
+
*/
|
|
1224
|
+
ensureCacheValid() {
|
|
1225
|
+
if (!this.cache) {
|
|
1226
|
+
return;
|
|
1227
|
+
}
|
|
1228
|
+
const now = Date.now();
|
|
1229
|
+
if (now - this.cache.timestamp > this.cacheMaxAge) {
|
|
1230
|
+
if (hasFilesChanged(this.appDir, this.projectRoot, this.cache.fileStats)) {
|
|
1231
|
+
this.cache = null;
|
|
1232
|
+
} else {
|
|
1233
|
+
this.cache.timestamp = now;
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1056
1236
|
}
|
|
1057
1237
|
loadRoutes() {
|
|
1058
|
-
|
|
1238
|
+
this.ensureCacheValid();
|
|
1239
|
+
if (!this.cache || hasFilesChanged(this.appDir, this.projectRoot, this.cache.fileStats)) {
|
|
1240
|
+
const routes = loadRoutes(this.appDir);
|
|
1241
|
+
const files = getRelevantFiles(this.appDir, this.projectRoot);
|
|
1242
|
+
const fileStats = buildFileStats(files);
|
|
1243
|
+
this.cache = {
|
|
1244
|
+
routes,
|
|
1245
|
+
apiRoutes: this.cache?.apiRoutes || [],
|
|
1246
|
+
wssRoutes: this.cache?.wssRoutes || [],
|
|
1247
|
+
notFoundRoute: this.cache?.notFoundRoute ?? null,
|
|
1248
|
+
errorRoute: this.cache?.errorRoute ?? null,
|
|
1249
|
+
fileStats,
|
|
1250
|
+
timestamp: Date.now()
|
|
1251
|
+
};
|
|
1252
|
+
}
|
|
1253
|
+
return this.cache.routes;
|
|
1059
1254
|
}
|
|
1060
1255
|
loadApiRoutes() {
|
|
1061
|
-
|
|
1256
|
+
this.ensureCacheValid();
|
|
1257
|
+
if (!this.cache) {
|
|
1258
|
+
this.loadRoutes();
|
|
1259
|
+
}
|
|
1260
|
+
if (!this.cache) {
|
|
1261
|
+
throw new Error("Failed to initialize route cache");
|
|
1262
|
+
}
|
|
1263
|
+
if (hasFilesChanged(this.appDir, this.projectRoot, this.cache.fileStats) || this.cache.apiRoutes.length === 0) {
|
|
1264
|
+
const files = getRelevantFiles(this.appDir, this.projectRoot);
|
|
1265
|
+
const fileStats = buildFileStats(files);
|
|
1266
|
+
this.cache.apiRoutes = loadApiRoutes(this.appDir);
|
|
1267
|
+
this.cache.fileStats = fileStats;
|
|
1268
|
+
this.cache.timestamp = Date.now();
|
|
1269
|
+
}
|
|
1270
|
+
return this.cache.apiRoutes;
|
|
1062
1271
|
}
|
|
1063
1272
|
loadWssRoutes() {
|
|
1064
|
-
|
|
1273
|
+
this.ensureCacheValid();
|
|
1274
|
+
if (!this.cache) {
|
|
1275
|
+
this.loadRoutes();
|
|
1276
|
+
}
|
|
1277
|
+
if (!this.cache) {
|
|
1278
|
+
throw new Error("Failed to initialize route cache");
|
|
1279
|
+
}
|
|
1280
|
+
if (hasFilesChanged(this.appDir, this.projectRoot, this.cache.fileStats) || this.cache.wssRoutes.length === 0) {
|
|
1281
|
+
const files = getRelevantFiles(this.appDir, this.projectRoot);
|
|
1282
|
+
const fileStats = buildFileStats(files);
|
|
1283
|
+
this.cache.wssRoutes = loadWssRoutes(this.appDir);
|
|
1284
|
+
this.cache.fileStats = fileStats;
|
|
1285
|
+
this.cache.timestamp = Date.now();
|
|
1286
|
+
}
|
|
1287
|
+
return this.cache.wssRoutes;
|
|
1065
1288
|
}
|
|
1066
1289
|
loadNotFoundRoute() {
|
|
1067
|
-
|
|
1290
|
+
this.ensureCacheValid();
|
|
1291
|
+
if (!this.cache) {
|
|
1292
|
+
this.loadRoutes();
|
|
1293
|
+
}
|
|
1294
|
+
if (!this.cache) {
|
|
1295
|
+
throw new Error("Failed to initialize route cache");
|
|
1296
|
+
}
|
|
1297
|
+
if (hasFilesChanged(this.appDir, this.projectRoot, this.cache.fileStats) || this.cache.notFoundRoute === void 0) {
|
|
1298
|
+
const files = getRelevantFiles(this.appDir, this.projectRoot);
|
|
1299
|
+
const fileStats = buildFileStats(files);
|
|
1300
|
+
this.cache.notFoundRoute = loadNotFoundRouteFromFilesystem(this.appDir);
|
|
1301
|
+
this.cache.fileStats = fileStats;
|
|
1302
|
+
this.cache.timestamp = Date.now();
|
|
1303
|
+
}
|
|
1304
|
+
return this.cache.notFoundRoute;
|
|
1068
1305
|
}
|
|
1069
1306
|
loadErrorRoute() {
|
|
1070
|
-
|
|
1307
|
+
this.ensureCacheValid();
|
|
1308
|
+
if (!this.cache) {
|
|
1309
|
+
this.loadRoutes();
|
|
1310
|
+
}
|
|
1311
|
+
if (!this.cache) {
|
|
1312
|
+
throw new Error("Failed to initialize route cache");
|
|
1313
|
+
}
|
|
1314
|
+
if (hasFilesChanged(this.appDir, this.projectRoot, this.cache.fileStats) || this.cache.errorRoute === void 0) {
|
|
1315
|
+
const files = getRelevantFiles(this.appDir, this.projectRoot);
|
|
1316
|
+
const fileStats = buildFileStats(files);
|
|
1317
|
+
this.cache.errorRoute = loadErrorRouteFromFilesystem(this.appDir);
|
|
1318
|
+
this.cache.fileStats = fileStats;
|
|
1319
|
+
this.cache.timestamp = Date.now();
|
|
1320
|
+
}
|
|
1321
|
+
return this.cache.errorRoute;
|
|
1071
1322
|
}
|
|
1072
1323
|
loadRouteChunks() {
|
|
1073
1324
|
return {};
|
|
@@ -1076,27 +1327,67 @@ var FilesystemRouteLoader = class {
|
|
|
1076
1327
|
var ManifestRouteLoader = class {
|
|
1077
1328
|
constructor(projectRoot) {
|
|
1078
1329
|
this.projectRoot = projectRoot;
|
|
1330
|
+
this.cache = {};
|
|
1331
|
+
}
|
|
1332
|
+
/**
|
|
1333
|
+
* Gets the manifest, using cache if available.
|
|
1334
|
+
* The manifest is read once and cached for the lifetime of the loader.
|
|
1335
|
+
*/
|
|
1336
|
+
getManifest() {
|
|
1337
|
+
if (this.cache.manifest !== void 0) {
|
|
1338
|
+
return this.cache.manifest;
|
|
1339
|
+
}
|
|
1340
|
+
const manifest = readManifest(this.projectRoot);
|
|
1341
|
+
this.cache.manifest = manifest;
|
|
1342
|
+
return manifest;
|
|
1079
1343
|
}
|
|
1080
1344
|
loadRoutes() {
|
|
1345
|
+
if (this.cache.routes) {
|
|
1346
|
+
return this.cache.routes;
|
|
1347
|
+
}
|
|
1081
1348
|
const { routes } = loadRoutesFromManifest(this.projectRoot);
|
|
1349
|
+
this.cache.routes = routes;
|
|
1082
1350
|
return routes;
|
|
1083
1351
|
}
|
|
1084
1352
|
loadApiRoutes() {
|
|
1353
|
+
if (this.cache.apiRoutes) {
|
|
1354
|
+
return this.cache.apiRoutes;
|
|
1355
|
+
}
|
|
1085
1356
|
const { apiRoutes } = loadRoutesFromManifest(this.projectRoot);
|
|
1357
|
+
this.cache.apiRoutes = apiRoutes;
|
|
1086
1358
|
return apiRoutes;
|
|
1087
1359
|
}
|
|
1088
1360
|
loadWssRoutes() {
|
|
1361
|
+
if (this.cache.wssRoutes) {
|
|
1362
|
+
return this.cache.wssRoutes;
|
|
1363
|
+
}
|
|
1089
1364
|
const { wssRoutes } = loadRoutesFromManifest(this.projectRoot);
|
|
1365
|
+
this.cache.wssRoutes = wssRoutes;
|
|
1090
1366
|
return wssRoutes;
|
|
1091
1367
|
}
|
|
1092
1368
|
loadNotFoundRoute() {
|
|
1093
|
-
|
|
1369
|
+
if (this.cache.notFoundRoute !== void 0) {
|
|
1370
|
+
return this.cache.notFoundRoute;
|
|
1371
|
+
}
|
|
1372
|
+
const route = loadNotFoundFromManifest(this.projectRoot);
|
|
1373
|
+
this.cache.notFoundRoute = route;
|
|
1374
|
+
return route;
|
|
1094
1375
|
}
|
|
1095
1376
|
loadErrorRoute() {
|
|
1096
|
-
|
|
1377
|
+
if (this.cache.errorRoute !== void 0) {
|
|
1378
|
+
return this.cache.errorRoute;
|
|
1379
|
+
}
|
|
1380
|
+
const route = loadErrorFromManifest(this.projectRoot);
|
|
1381
|
+
this.cache.errorRoute = route;
|
|
1382
|
+
return route;
|
|
1097
1383
|
}
|
|
1098
1384
|
loadRouteChunks() {
|
|
1099
|
-
|
|
1385
|
+
if (this.cache.routeChunks) {
|
|
1386
|
+
return this.cache.routeChunks;
|
|
1387
|
+
}
|
|
1388
|
+
const chunks = loadChunksFromManifest(this.projectRoot);
|
|
1389
|
+
this.cache.routeChunks = chunks;
|
|
1390
|
+
return chunks;
|
|
1100
1391
|
}
|
|
1101
1392
|
};
|
|
1102
1393
|
function loadNotFoundRouteFromFilesystem(appDir) {
|
|
@@ -1229,7 +1520,8 @@ function loadAliasesFromTsconfig(projectRoot) {
|
|
|
1229
1520
|
try {
|
|
1230
1521
|
tsconfig = JSON.parse(fs11.readFileSync(tsconfigPath, "utf-8"));
|
|
1231
1522
|
} catch (err) {
|
|
1232
|
-
console.warn("[framework] Could not read tsconfig.json:", err);
|
|
1523
|
+
console.warn("\u26A0\uFE0F [framework] Could not read tsconfig.json:", err instanceof Error ? err.message : String(err));
|
|
1524
|
+
console.warn("\u{1F4A1} Using default path aliases. For custom aliases, ensure tsconfig.json is valid.");
|
|
1233
1525
|
aliases["@app"] = path11.resolve(projectRoot, "app");
|
|
1234
1526
|
return aliases;
|
|
1235
1527
|
}
|
|
@@ -1283,7 +1575,7 @@ function copyStaticAssets(projectRoot, outDir) {
|
|
|
1283
1575
|
}
|
|
1284
1576
|
}
|
|
1285
1577
|
}
|
|
1286
|
-
function generateAssetManifest(outDir) {
|
|
1578
|
+
function generateAssetManifest(outDir, stats) {
|
|
1287
1579
|
const manifest = {
|
|
1288
1580
|
client: {
|
|
1289
1581
|
js: "client.js",
|
|
@@ -1295,27 +1587,128 @@ function generateAssetManifest(outDir) {
|
|
|
1295
1587
|
return manifest;
|
|
1296
1588
|
}
|
|
1297
1589
|
const files = fs11.readdirSync(outDir);
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1590
|
+
if (stats) {
|
|
1591
|
+
try {
|
|
1592
|
+
const statsJson = stats.toJson({
|
|
1593
|
+
all: false,
|
|
1594
|
+
entrypoints: true,
|
|
1595
|
+
assets: true,
|
|
1596
|
+
chunks: true,
|
|
1597
|
+
chunkRelations: true
|
|
1598
|
+
// Include chunk dependencies
|
|
1599
|
+
});
|
|
1600
|
+
const clientEntrypoint = statsJson.entrypoints?.client;
|
|
1601
|
+
if (clientEntrypoint?.assets) {
|
|
1602
|
+
const clientJsFiles = clientEntrypoint.assets.map((asset) => typeof asset === "string" ? asset : asset.name).filter((name) => name.endsWith(".js"));
|
|
1603
|
+
if (statsJson.chunks && clientEntrypoint.chunks) {
|
|
1604
|
+
const entrypointChunkIds = new Set(
|
|
1605
|
+
Array.isArray(clientEntrypoint.chunks) ? clientEntrypoint.chunks : [clientEntrypoint.chunks]
|
|
1606
|
+
);
|
|
1607
|
+
const dependencyChunks = [];
|
|
1608
|
+
for (const chunk of statsJson.chunks) {
|
|
1609
|
+
if (chunk.id && entrypointChunkIds.has(chunk.id)) {
|
|
1610
|
+
if (chunk.files) {
|
|
1611
|
+
const jsFiles = chunk.files.filter((f) => f.endsWith(".js")).filter((f) => !clientJsFiles.includes(f));
|
|
1612
|
+
dependencyChunks.push(...jsFiles);
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
const visitedChunkIds = new Set(entrypointChunkIds);
|
|
1617
|
+
const chunksToCheck = Array.from(entrypointChunkIds);
|
|
1618
|
+
while (chunksToCheck.length > 0) {
|
|
1619
|
+
const chunkId = chunksToCheck.shift();
|
|
1620
|
+
if (!chunkId) continue;
|
|
1621
|
+
const chunk = statsJson.chunks?.find((c) => c.id === chunkId);
|
|
1622
|
+
if (chunk?.children) {
|
|
1623
|
+
const children = Array.isArray(chunk.children) ? chunk.children : [chunk.children];
|
|
1624
|
+
for (const childId of children) {
|
|
1625
|
+
if (!visitedChunkIds.has(childId)) {
|
|
1626
|
+
visitedChunkIds.add(childId);
|
|
1627
|
+
chunksToCheck.push(childId);
|
|
1628
|
+
const childChunk = statsJson.chunks?.find((c) => c.id === childId);
|
|
1629
|
+
if (childChunk?.files) {
|
|
1630
|
+
const jsFiles = childChunk.files.filter((f) => f.endsWith(".js")).filter((f) => !clientJsFiles.includes(f) && !dependencyChunks.includes(f));
|
|
1631
|
+
dependencyChunks.push(...jsFiles);
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
if (dependencyChunks.length > 0) {
|
|
1638
|
+
clientJsFiles.splice(-1, 0, ...dependencyChunks);
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
if (clientJsFiles.length > 0) {
|
|
1642
|
+
manifest.entrypoints = {
|
|
1643
|
+
client: clientJsFiles
|
|
1644
|
+
};
|
|
1645
|
+
manifest.client.js = clientJsFiles[clientJsFiles.length - 1];
|
|
1646
|
+
const clientCssFiles = clientEntrypoint.assets.map((asset) => typeof asset === "string" ? asset : asset.name).filter((name) => name.endsWith(".css"));
|
|
1647
|
+
if (clientCssFiles.length > 0) {
|
|
1648
|
+
manifest.client.css = clientCssFiles[0];
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
} catch (err) {
|
|
1653
|
+
console.warn("[framework] Failed to extract entrypoints from stats, falling back to file scanning:", err);
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
if (!manifest.client.js) {
|
|
1657
|
+
const clientJsMatch = files.find((f) => /^client\.[\w-]+\.js$/.test(f) || f === "client.js");
|
|
1658
|
+
if (clientJsMatch) {
|
|
1659
|
+
manifest.client.js = clientJsMatch;
|
|
1660
|
+
}
|
|
1301
1661
|
}
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1662
|
+
if (!manifest.client.css) {
|
|
1663
|
+
const clientCssMatch = files.find((f) => /^client\.[\w-]+\.css$/.test(f) || f === "client.css");
|
|
1664
|
+
if (clientCssMatch) {
|
|
1665
|
+
manifest.client.css = clientCssMatch;
|
|
1666
|
+
}
|
|
1305
1667
|
}
|
|
1668
|
+
const sharedChunksToAdd = [];
|
|
1306
1669
|
for (const file of files) {
|
|
1307
1670
|
if (!file.endsWith(".js")) continue;
|
|
1308
1671
|
if (file === manifest.client.js) continue;
|
|
1672
|
+
if (manifest.entrypoints?.client?.includes(file)) continue;
|
|
1309
1673
|
const routeMatch = file.match(/^(route-[^.]+)(\.[\w-]+)?\.js$/);
|
|
1310
1674
|
if (routeMatch) {
|
|
1311
1675
|
const chunkName = routeMatch[1];
|
|
1312
1676
|
manifest.chunks[chunkName] = file;
|
|
1313
1677
|
continue;
|
|
1314
1678
|
}
|
|
1679
|
+
const vendorMatch = file.match(/^(vendor)(\.[\w-]+)?\.js$/);
|
|
1680
|
+
if (vendorMatch) {
|
|
1681
|
+
const chunkName = vendorMatch[1];
|
|
1682
|
+
manifest.chunks[chunkName] = file;
|
|
1683
|
+
sharedChunksToAdd.push(file);
|
|
1684
|
+
continue;
|
|
1685
|
+
}
|
|
1686
|
+
const vendorCommonsMatch = file.match(/^(vendor-commons)(\.[\w-]+)?\.js$/);
|
|
1687
|
+
if (vendorCommonsMatch) {
|
|
1688
|
+
const chunkName = vendorCommonsMatch[1];
|
|
1689
|
+
manifest.chunks[chunkName] = file;
|
|
1690
|
+
sharedChunksToAdd.push(file);
|
|
1691
|
+
continue;
|
|
1692
|
+
}
|
|
1315
1693
|
const numericMatch = file.match(/^(\d+)(\.[\w-]+)?\.js$/);
|
|
1316
1694
|
if (numericMatch) {
|
|
1317
1695
|
const chunkName = numericMatch[1];
|
|
1318
1696
|
manifest.chunks[chunkName] = file;
|
|
1697
|
+
sharedChunksToAdd.push(file);
|
|
1698
|
+
continue;
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
if (sharedChunksToAdd.length > 0 && manifest.entrypoints?.client) {
|
|
1702
|
+
const entrypoints = manifest.entrypoints.client;
|
|
1703
|
+
const mainEntry = entrypoints[entrypoints.length - 1];
|
|
1704
|
+
const uniqueShared = sharedChunksToAdd.filter((f) => !entrypoints.includes(f));
|
|
1705
|
+
entrypoints.splice(-1, 0, ...uniqueShared);
|
|
1706
|
+
if (entrypoints[entrypoints.length - 1] !== mainEntry) {
|
|
1707
|
+
const mainIndex = entrypoints.indexOf(mainEntry);
|
|
1708
|
+
if (mainIndex >= 0) {
|
|
1709
|
+
entrypoints.splice(mainIndex, 1);
|
|
1710
|
+
}
|
|
1711
|
+
entrypoints.push(mainEntry);
|
|
1319
1712
|
}
|
|
1320
1713
|
}
|
|
1321
1714
|
return manifest;
|
|
@@ -1355,9 +1748,7 @@ function createClientConfig(projectRoot, mode) {
|
|
|
1355
1748
|
const outDir = path12.join(buildDir, "client");
|
|
1356
1749
|
const envPath2 = path12.join(projectRoot, ".env");
|
|
1357
1750
|
if (fs12.existsSync(envPath2)) {
|
|
1358
|
-
dotenv.config({
|
|
1359
|
-
path: envPath2
|
|
1360
|
-
});
|
|
1751
|
+
dotenv.config({ path: envPath2 });
|
|
1361
1752
|
}
|
|
1362
1753
|
const publicEnv = {};
|
|
1363
1754
|
for (const [key, value] of Object.entries(process.env)) {
|
|
@@ -1416,14 +1807,57 @@ function createClientConfig(projectRoot, mode) {
|
|
|
1416
1807
|
},
|
|
1417
1808
|
plugins: [
|
|
1418
1809
|
new rspack.DefinePlugin({
|
|
1419
|
-
//
|
|
1420
|
-
|
|
1810
|
+
// Use mode directly to ensure development mode is correctly set
|
|
1811
|
+
// This replaces process.env.NODE_ENV in the client bundle with the literal string value
|
|
1812
|
+
"process.env.NODE_ENV": JSON.stringify(mode),
|
|
1421
1813
|
...publicEnv
|
|
1422
1814
|
}),
|
|
1423
1815
|
new rspack.CssExtractRspackPlugin({
|
|
1424
1816
|
filename: mode === "production" ? "client.[contenthash].css" : "client.css"
|
|
1425
1817
|
})
|
|
1426
1818
|
],
|
|
1819
|
+
optimization: mode === "production" ? {
|
|
1820
|
+
usedExports: true,
|
|
1821
|
+
sideEffects: false,
|
|
1822
|
+
// More aggressive tree shaking - assume no side effects
|
|
1823
|
+
providedExports: true,
|
|
1824
|
+
concatenateModules: true,
|
|
1825
|
+
// Better for tree shaking
|
|
1826
|
+
minimize: true,
|
|
1827
|
+
removeEmptyChunks: true,
|
|
1828
|
+
mergeDuplicateChunks: true,
|
|
1829
|
+
// Improved code splitting: separate vendor chunks for better caching
|
|
1830
|
+
splitChunks: {
|
|
1831
|
+
chunks: "all",
|
|
1832
|
+
cacheGroups: {
|
|
1833
|
+
// Separate React/React-DOM into dedicated vendor chunk
|
|
1834
|
+
// This improves caching: React rarely changes, so users don't need to re-download it
|
|
1835
|
+
vendor: {
|
|
1836
|
+
test: /[\\/]node_modules[\\/](react|react-dom|scheduler)[\\/]/,
|
|
1837
|
+
name: "vendor",
|
|
1838
|
+
priority: 30,
|
|
1839
|
+
enforce: true,
|
|
1840
|
+
// Force separation even if only used once
|
|
1841
|
+
reuseExistingChunk: true
|
|
1842
|
+
},
|
|
1843
|
+
// Other node_modules dependencies in a separate chunk
|
|
1844
|
+
vendorCommons: {
|
|
1845
|
+
test: /[\\/]node_modules[\\/](?!(react|react-dom|scheduler)[\\/])/,
|
|
1846
|
+
name: "vendor-commons",
|
|
1847
|
+
priority: 10,
|
|
1848
|
+
minChunks: 2,
|
|
1849
|
+
// Only create if used in 2+ chunks
|
|
1850
|
+
reuseExistingChunk: true
|
|
1851
|
+
},
|
|
1852
|
+
// Default: shared application code (not in node_modules)
|
|
1853
|
+
default: {
|
|
1854
|
+
minChunks: 2,
|
|
1855
|
+
priority: 5,
|
|
1856
|
+
reuseExistingChunk: true
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1860
|
+
} : void 0,
|
|
1427
1861
|
infrastructureLogging: {
|
|
1428
1862
|
level: "error"
|
|
1429
1863
|
},
|
|
@@ -1452,7 +1886,13 @@ function startClientBundler(projectRoot, mode = "development") {
|
|
|
1452
1886
|
});
|
|
1453
1887
|
compiler.watch({}, (err, stats) => {
|
|
1454
1888
|
if (err) {
|
|
1455
|
-
console.error("[framework][client] Rspack error:"
|
|
1889
|
+
console.error("\n\u274C [framework][client] Rspack compilation error:");
|
|
1890
|
+
console.error(err);
|
|
1891
|
+
console.error("\n\u{1F4A1} Suggestions:");
|
|
1892
|
+
console.error(" \u2022 Check for syntax errors in your code");
|
|
1893
|
+
console.error(" \u2022 Verify all imports are correct");
|
|
1894
|
+
console.error(" \u2022 Ensure all dependencies are installed");
|
|
1895
|
+
console.error(" \u2022 Try deleting .loly folder and rebuilding\n");
|
|
1456
1896
|
isBuilding = false;
|
|
1457
1897
|
lastBuildTime = Date.now();
|
|
1458
1898
|
if (buildResolve) {
|
|
@@ -1463,17 +1903,20 @@ function startClientBundler(projectRoot, mode = "development") {
|
|
|
1463
1903
|
return;
|
|
1464
1904
|
}
|
|
1465
1905
|
if (!stats) {
|
|
1906
|
+
console.warn("\u26A0\uFE0F [framework][client] Build completed but no stats available");
|
|
1466
1907
|
isBuilding = false;
|
|
1467
1908
|
lastBuildTime = Date.now();
|
|
1468
1909
|
return;
|
|
1469
1910
|
}
|
|
1470
1911
|
if (stats.hasErrors()) {
|
|
1471
|
-
console.error(
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
);
|
|
1912
|
+
console.error("\n\u274C [framework][client] Build failed with errors:\n");
|
|
1913
|
+
console.error(stats.toString("errors-only"));
|
|
1914
|
+
console.error("\n\u{1F4A1} Common fixes:");
|
|
1915
|
+
console.error(" \u2022 Fix syntax errors shown above");
|
|
1916
|
+
console.error(" \u2022 Check for missing imports or dependencies");
|
|
1917
|
+
console.error(" \u2022 Verify TypeScript types are correct\n");
|
|
1475
1918
|
} else {
|
|
1476
|
-
console.log("[framework][client]
|
|
1919
|
+
console.log("\u2705 [framework][client] Client bundle rebuilt successfully");
|
|
1477
1920
|
}
|
|
1478
1921
|
isBuilding = false;
|
|
1479
1922
|
lastBuildTime = Date.now();
|
|
@@ -1508,23 +1951,33 @@ function buildClientBundle(projectRoot) {
|
|
|
1508
1951
|
compiler.close(() => {
|
|
1509
1952
|
});
|
|
1510
1953
|
if (err) {
|
|
1511
|
-
console.error("[framework][client]
|
|
1954
|
+
console.error("\n\u274C [framework][client] Production build error:");
|
|
1955
|
+
console.error(err);
|
|
1956
|
+
console.error("\n\u{1F4A1} Suggestions:");
|
|
1957
|
+
console.error(" \u2022 Check for syntax errors in your code");
|
|
1958
|
+
console.error(" \u2022 Verify all imports are correct");
|
|
1959
|
+
console.error(" \u2022 Ensure all dependencies are installed");
|
|
1960
|
+
console.error(" \u2022 Review the error details above\n");
|
|
1512
1961
|
return reject(err);
|
|
1513
1962
|
}
|
|
1514
1963
|
if (!stats) {
|
|
1515
|
-
const error = new Error("No stats from Rspack");
|
|
1516
|
-
console.error("[framework][client] Build error:", error);
|
|
1964
|
+
const error = new Error("No stats from Rspack - build may have failed silently");
|
|
1965
|
+
console.error("\n\u274C [framework][client] Build error:", error.message);
|
|
1966
|
+
console.error("\u{1F4A1} Try rebuilding or check Rspack configuration\n");
|
|
1517
1967
|
return reject(error);
|
|
1518
1968
|
}
|
|
1519
1969
|
if (stats.hasErrors()) {
|
|
1520
|
-
console.error(
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
);
|
|
1524
|
-
|
|
1970
|
+
console.error("\n\u274C [framework][client] Production build failed:\n");
|
|
1971
|
+
console.error(stats.toString("errors-only"));
|
|
1972
|
+
console.error("\n\u{1F4A1} Common fixes:");
|
|
1973
|
+
console.error(" \u2022 Fix syntax errors shown above");
|
|
1974
|
+
console.error(" \u2022 Check for missing imports or dependencies");
|
|
1975
|
+
console.error(" \u2022 Verify TypeScript types are correct");
|
|
1976
|
+
console.error(" \u2022 Review build configuration\n");
|
|
1977
|
+
return reject(new Error("Client build failed - see errors above"));
|
|
1525
1978
|
}
|
|
1526
1979
|
copyStaticAssets(projectRoot, outDir);
|
|
1527
|
-
const assetManifest = generateAssetManifest(outDir);
|
|
1980
|
+
const assetManifest = generateAssetManifest(outDir, stats);
|
|
1528
1981
|
const manifestPath = path13.join(projectRoot, BUILD_FOLDER_NAME, "asset-manifest.json");
|
|
1529
1982
|
fs13.writeFileSync(manifestPath, JSON.stringify(assetManifest, null, 2), "utf-8");
|
|
1530
1983
|
resolve3({ outDir });
|
|
@@ -1608,7 +2061,7 @@ var ReaddirpStream = class extends Readable {
|
|
|
1608
2061
|
this._directoryFilter = normalizeFilter(opts.directoryFilter);
|
|
1609
2062
|
const statMethod = opts.lstat ? lstat : stat;
|
|
1610
2063
|
if (wantBigintFsStats) {
|
|
1611
|
-
this._stat = (
|
|
2064
|
+
this._stat = (path28) => statMethod(path28, { bigint: true });
|
|
1612
2065
|
} else {
|
|
1613
2066
|
this._stat = statMethod;
|
|
1614
2067
|
}
|
|
@@ -1633,8 +2086,8 @@ var ReaddirpStream = class extends Readable {
|
|
|
1633
2086
|
const par = this.parent;
|
|
1634
2087
|
const fil = par && par.files;
|
|
1635
2088
|
if (fil && fil.length > 0) {
|
|
1636
|
-
const { path:
|
|
1637
|
-
const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent,
|
|
2089
|
+
const { path: path28, depth } = par;
|
|
2090
|
+
const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path28));
|
|
1638
2091
|
const awaited = await Promise.all(slice);
|
|
1639
2092
|
for (const entry of awaited) {
|
|
1640
2093
|
if (!entry)
|
|
@@ -1674,20 +2127,20 @@ var ReaddirpStream = class extends Readable {
|
|
|
1674
2127
|
this.reading = false;
|
|
1675
2128
|
}
|
|
1676
2129
|
}
|
|
1677
|
-
async _exploreDir(
|
|
2130
|
+
async _exploreDir(path28, depth) {
|
|
1678
2131
|
let files;
|
|
1679
2132
|
try {
|
|
1680
|
-
files = await readdir(
|
|
2133
|
+
files = await readdir(path28, this._rdOptions);
|
|
1681
2134
|
} catch (error) {
|
|
1682
2135
|
this._onError(error);
|
|
1683
2136
|
}
|
|
1684
|
-
return { files, depth, path:
|
|
2137
|
+
return { files, depth, path: path28 };
|
|
1685
2138
|
}
|
|
1686
|
-
async _formatEntry(dirent,
|
|
2139
|
+
async _formatEntry(dirent, path28) {
|
|
1687
2140
|
let entry;
|
|
1688
2141
|
const basename3 = this._isDirent ? dirent.name : dirent;
|
|
1689
2142
|
try {
|
|
1690
|
-
const fullPath = presolve(pjoin(
|
|
2143
|
+
const fullPath = presolve(pjoin(path28, basename3));
|
|
1691
2144
|
entry = { path: prelative(this._root, fullPath), fullPath, basename: basename3 };
|
|
1692
2145
|
entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
|
|
1693
2146
|
} catch (err) {
|
|
@@ -2087,16 +2540,16 @@ var delFromSet = (main, prop, item) => {
|
|
|
2087
2540
|
};
|
|
2088
2541
|
var isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
|
|
2089
2542
|
var FsWatchInstances = /* @__PURE__ */ new Map();
|
|
2090
|
-
function createFsWatchInstance(
|
|
2543
|
+
function createFsWatchInstance(path28, options, listener, errHandler, emitRaw) {
|
|
2091
2544
|
const handleEvent = (rawEvent, evPath) => {
|
|
2092
|
-
listener(
|
|
2093
|
-
emitRaw(rawEvent, evPath, { watchedPath:
|
|
2094
|
-
if (evPath &&
|
|
2095
|
-
fsWatchBroadcast(sysPath.resolve(
|
|
2545
|
+
listener(path28);
|
|
2546
|
+
emitRaw(rawEvent, evPath, { watchedPath: path28 });
|
|
2547
|
+
if (evPath && path28 !== evPath) {
|
|
2548
|
+
fsWatchBroadcast(sysPath.resolve(path28, evPath), KEY_LISTENERS, sysPath.join(path28, evPath));
|
|
2096
2549
|
}
|
|
2097
2550
|
};
|
|
2098
2551
|
try {
|
|
2099
|
-
return fs_watch(
|
|
2552
|
+
return fs_watch(path28, {
|
|
2100
2553
|
persistent: options.persistent
|
|
2101
2554
|
}, handleEvent);
|
|
2102
2555
|
} catch (error) {
|
|
@@ -2112,12 +2565,12 @@ var fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {
|
|
|
2112
2565
|
listener(val1, val2, val3);
|
|
2113
2566
|
});
|
|
2114
2567
|
};
|
|
2115
|
-
var setFsWatchListener = (
|
|
2568
|
+
var setFsWatchListener = (path28, fullPath, options, handlers) => {
|
|
2116
2569
|
const { listener, errHandler, rawEmitter } = handlers;
|
|
2117
2570
|
let cont = FsWatchInstances.get(fullPath);
|
|
2118
2571
|
let watcher;
|
|
2119
2572
|
if (!options.persistent) {
|
|
2120
|
-
watcher = createFsWatchInstance(
|
|
2573
|
+
watcher = createFsWatchInstance(path28, options, listener, errHandler, rawEmitter);
|
|
2121
2574
|
if (!watcher)
|
|
2122
2575
|
return;
|
|
2123
2576
|
return watcher.close.bind(watcher);
|
|
@@ -2128,7 +2581,7 @@ var setFsWatchListener = (path25, fullPath, options, handlers) => {
|
|
|
2128
2581
|
addAndConvert(cont, KEY_RAW, rawEmitter);
|
|
2129
2582
|
} else {
|
|
2130
2583
|
watcher = createFsWatchInstance(
|
|
2131
|
-
|
|
2584
|
+
path28,
|
|
2132
2585
|
options,
|
|
2133
2586
|
fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS),
|
|
2134
2587
|
errHandler,
|
|
@@ -2143,7 +2596,7 @@ var setFsWatchListener = (path25, fullPath, options, handlers) => {
|
|
|
2143
2596
|
cont.watcherUnusable = true;
|
|
2144
2597
|
if (isWindows && error.code === "EPERM") {
|
|
2145
2598
|
try {
|
|
2146
|
-
const fd = await open(
|
|
2599
|
+
const fd = await open(path28, "r");
|
|
2147
2600
|
await fd.close();
|
|
2148
2601
|
broadcastErr(error);
|
|
2149
2602
|
} catch (err) {
|
|
@@ -2174,7 +2627,7 @@ var setFsWatchListener = (path25, fullPath, options, handlers) => {
|
|
|
2174
2627
|
};
|
|
2175
2628
|
};
|
|
2176
2629
|
var FsWatchFileInstances = /* @__PURE__ */ new Map();
|
|
2177
|
-
var setFsWatchFileListener = (
|
|
2630
|
+
var setFsWatchFileListener = (path28, fullPath, options, handlers) => {
|
|
2178
2631
|
const { listener, rawEmitter } = handlers;
|
|
2179
2632
|
let cont = FsWatchFileInstances.get(fullPath);
|
|
2180
2633
|
const copts = cont && cont.options;
|
|
@@ -2196,7 +2649,7 @@ var setFsWatchFileListener = (path25, fullPath, options, handlers) => {
|
|
|
2196
2649
|
});
|
|
2197
2650
|
const currmtime = curr.mtimeMs;
|
|
2198
2651
|
if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
|
|
2199
|
-
foreach(cont.listeners, (listener2) => listener2(
|
|
2652
|
+
foreach(cont.listeners, (listener2) => listener2(path28, curr));
|
|
2200
2653
|
}
|
|
2201
2654
|
})
|
|
2202
2655
|
};
|
|
@@ -2224,13 +2677,13 @@ var NodeFsHandler = class {
|
|
|
2224
2677
|
* @param listener on fs change
|
|
2225
2678
|
* @returns closer for the watcher instance
|
|
2226
2679
|
*/
|
|
2227
|
-
_watchWithNodeFs(
|
|
2680
|
+
_watchWithNodeFs(path28, listener) {
|
|
2228
2681
|
const opts = this.fsw.options;
|
|
2229
|
-
const directory = sysPath.dirname(
|
|
2230
|
-
const basename3 = sysPath.basename(
|
|
2682
|
+
const directory = sysPath.dirname(path28);
|
|
2683
|
+
const basename3 = sysPath.basename(path28);
|
|
2231
2684
|
const parent = this.fsw._getWatchedDir(directory);
|
|
2232
2685
|
parent.add(basename3);
|
|
2233
|
-
const absolutePath = sysPath.resolve(
|
|
2686
|
+
const absolutePath = sysPath.resolve(path28);
|
|
2234
2687
|
const options = {
|
|
2235
2688
|
persistent: opts.persistent
|
|
2236
2689
|
};
|
|
@@ -2240,12 +2693,12 @@ var NodeFsHandler = class {
|
|
|
2240
2693
|
if (opts.usePolling) {
|
|
2241
2694
|
const enableBin = opts.interval !== opts.binaryInterval;
|
|
2242
2695
|
options.interval = enableBin && isBinaryPath(basename3) ? opts.binaryInterval : opts.interval;
|
|
2243
|
-
closer = setFsWatchFileListener(
|
|
2696
|
+
closer = setFsWatchFileListener(path28, absolutePath, options, {
|
|
2244
2697
|
listener,
|
|
2245
2698
|
rawEmitter: this.fsw._emitRaw
|
|
2246
2699
|
});
|
|
2247
2700
|
} else {
|
|
2248
|
-
closer = setFsWatchListener(
|
|
2701
|
+
closer = setFsWatchListener(path28, absolutePath, options, {
|
|
2249
2702
|
listener,
|
|
2250
2703
|
errHandler: this._boundHandleError,
|
|
2251
2704
|
rawEmitter: this.fsw._emitRaw
|
|
@@ -2267,7 +2720,7 @@ var NodeFsHandler = class {
|
|
|
2267
2720
|
let prevStats = stats;
|
|
2268
2721
|
if (parent.has(basename3))
|
|
2269
2722
|
return;
|
|
2270
|
-
const listener = async (
|
|
2723
|
+
const listener = async (path28, newStats) => {
|
|
2271
2724
|
if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
|
|
2272
2725
|
return;
|
|
2273
2726
|
if (!newStats || newStats.mtimeMs === 0) {
|
|
@@ -2281,11 +2734,11 @@ var NodeFsHandler = class {
|
|
|
2281
2734
|
this.fsw._emit(EV.CHANGE, file, newStats2);
|
|
2282
2735
|
}
|
|
2283
2736
|
if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats2.ino) {
|
|
2284
|
-
this.fsw._closeFile(
|
|
2737
|
+
this.fsw._closeFile(path28);
|
|
2285
2738
|
prevStats = newStats2;
|
|
2286
2739
|
const closer2 = this._watchWithNodeFs(file, listener);
|
|
2287
2740
|
if (closer2)
|
|
2288
|
-
this.fsw._addPathCloser(
|
|
2741
|
+
this.fsw._addPathCloser(path28, closer2);
|
|
2289
2742
|
} else {
|
|
2290
2743
|
prevStats = newStats2;
|
|
2291
2744
|
}
|
|
@@ -2317,7 +2770,7 @@ var NodeFsHandler = class {
|
|
|
2317
2770
|
* @param item basename of this item
|
|
2318
2771
|
* @returns true if no more processing is needed for this entry.
|
|
2319
2772
|
*/
|
|
2320
|
-
async _handleSymlink(entry, directory,
|
|
2773
|
+
async _handleSymlink(entry, directory, path28, item) {
|
|
2321
2774
|
if (this.fsw.closed) {
|
|
2322
2775
|
return;
|
|
2323
2776
|
}
|
|
@@ -2327,7 +2780,7 @@ var NodeFsHandler = class {
|
|
|
2327
2780
|
this.fsw._incrReadyCount();
|
|
2328
2781
|
let linkPath;
|
|
2329
2782
|
try {
|
|
2330
|
-
linkPath = await fsrealpath(
|
|
2783
|
+
linkPath = await fsrealpath(path28);
|
|
2331
2784
|
} catch (e) {
|
|
2332
2785
|
this.fsw._emitReady();
|
|
2333
2786
|
return true;
|
|
@@ -2337,12 +2790,12 @@ var NodeFsHandler = class {
|
|
|
2337
2790
|
if (dir.has(item)) {
|
|
2338
2791
|
if (this.fsw._symlinkPaths.get(full) !== linkPath) {
|
|
2339
2792
|
this.fsw._symlinkPaths.set(full, linkPath);
|
|
2340
|
-
this.fsw._emit(EV.CHANGE,
|
|
2793
|
+
this.fsw._emit(EV.CHANGE, path28, entry.stats);
|
|
2341
2794
|
}
|
|
2342
2795
|
} else {
|
|
2343
2796
|
dir.add(item);
|
|
2344
2797
|
this.fsw._symlinkPaths.set(full, linkPath);
|
|
2345
|
-
this.fsw._emit(EV.ADD,
|
|
2798
|
+
this.fsw._emit(EV.ADD, path28, entry.stats);
|
|
2346
2799
|
}
|
|
2347
2800
|
this.fsw._emitReady();
|
|
2348
2801
|
return true;
|
|
@@ -2371,9 +2824,9 @@ var NodeFsHandler = class {
|
|
|
2371
2824
|
return;
|
|
2372
2825
|
}
|
|
2373
2826
|
const item = entry.path;
|
|
2374
|
-
let
|
|
2827
|
+
let path28 = sysPath.join(directory, item);
|
|
2375
2828
|
current.add(item);
|
|
2376
|
-
if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory,
|
|
2829
|
+
if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path28, item)) {
|
|
2377
2830
|
return;
|
|
2378
2831
|
}
|
|
2379
2832
|
if (this.fsw.closed) {
|
|
@@ -2382,8 +2835,8 @@ var NodeFsHandler = class {
|
|
|
2382
2835
|
}
|
|
2383
2836
|
if (item === target || !target && !previous.has(item)) {
|
|
2384
2837
|
this.fsw._incrReadyCount();
|
|
2385
|
-
|
|
2386
|
-
this._addToNodeFs(
|
|
2838
|
+
path28 = sysPath.join(dir, sysPath.relative(dir, path28));
|
|
2839
|
+
this._addToNodeFs(path28, initialAdd, wh, depth + 1);
|
|
2387
2840
|
}
|
|
2388
2841
|
}).on(EV.ERROR, this._boundHandleError);
|
|
2389
2842
|
return new Promise((resolve3, reject) => {
|
|
@@ -2452,13 +2905,13 @@ var NodeFsHandler = class {
|
|
|
2452
2905
|
* @param depth Child path actually targeted for watch
|
|
2453
2906
|
* @param target Child path actually targeted for watch
|
|
2454
2907
|
*/
|
|
2455
|
-
async _addToNodeFs(
|
|
2908
|
+
async _addToNodeFs(path28, initialAdd, priorWh, depth, target) {
|
|
2456
2909
|
const ready = this.fsw._emitReady;
|
|
2457
|
-
if (this.fsw._isIgnored(
|
|
2910
|
+
if (this.fsw._isIgnored(path28) || this.fsw.closed) {
|
|
2458
2911
|
ready();
|
|
2459
2912
|
return false;
|
|
2460
2913
|
}
|
|
2461
|
-
const wh = this.fsw._getWatchHelpers(
|
|
2914
|
+
const wh = this.fsw._getWatchHelpers(path28);
|
|
2462
2915
|
if (priorWh) {
|
|
2463
2916
|
wh.filterPath = (entry) => priorWh.filterPath(entry);
|
|
2464
2917
|
wh.filterDir = (entry) => priorWh.filterDir(entry);
|
|
@@ -2474,8 +2927,8 @@ var NodeFsHandler = class {
|
|
|
2474
2927
|
const follow = this.fsw.options.followSymlinks;
|
|
2475
2928
|
let closer;
|
|
2476
2929
|
if (stats.isDirectory()) {
|
|
2477
|
-
const absPath = sysPath.resolve(
|
|
2478
|
-
const targetPath = follow ? await fsrealpath(
|
|
2930
|
+
const absPath = sysPath.resolve(path28);
|
|
2931
|
+
const targetPath = follow ? await fsrealpath(path28) : path28;
|
|
2479
2932
|
if (this.fsw.closed)
|
|
2480
2933
|
return;
|
|
2481
2934
|
closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
|
|
@@ -2485,29 +2938,29 @@ var NodeFsHandler = class {
|
|
|
2485
2938
|
this.fsw._symlinkPaths.set(absPath, targetPath);
|
|
2486
2939
|
}
|
|
2487
2940
|
} else if (stats.isSymbolicLink()) {
|
|
2488
|
-
const targetPath = follow ? await fsrealpath(
|
|
2941
|
+
const targetPath = follow ? await fsrealpath(path28) : path28;
|
|
2489
2942
|
if (this.fsw.closed)
|
|
2490
2943
|
return;
|
|
2491
2944
|
const parent = sysPath.dirname(wh.watchPath);
|
|
2492
2945
|
this.fsw._getWatchedDir(parent).add(wh.watchPath);
|
|
2493
2946
|
this.fsw._emit(EV.ADD, wh.watchPath, stats);
|
|
2494
|
-
closer = await this._handleDir(parent, stats, initialAdd, depth,
|
|
2947
|
+
closer = await this._handleDir(parent, stats, initialAdd, depth, path28, wh, targetPath);
|
|
2495
2948
|
if (this.fsw.closed)
|
|
2496
2949
|
return;
|
|
2497
2950
|
if (targetPath !== void 0) {
|
|
2498
|
-
this.fsw._symlinkPaths.set(sysPath.resolve(
|
|
2951
|
+
this.fsw._symlinkPaths.set(sysPath.resolve(path28), targetPath);
|
|
2499
2952
|
}
|
|
2500
2953
|
} else {
|
|
2501
2954
|
closer = this._handleFile(wh.watchPath, stats, initialAdd);
|
|
2502
2955
|
}
|
|
2503
2956
|
ready();
|
|
2504
2957
|
if (closer)
|
|
2505
|
-
this.fsw._addPathCloser(
|
|
2958
|
+
this.fsw._addPathCloser(path28, closer);
|
|
2506
2959
|
return false;
|
|
2507
2960
|
} catch (error) {
|
|
2508
2961
|
if (this.fsw._handleError(error)) {
|
|
2509
2962
|
ready();
|
|
2510
|
-
return
|
|
2963
|
+
return path28;
|
|
2511
2964
|
}
|
|
2512
2965
|
}
|
|
2513
2966
|
}
|
|
@@ -2550,26 +3003,26 @@ function createPattern(matcher) {
|
|
|
2550
3003
|
}
|
|
2551
3004
|
return () => false;
|
|
2552
3005
|
}
|
|
2553
|
-
function normalizePath(
|
|
2554
|
-
if (typeof
|
|
3006
|
+
function normalizePath(path28) {
|
|
3007
|
+
if (typeof path28 !== "string")
|
|
2555
3008
|
throw new Error("string expected");
|
|
2556
|
-
|
|
2557
|
-
|
|
3009
|
+
path28 = sysPath2.normalize(path28);
|
|
3010
|
+
path28 = path28.replace(/\\/g, "/");
|
|
2558
3011
|
let prepend = false;
|
|
2559
|
-
if (
|
|
3012
|
+
if (path28.startsWith("//"))
|
|
2560
3013
|
prepend = true;
|
|
2561
3014
|
const DOUBLE_SLASH_RE2 = /\/\//;
|
|
2562
|
-
while (
|
|
2563
|
-
|
|
3015
|
+
while (path28.match(DOUBLE_SLASH_RE2))
|
|
3016
|
+
path28 = path28.replace(DOUBLE_SLASH_RE2, "/");
|
|
2564
3017
|
if (prepend)
|
|
2565
|
-
|
|
2566
|
-
return
|
|
3018
|
+
path28 = "/" + path28;
|
|
3019
|
+
return path28;
|
|
2567
3020
|
}
|
|
2568
3021
|
function matchPatterns(patterns, testString, stats) {
|
|
2569
|
-
const
|
|
3022
|
+
const path28 = normalizePath(testString);
|
|
2570
3023
|
for (let index = 0; index < patterns.length; index++) {
|
|
2571
3024
|
const pattern = patterns[index];
|
|
2572
|
-
if (pattern(
|
|
3025
|
+
if (pattern(path28, stats)) {
|
|
2573
3026
|
return true;
|
|
2574
3027
|
}
|
|
2575
3028
|
}
|
|
@@ -2609,19 +3062,19 @@ var toUnix = (string) => {
|
|
|
2609
3062
|
}
|
|
2610
3063
|
return str;
|
|
2611
3064
|
};
|
|
2612
|
-
var normalizePathToUnix = (
|
|
2613
|
-
var normalizeIgnored = (cwd = "") => (
|
|
2614
|
-
if (typeof
|
|
2615
|
-
return normalizePathToUnix(sysPath2.isAbsolute(
|
|
3065
|
+
var normalizePathToUnix = (path28) => toUnix(sysPath2.normalize(toUnix(path28)));
|
|
3066
|
+
var normalizeIgnored = (cwd = "") => (path28) => {
|
|
3067
|
+
if (typeof path28 === "string") {
|
|
3068
|
+
return normalizePathToUnix(sysPath2.isAbsolute(path28) ? path28 : sysPath2.join(cwd, path28));
|
|
2616
3069
|
} else {
|
|
2617
|
-
return
|
|
3070
|
+
return path28;
|
|
2618
3071
|
}
|
|
2619
3072
|
};
|
|
2620
|
-
var getAbsolutePath = (
|
|
2621
|
-
if (sysPath2.isAbsolute(
|
|
2622
|
-
return
|
|
3073
|
+
var getAbsolutePath = (path28, cwd) => {
|
|
3074
|
+
if (sysPath2.isAbsolute(path28)) {
|
|
3075
|
+
return path28;
|
|
2623
3076
|
}
|
|
2624
|
-
return sysPath2.join(cwd,
|
|
3077
|
+
return sysPath2.join(cwd, path28);
|
|
2625
3078
|
};
|
|
2626
3079
|
var EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set());
|
|
2627
3080
|
var DirEntry = class {
|
|
@@ -2676,10 +3129,10 @@ var DirEntry = class {
|
|
|
2676
3129
|
var STAT_METHOD_F = "stat";
|
|
2677
3130
|
var STAT_METHOD_L = "lstat";
|
|
2678
3131
|
var WatchHelper = class {
|
|
2679
|
-
constructor(
|
|
3132
|
+
constructor(path28, follow, fsw) {
|
|
2680
3133
|
this.fsw = fsw;
|
|
2681
|
-
const watchPath =
|
|
2682
|
-
this.path =
|
|
3134
|
+
const watchPath = path28;
|
|
3135
|
+
this.path = path28 = path28.replace(REPLACER_RE, "");
|
|
2683
3136
|
this.watchPath = watchPath;
|
|
2684
3137
|
this.fullWatchPath = sysPath2.resolve(watchPath);
|
|
2685
3138
|
this.dirParts = [];
|
|
@@ -2801,20 +3254,20 @@ var FSWatcher = class extends EventEmitter {
|
|
|
2801
3254
|
this._closePromise = void 0;
|
|
2802
3255
|
let paths = unifyPaths(paths_);
|
|
2803
3256
|
if (cwd) {
|
|
2804
|
-
paths = paths.map((
|
|
2805
|
-
const absPath = getAbsolutePath(
|
|
3257
|
+
paths = paths.map((path28) => {
|
|
3258
|
+
const absPath = getAbsolutePath(path28, cwd);
|
|
2806
3259
|
return absPath;
|
|
2807
3260
|
});
|
|
2808
3261
|
}
|
|
2809
|
-
paths.forEach((
|
|
2810
|
-
this._removeIgnoredPath(
|
|
3262
|
+
paths.forEach((path28) => {
|
|
3263
|
+
this._removeIgnoredPath(path28);
|
|
2811
3264
|
});
|
|
2812
3265
|
this._userIgnored = void 0;
|
|
2813
3266
|
if (!this._readyCount)
|
|
2814
3267
|
this._readyCount = 0;
|
|
2815
3268
|
this._readyCount += paths.length;
|
|
2816
|
-
Promise.all(paths.map(async (
|
|
2817
|
-
const res = await this._nodeFsHandler._addToNodeFs(
|
|
3269
|
+
Promise.all(paths.map(async (path28) => {
|
|
3270
|
+
const res = await this._nodeFsHandler._addToNodeFs(path28, !_internal, void 0, 0, _origAdd);
|
|
2818
3271
|
if (res)
|
|
2819
3272
|
this._emitReady();
|
|
2820
3273
|
return res;
|
|
@@ -2836,17 +3289,17 @@ var FSWatcher = class extends EventEmitter {
|
|
|
2836
3289
|
return this;
|
|
2837
3290
|
const paths = unifyPaths(paths_);
|
|
2838
3291
|
const { cwd } = this.options;
|
|
2839
|
-
paths.forEach((
|
|
2840
|
-
if (!sysPath2.isAbsolute(
|
|
3292
|
+
paths.forEach((path28) => {
|
|
3293
|
+
if (!sysPath2.isAbsolute(path28) && !this._closers.has(path28)) {
|
|
2841
3294
|
if (cwd)
|
|
2842
|
-
|
|
2843
|
-
|
|
3295
|
+
path28 = sysPath2.join(cwd, path28);
|
|
3296
|
+
path28 = sysPath2.resolve(path28);
|
|
2844
3297
|
}
|
|
2845
|
-
this._closePath(
|
|
2846
|
-
this._addIgnoredPath(
|
|
2847
|
-
if (this._watched.has(
|
|
3298
|
+
this._closePath(path28);
|
|
3299
|
+
this._addIgnoredPath(path28);
|
|
3300
|
+
if (this._watched.has(path28)) {
|
|
2848
3301
|
this._addIgnoredPath({
|
|
2849
|
-
path:
|
|
3302
|
+
path: path28,
|
|
2850
3303
|
recursive: true
|
|
2851
3304
|
});
|
|
2852
3305
|
}
|
|
@@ -2910,38 +3363,38 @@ var FSWatcher = class extends EventEmitter {
|
|
|
2910
3363
|
* @param stats arguments to be passed with event
|
|
2911
3364
|
* @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
|
|
2912
3365
|
*/
|
|
2913
|
-
async _emit(event,
|
|
3366
|
+
async _emit(event, path28, stats) {
|
|
2914
3367
|
if (this.closed)
|
|
2915
3368
|
return;
|
|
2916
3369
|
const opts = this.options;
|
|
2917
3370
|
if (isWindows)
|
|
2918
|
-
|
|
3371
|
+
path28 = sysPath2.normalize(path28);
|
|
2919
3372
|
if (opts.cwd)
|
|
2920
|
-
|
|
2921
|
-
const args = [
|
|
3373
|
+
path28 = sysPath2.relative(opts.cwd, path28);
|
|
3374
|
+
const args = [path28];
|
|
2922
3375
|
if (stats != null)
|
|
2923
3376
|
args.push(stats);
|
|
2924
3377
|
const awf = opts.awaitWriteFinish;
|
|
2925
3378
|
let pw;
|
|
2926
|
-
if (awf && (pw = this._pendingWrites.get(
|
|
3379
|
+
if (awf && (pw = this._pendingWrites.get(path28))) {
|
|
2927
3380
|
pw.lastChange = /* @__PURE__ */ new Date();
|
|
2928
3381
|
return this;
|
|
2929
3382
|
}
|
|
2930
3383
|
if (opts.atomic) {
|
|
2931
3384
|
if (event === EVENTS.UNLINK) {
|
|
2932
|
-
this._pendingUnlinks.set(
|
|
3385
|
+
this._pendingUnlinks.set(path28, [event, ...args]);
|
|
2933
3386
|
setTimeout(() => {
|
|
2934
|
-
this._pendingUnlinks.forEach((entry,
|
|
3387
|
+
this._pendingUnlinks.forEach((entry, path29) => {
|
|
2935
3388
|
this.emit(...entry);
|
|
2936
3389
|
this.emit(EVENTS.ALL, ...entry);
|
|
2937
|
-
this._pendingUnlinks.delete(
|
|
3390
|
+
this._pendingUnlinks.delete(path29);
|
|
2938
3391
|
});
|
|
2939
3392
|
}, typeof opts.atomic === "number" ? opts.atomic : 100);
|
|
2940
3393
|
return this;
|
|
2941
3394
|
}
|
|
2942
|
-
if (event === EVENTS.ADD && this._pendingUnlinks.has(
|
|
3395
|
+
if (event === EVENTS.ADD && this._pendingUnlinks.has(path28)) {
|
|
2943
3396
|
event = EVENTS.CHANGE;
|
|
2944
|
-
this._pendingUnlinks.delete(
|
|
3397
|
+
this._pendingUnlinks.delete(path28);
|
|
2945
3398
|
}
|
|
2946
3399
|
}
|
|
2947
3400
|
if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
|
|
@@ -2959,16 +3412,16 @@ var FSWatcher = class extends EventEmitter {
|
|
|
2959
3412
|
this.emitWithAll(event, args);
|
|
2960
3413
|
}
|
|
2961
3414
|
};
|
|
2962
|
-
this._awaitWriteFinish(
|
|
3415
|
+
this._awaitWriteFinish(path28, awf.stabilityThreshold, event, awfEmit);
|
|
2963
3416
|
return this;
|
|
2964
3417
|
}
|
|
2965
3418
|
if (event === EVENTS.CHANGE) {
|
|
2966
|
-
const isThrottled = !this._throttle(EVENTS.CHANGE,
|
|
3419
|
+
const isThrottled = !this._throttle(EVENTS.CHANGE, path28, 50);
|
|
2967
3420
|
if (isThrottled)
|
|
2968
3421
|
return this;
|
|
2969
3422
|
}
|
|
2970
3423
|
if (opts.alwaysStat && stats === void 0 && (event === EVENTS.ADD || event === EVENTS.ADD_DIR || event === EVENTS.CHANGE)) {
|
|
2971
|
-
const fullPath = opts.cwd ? sysPath2.join(opts.cwd,
|
|
3424
|
+
const fullPath = opts.cwd ? sysPath2.join(opts.cwd, path28) : path28;
|
|
2972
3425
|
let stats2;
|
|
2973
3426
|
try {
|
|
2974
3427
|
stats2 = await stat3(fullPath);
|
|
@@ -2999,23 +3452,23 @@ var FSWatcher = class extends EventEmitter {
|
|
|
2999
3452
|
* @param timeout duration of time to suppress duplicate actions
|
|
3000
3453
|
* @returns tracking object or false if action should be suppressed
|
|
3001
3454
|
*/
|
|
3002
|
-
_throttle(actionType,
|
|
3455
|
+
_throttle(actionType, path28, timeout) {
|
|
3003
3456
|
if (!this._throttled.has(actionType)) {
|
|
3004
3457
|
this._throttled.set(actionType, /* @__PURE__ */ new Map());
|
|
3005
3458
|
}
|
|
3006
3459
|
const action = this._throttled.get(actionType);
|
|
3007
3460
|
if (!action)
|
|
3008
3461
|
throw new Error("invalid throttle");
|
|
3009
|
-
const actionPath = action.get(
|
|
3462
|
+
const actionPath = action.get(path28);
|
|
3010
3463
|
if (actionPath) {
|
|
3011
3464
|
actionPath.count++;
|
|
3012
3465
|
return false;
|
|
3013
3466
|
}
|
|
3014
3467
|
let timeoutObject;
|
|
3015
3468
|
const clear = () => {
|
|
3016
|
-
const item = action.get(
|
|
3469
|
+
const item = action.get(path28);
|
|
3017
3470
|
const count = item ? item.count : 0;
|
|
3018
|
-
action.delete(
|
|
3471
|
+
action.delete(path28);
|
|
3019
3472
|
clearTimeout(timeoutObject);
|
|
3020
3473
|
if (item)
|
|
3021
3474
|
clearTimeout(item.timeoutObject);
|
|
@@ -3023,7 +3476,7 @@ var FSWatcher = class extends EventEmitter {
|
|
|
3023
3476
|
};
|
|
3024
3477
|
timeoutObject = setTimeout(clear, timeout);
|
|
3025
3478
|
const thr = { timeoutObject, clear, count: 0 };
|
|
3026
|
-
action.set(
|
|
3479
|
+
action.set(path28, thr);
|
|
3027
3480
|
return thr;
|
|
3028
3481
|
}
|
|
3029
3482
|
_incrReadyCount() {
|
|
@@ -3037,44 +3490,44 @@ var FSWatcher = class extends EventEmitter {
|
|
|
3037
3490
|
* @param event
|
|
3038
3491
|
* @param awfEmit Callback to be called when ready for event to be emitted.
|
|
3039
3492
|
*/
|
|
3040
|
-
_awaitWriteFinish(
|
|
3493
|
+
_awaitWriteFinish(path28, threshold, event, awfEmit) {
|
|
3041
3494
|
const awf = this.options.awaitWriteFinish;
|
|
3042
3495
|
if (typeof awf !== "object")
|
|
3043
3496
|
return;
|
|
3044
3497
|
const pollInterval = awf.pollInterval;
|
|
3045
3498
|
let timeoutHandler;
|
|
3046
|
-
let fullPath =
|
|
3047
|
-
if (this.options.cwd && !sysPath2.isAbsolute(
|
|
3048
|
-
fullPath = sysPath2.join(this.options.cwd,
|
|
3499
|
+
let fullPath = path28;
|
|
3500
|
+
if (this.options.cwd && !sysPath2.isAbsolute(path28)) {
|
|
3501
|
+
fullPath = sysPath2.join(this.options.cwd, path28);
|
|
3049
3502
|
}
|
|
3050
3503
|
const now = /* @__PURE__ */ new Date();
|
|
3051
3504
|
const writes = this._pendingWrites;
|
|
3052
3505
|
function awaitWriteFinishFn(prevStat) {
|
|
3053
3506
|
statcb(fullPath, (err, curStat) => {
|
|
3054
|
-
if (err || !writes.has(
|
|
3507
|
+
if (err || !writes.has(path28)) {
|
|
3055
3508
|
if (err && err.code !== "ENOENT")
|
|
3056
3509
|
awfEmit(err);
|
|
3057
3510
|
return;
|
|
3058
3511
|
}
|
|
3059
3512
|
const now2 = Number(/* @__PURE__ */ new Date());
|
|
3060
3513
|
if (prevStat && curStat.size !== prevStat.size) {
|
|
3061
|
-
writes.get(
|
|
3514
|
+
writes.get(path28).lastChange = now2;
|
|
3062
3515
|
}
|
|
3063
|
-
const pw = writes.get(
|
|
3516
|
+
const pw = writes.get(path28);
|
|
3064
3517
|
const df = now2 - pw.lastChange;
|
|
3065
3518
|
if (df >= threshold) {
|
|
3066
|
-
writes.delete(
|
|
3519
|
+
writes.delete(path28);
|
|
3067
3520
|
awfEmit(void 0, curStat);
|
|
3068
3521
|
} else {
|
|
3069
3522
|
timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
|
|
3070
3523
|
}
|
|
3071
3524
|
});
|
|
3072
3525
|
}
|
|
3073
|
-
if (!writes.has(
|
|
3074
|
-
writes.set(
|
|
3526
|
+
if (!writes.has(path28)) {
|
|
3527
|
+
writes.set(path28, {
|
|
3075
3528
|
lastChange: now,
|
|
3076
3529
|
cancelWait: () => {
|
|
3077
|
-
writes.delete(
|
|
3530
|
+
writes.delete(path28);
|
|
3078
3531
|
clearTimeout(timeoutHandler);
|
|
3079
3532
|
return event;
|
|
3080
3533
|
}
|
|
@@ -3085,8 +3538,8 @@ var FSWatcher = class extends EventEmitter {
|
|
|
3085
3538
|
/**
|
|
3086
3539
|
* Determines whether user has asked to ignore this path.
|
|
3087
3540
|
*/
|
|
3088
|
-
_isIgnored(
|
|
3089
|
-
if (this.options.atomic && DOT_RE.test(
|
|
3541
|
+
_isIgnored(path28, stats) {
|
|
3542
|
+
if (this.options.atomic && DOT_RE.test(path28))
|
|
3090
3543
|
return true;
|
|
3091
3544
|
if (!this._userIgnored) {
|
|
3092
3545
|
const { cwd } = this.options;
|
|
@@ -3096,17 +3549,17 @@ var FSWatcher = class extends EventEmitter {
|
|
|
3096
3549
|
const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
|
|
3097
3550
|
this._userIgnored = anymatch(list, void 0);
|
|
3098
3551
|
}
|
|
3099
|
-
return this._userIgnored(
|
|
3552
|
+
return this._userIgnored(path28, stats);
|
|
3100
3553
|
}
|
|
3101
|
-
_isntIgnored(
|
|
3102
|
-
return !this._isIgnored(
|
|
3554
|
+
_isntIgnored(path28, stat4) {
|
|
3555
|
+
return !this._isIgnored(path28, stat4);
|
|
3103
3556
|
}
|
|
3104
3557
|
/**
|
|
3105
3558
|
* Provides a set of common helpers and properties relating to symlink handling.
|
|
3106
3559
|
* @param path file or directory pattern being watched
|
|
3107
3560
|
*/
|
|
3108
|
-
_getWatchHelpers(
|
|
3109
|
-
return new WatchHelper(
|
|
3561
|
+
_getWatchHelpers(path28) {
|
|
3562
|
+
return new WatchHelper(path28, this.options.followSymlinks, this);
|
|
3110
3563
|
}
|
|
3111
3564
|
// Directory helpers
|
|
3112
3565
|
// -----------------
|
|
@@ -3138,63 +3591,63 @@ var FSWatcher = class extends EventEmitter {
|
|
|
3138
3591
|
* @param item base path of item/directory
|
|
3139
3592
|
*/
|
|
3140
3593
|
_remove(directory, item, isDirectory) {
|
|
3141
|
-
const
|
|
3142
|
-
const fullPath = sysPath2.resolve(
|
|
3143
|
-
isDirectory = isDirectory != null ? isDirectory : this._watched.has(
|
|
3144
|
-
if (!this._throttle("remove",
|
|
3594
|
+
const path28 = sysPath2.join(directory, item);
|
|
3595
|
+
const fullPath = sysPath2.resolve(path28);
|
|
3596
|
+
isDirectory = isDirectory != null ? isDirectory : this._watched.has(path28) || this._watched.has(fullPath);
|
|
3597
|
+
if (!this._throttle("remove", path28, 100))
|
|
3145
3598
|
return;
|
|
3146
3599
|
if (!isDirectory && this._watched.size === 1) {
|
|
3147
3600
|
this.add(directory, item, true);
|
|
3148
3601
|
}
|
|
3149
|
-
const wp = this._getWatchedDir(
|
|
3602
|
+
const wp = this._getWatchedDir(path28);
|
|
3150
3603
|
const nestedDirectoryChildren = wp.getChildren();
|
|
3151
|
-
nestedDirectoryChildren.forEach((nested) => this._remove(
|
|
3604
|
+
nestedDirectoryChildren.forEach((nested) => this._remove(path28, nested));
|
|
3152
3605
|
const parent = this._getWatchedDir(directory);
|
|
3153
3606
|
const wasTracked = parent.has(item);
|
|
3154
3607
|
parent.remove(item);
|
|
3155
3608
|
if (this._symlinkPaths.has(fullPath)) {
|
|
3156
3609
|
this._symlinkPaths.delete(fullPath);
|
|
3157
3610
|
}
|
|
3158
|
-
let relPath =
|
|
3611
|
+
let relPath = path28;
|
|
3159
3612
|
if (this.options.cwd)
|
|
3160
|
-
relPath = sysPath2.relative(this.options.cwd,
|
|
3613
|
+
relPath = sysPath2.relative(this.options.cwd, path28);
|
|
3161
3614
|
if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
|
|
3162
3615
|
const event = this._pendingWrites.get(relPath).cancelWait();
|
|
3163
3616
|
if (event === EVENTS.ADD)
|
|
3164
3617
|
return;
|
|
3165
3618
|
}
|
|
3166
|
-
this._watched.delete(
|
|
3619
|
+
this._watched.delete(path28);
|
|
3167
3620
|
this._watched.delete(fullPath);
|
|
3168
3621
|
const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
|
|
3169
|
-
if (wasTracked && !this._isIgnored(
|
|
3170
|
-
this._emit(eventName,
|
|
3171
|
-
this._closePath(
|
|
3622
|
+
if (wasTracked && !this._isIgnored(path28))
|
|
3623
|
+
this._emit(eventName, path28);
|
|
3624
|
+
this._closePath(path28);
|
|
3172
3625
|
}
|
|
3173
3626
|
/**
|
|
3174
3627
|
* Closes all watchers for a path
|
|
3175
3628
|
*/
|
|
3176
|
-
_closePath(
|
|
3177
|
-
this._closeFile(
|
|
3178
|
-
const dir = sysPath2.dirname(
|
|
3179
|
-
this._getWatchedDir(dir).remove(sysPath2.basename(
|
|
3629
|
+
_closePath(path28) {
|
|
3630
|
+
this._closeFile(path28);
|
|
3631
|
+
const dir = sysPath2.dirname(path28);
|
|
3632
|
+
this._getWatchedDir(dir).remove(sysPath2.basename(path28));
|
|
3180
3633
|
}
|
|
3181
3634
|
/**
|
|
3182
3635
|
* Closes only file-specific watchers
|
|
3183
3636
|
*/
|
|
3184
|
-
_closeFile(
|
|
3185
|
-
const closers = this._closers.get(
|
|
3637
|
+
_closeFile(path28) {
|
|
3638
|
+
const closers = this._closers.get(path28);
|
|
3186
3639
|
if (!closers)
|
|
3187
3640
|
return;
|
|
3188
3641
|
closers.forEach((closer) => closer());
|
|
3189
|
-
this._closers.delete(
|
|
3642
|
+
this._closers.delete(path28);
|
|
3190
3643
|
}
|
|
3191
|
-
_addPathCloser(
|
|
3644
|
+
_addPathCloser(path28, closer) {
|
|
3192
3645
|
if (!closer)
|
|
3193
3646
|
return;
|
|
3194
|
-
let list = this._closers.get(
|
|
3647
|
+
let list = this._closers.get(path28);
|
|
3195
3648
|
if (!list) {
|
|
3196
3649
|
list = [];
|
|
3197
|
-
this._closers.set(
|
|
3650
|
+
this._closers.set(path28, list);
|
|
3198
3651
|
}
|
|
3199
3652
|
list.push(closer);
|
|
3200
3653
|
}
|
|
@@ -3229,54 +3682,132 @@ import path14 from "path";
|
|
|
3229
3682
|
function setupHotReload({
|
|
3230
3683
|
app,
|
|
3231
3684
|
appDir,
|
|
3685
|
+
projectRoot,
|
|
3232
3686
|
route = "/__fw/hot",
|
|
3233
3687
|
waitForBuild,
|
|
3234
3688
|
onFileChange
|
|
3235
3689
|
}) {
|
|
3236
3690
|
const clients = /* @__PURE__ */ new Set();
|
|
3237
3691
|
app.get(route, (req, res) => {
|
|
3238
|
-
res.setHeader("Content-Type", "text/event-stream");
|
|
3239
|
-
res.setHeader("Cache-Control", "no-cache");
|
|
3692
|
+
res.setHeader("Content-Type", "text/event-stream; charset=utf-8");
|
|
3693
|
+
res.setHeader("Cache-Control", "no-cache, no-transform");
|
|
3240
3694
|
res.setHeader("Connection", "keep-alive");
|
|
3241
|
-
res.
|
|
3242
|
-
res.
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3695
|
+
res.setHeader("X-Accel-Buffering", "no");
|
|
3696
|
+
if (res.flushHeaders) {
|
|
3697
|
+
res.flushHeaders();
|
|
3698
|
+
} else {
|
|
3699
|
+
res.writeHead(200, {
|
|
3700
|
+
"Content-Type": "text/event-stream; charset=utf-8",
|
|
3701
|
+
"Cache-Control": "no-cache, no-transform",
|
|
3702
|
+
"Connection": "keep-alive",
|
|
3703
|
+
"X-Accel-Buffering": "no"
|
|
3704
|
+
});
|
|
3705
|
+
}
|
|
3246
3706
|
clients.add(res);
|
|
3707
|
+
const pingMessage = "event: ping\ndata: connected\n\n";
|
|
3708
|
+
try {
|
|
3709
|
+
res.write(pingMessage, "utf-8");
|
|
3710
|
+
} catch (error) {
|
|
3711
|
+
console.error(`[hot-reload-server] \u274C Error sending ping:`, error);
|
|
3712
|
+
clients.delete(res);
|
|
3713
|
+
}
|
|
3247
3714
|
req.on("close", () => {
|
|
3248
3715
|
clients.delete(res);
|
|
3249
3716
|
});
|
|
3717
|
+
req.on("aborted", () => {
|
|
3718
|
+
clients.delete(res);
|
|
3719
|
+
});
|
|
3250
3720
|
});
|
|
3251
|
-
|
|
3721
|
+
console.log(`[hot-reload-server] \u2705 SSE endpoint registered at ${route}`);
|
|
3722
|
+
const resolvedProjectRoot = projectRoot ? path14.resolve(projectRoot) : path14.dirname(path14.resolve(appDir));
|
|
3723
|
+
const watcher = esm_default.watch(resolvedProjectRoot, {
|
|
3252
3724
|
ignoreInitial: true,
|
|
3253
|
-
ignored: [
|
|
3725
|
+
ignored: [
|
|
3726
|
+
"**/node_modules/**",
|
|
3727
|
+
`**/${BUILD_FOLDER_NAME}/**`,
|
|
3728
|
+
"**/.loly/**",
|
|
3729
|
+
// Ignore build output directory completely
|
|
3730
|
+
"**/.git/**",
|
|
3731
|
+
"**/dist/**",
|
|
3732
|
+
"**/build/**",
|
|
3733
|
+
"**/.next/**",
|
|
3734
|
+
"**/.cache/**",
|
|
3735
|
+
"**/.turbo/**",
|
|
3736
|
+
"**/.swc/**",
|
|
3737
|
+
"**/coverage/**",
|
|
3738
|
+
// Ignore generated files that trigger unnecessary reloads
|
|
3739
|
+
"**/*.map",
|
|
3740
|
+
// Source maps
|
|
3741
|
+
"**/*.log",
|
|
3742
|
+
// Log files
|
|
3743
|
+
"**/.DS_Store",
|
|
3744
|
+
// macOS
|
|
3745
|
+
"**/Thumbs.db"
|
|
3746
|
+
// Windows
|
|
3747
|
+
],
|
|
3748
|
+
persistent: true,
|
|
3749
|
+
ignorePermissionErrors: true,
|
|
3750
|
+
// Only watch relevant source files (TypeScript, JavaScript, CSS)
|
|
3751
|
+
// Filter out JSON files in build directories
|
|
3752
|
+
awaitWriteFinish: {
|
|
3753
|
+
stabilityThreshold: 150,
|
|
3754
|
+
// Wait 150ms after file change to trigger event (debounce)
|
|
3755
|
+
pollInterval: 50
|
|
3756
|
+
// Check every 50ms
|
|
3757
|
+
}
|
|
3254
3758
|
});
|
|
3759
|
+
let broadcastTimeout = null;
|
|
3760
|
+
const BROADCAST_DEBOUNCE_MS = 300;
|
|
3255
3761
|
async function broadcastReload(reason, filePath) {
|
|
3762
|
+
const normalizedPath = path14.normalize(filePath);
|
|
3763
|
+
if (normalizedPath.includes(BUILD_FOLDER_NAME) || normalizedPath.includes(".loly") || normalizedPath.endsWith(".map") || normalizedPath.endsWith(".log") || normalizedPath.includes("route-chunks.json") || normalizedPath.includes("routes-client.ts") || normalizedPath.includes("/client/route-")) {
|
|
3764
|
+
return;
|
|
3765
|
+
}
|
|
3256
3766
|
const rel = path14.relative(appDir, filePath);
|
|
3257
3767
|
console.log(`[hot-reload] ${reason}: ${rel}`);
|
|
3258
|
-
if (
|
|
3259
|
-
|
|
3260
|
-
await onFileChange(filePath);
|
|
3261
|
-
} catch (error) {
|
|
3262
|
-
console.warn("[hot-reload] Error in onFileChange callback:", error);
|
|
3263
|
-
}
|
|
3768
|
+
if (broadcastTimeout) {
|
|
3769
|
+
clearTimeout(broadcastTimeout);
|
|
3264
3770
|
}
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3771
|
+
broadcastTimeout = setTimeout(async () => {
|
|
3772
|
+
if (onFileChange) {
|
|
3773
|
+
try {
|
|
3774
|
+
await onFileChange(filePath);
|
|
3775
|
+
} catch (error) {
|
|
3776
|
+
console.warn("[hot-reload] Error in onFileChange callback:", error);
|
|
3777
|
+
}
|
|
3272
3778
|
}
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3779
|
+
if (waitForBuild) {
|
|
3780
|
+
try {
|
|
3781
|
+
console.log("[hot-reload] Waiting for client bundle to finish...");
|
|
3782
|
+
await waitForBuild();
|
|
3783
|
+
console.log("[hot-reload] Client bundle ready, sending reload event");
|
|
3784
|
+
} catch (error) {
|
|
3785
|
+
console.warn("[hot-reload] Error waiting for build:", error);
|
|
3786
|
+
}
|
|
3787
|
+
}
|
|
3788
|
+
const message = `event: message
|
|
3276
3789
|
data: reload:${rel}
|
|
3277
3790
|
|
|
3278
|
-
|
|
3279
|
-
|
|
3791
|
+
`;
|
|
3792
|
+
console.log(`[hot-reload-server] \u{1F4E4} Broadcasting reload event to ${clients.size} client(s)`);
|
|
3793
|
+
let sentCount = 0;
|
|
3794
|
+
for (const res of clients) {
|
|
3795
|
+
try {
|
|
3796
|
+
if (res.writableEnded || res.destroyed) {
|
|
3797
|
+
clients.delete(res);
|
|
3798
|
+
continue;
|
|
3799
|
+
}
|
|
3800
|
+
res.write(message, "utf-8");
|
|
3801
|
+
sentCount++;
|
|
3802
|
+
} catch (error) {
|
|
3803
|
+
console.error(`[hot-reload-server] \u2717 Error sending to client:`, error);
|
|
3804
|
+
clients.delete(res);
|
|
3805
|
+
}
|
|
3806
|
+
}
|
|
3807
|
+
if (sentCount > 0) {
|
|
3808
|
+
console.log(`[hot-reload-server] \u2705 Reload event sent to ${sentCount} client(s)`);
|
|
3809
|
+
}
|
|
3810
|
+
}, BROADCAST_DEBOUNCE_MS);
|
|
3280
3811
|
}
|
|
3281
3812
|
watcher.on("add", (filePath) => broadcastReload("add", filePath)).on("change", (filePath) => broadcastReload("change", filePath)).on("unlink", (filePath) => broadcastReload("unlink", filePath));
|
|
3282
3813
|
}
|
|
@@ -3352,6 +3883,121 @@ function deepMerge(target, source) {
|
|
|
3352
3883
|
}
|
|
3353
3884
|
return result;
|
|
3354
3885
|
}
|
|
3886
|
+
var ConfigValidationError = class extends Error {
|
|
3887
|
+
constructor(message, errors = []) {
|
|
3888
|
+
super(message);
|
|
3889
|
+
this.errors = errors;
|
|
3890
|
+
this.name = "ConfigValidationError";
|
|
3891
|
+
}
|
|
3892
|
+
};
|
|
3893
|
+
function validateConfig(config, projectRoot) {
|
|
3894
|
+
const errors = [];
|
|
3895
|
+
if (!config.directories.app || typeof config.directories.app !== "string") {
|
|
3896
|
+
errors.push("config.directories.app must be a non-empty string");
|
|
3897
|
+
} else {
|
|
3898
|
+
const appDir = path16.join(projectRoot, config.directories.app);
|
|
3899
|
+
if (!fs14.existsSync(appDir) && process.env.NODE_ENV !== "test") {
|
|
3900
|
+
errors.push(
|
|
3901
|
+
`App directory not found: ${config.directories.app}
|
|
3902
|
+
Expected at: ${appDir}
|
|
3903
|
+
\u{1F4A1} Suggestion: Create the directory or update config.directories.app`
|
|
3904
|
+
);
|
|
3905
|
+
}
|
|
3906
|
+
}
|
|
3907
|
+
if (!config.directories.build || typeof config.directories.build !== "string") {
|
|
3908
|
+
errors.push("config.directories.build must be a non-empty string");
|
|
3909
|
+
}
|
|
3910
|
+
if (!config.directories.static || typeof config.directories.static !== "string") {
|
|
3911
|
+
errors.push("config.directories.static must be a non-empty string");
|
|
3912
|
+
}
|
|
3913
|
+
const conventionKeys = ["page", "layout", "notFound", "error", "api"];
|
|
3914
|
+
for (const key of conventionKeys) {
|
|
3915
|
+
if (!config.conventions[key] || typeof config.conventions[key] !== "string") {
|
|
3916
|
+
errors.push(`config.conventions.${key} must be a non-empty string`);
|
|
3917
|
+
}
|
|
3918
|
+
}
|
|
3919
|
+
if (!["always", "never", "ignore"].includes(config.routing.trailingSlash)) {
|
|
3920
|
+
errors.push(
|
|
3921
|
+
`config.routing.trailingSlash must be 'always', 'never', or 'ignore'
|
|
3922
|
+
Received: ${JSON.stringify(config.routing.trailingSlash)}
|
|
3923
|
+
\u{1F4A1} Suggestion: Use one of the valid values: 'always' | 'never' | 'ignore'`
|
|
3924
|
+
);
|
|
3925
|
+
}
|
|
3926
|
+
if (typeof config.routing.caseSensitive !== "boolean") {
|
|
3927
|
+
errors.push("config.routing.caseSensitive must be a boolean");
|
|
3928
|
+
}
|
|
3929
|
+
if (typeof config.routing.basePath !== "string") {
|
|
3930
|
+
errors.push("config.routing.basePath must be a string");
|
|
3931
|
+
} else if (config.routing.basePath && !config.routing.basePath.startsWith("/")) {
|
|
3932
|
+
errors.push(
|
|
3933
|
+
`config.routing.basePath must start with '/' (if not empty)
|
|
3934
|
+
Received: ${JSON.stringify(config.routing.basePath)}
|
|
3935
|
+
\u{1F4A1} Suggestion: Use an empty string '' or a path starting with '/', e.g., '/api'`
|
|
3936
|
+
);
|
|
3937
|
+
}
|
|
3938
|
+
const validClientBundlers = ["rspack", "webpack", "vite"];
|
|
3939
|
+
if (!validClientBundlers.includes(config.build.clientBundler)) {
|
|
3940
|
+
errors.push(
|
|
3941
|
+
`config.build.clientBundler must be one of: ${validClientBundlers.join(", ")}
|
|
3942
|
+
Received: ${JSON.stringify(config.build.clientBundler)}`
|
|
3943
|
+
);
|
|
3944
|
+
}
|
|
3945
|
+
const validServerBundlers = ["esbuild", "tsup", "swc"];
|
|
3946
|
+
if (!validServerBundlers.includes(config.build.serverBundler)) {
|
|
3947
|
+
errors.push(
|
|
3948
|
+
`config.build.serverBundler must be one of: ${validServerBundlers.join(", ")}
|
|
3949
|
+
Received: ${JSON.stringify(config.build.serverBundler)}`
|
|
3950
|
+
);
|
|
3951
|
+
}
|
|
3952
|
+
if (!["cjs", "esm"].includes(config.build.outputFormat)) {
|
|
3953
|
+
errors.push(
|
|
3954
|
+
`config.build.outputFormat must be 'cjs' or 'esm'
|
|
3955
|
+
Received: ${JSON.stringify(config.build.outputFormat)}`
|
|
3956
|
+
);
|
|
3957
|
+
}
|
|
3958
|
+
const validAdapters = ["express", "fastify", "koa"];
|
|
3959
|
+
if (!validAdapters.includes(config.server.adapter)) {
|
|
3960
|
+
errors.push(
|
|
3961
|
+
`config.server.adapter must be one of: ${validAdapters.join(", ")}
|
|
3962
|
+
Received: ${JSON.stringify(config.server.adapter)}`
|
|
3963
|
+
);
|
|
3964
|
+
}
|
|
3965
|
+
if (typeof config.server.port !== "number" || config.server.port < 1 || config.server.port > 65535) {
|
|
3966
|
+
errors.push(
|
|
3967
|
+
`config.server.port must be a number between 1 and 65535
|
|
3968
|
+
Received: ${JSON.stringify(config.server.port)}`
|
|
3969
|
+
);
|
|
3970
|
+
}
|
|
3971
|
+
if (!config.server.host || typeof config.server.host !== "string") {
|
|
3972
|
+
errors.push("config.server.host must be a non-empty string");
|
|
3973
|
+
}
|
|
3974
|
+
const validFrameworks = ["react", "preact", "vue", "svelte"];
|
|
3975
|
+
if (!validFrameworks.includes(config.rendering.framework)) {
|
|
3976
|
+
errors.push(
|
|
3977
|
+
`config.rendering.framework must be one of: ${validFrameworks.join(", ")}
|
|
3978
|
+
Received: ${JSON.stringify(config.rendering.framework)}`
|
|
3979
|
+
);
|
|
3980
|
+
}
|
|
3981
|
+
if (typeof config.rendering.streaming !== "boolean") {
|
|
3982
|
+
errors.push("config.rendering.streaming must be a boolean");
|
|
3983
|
+
}
|
|
3984
|
+
if (typeof config.rendering.ssr !== "boolean") {
|
|
3985
|
+
errors.push("config.rendering.ssr must be a boolean");
|
|
3986
|
+
}
|
|
3987
|
+
if (typeof config.rendering.ssg !== "boolean") {
|
|
3988
|
+
errors.push("config.rendering.ssg must be a boolean");
|
|
3989
|
+
}
|
|
3990
|
+
if (errors.length > 0) {
|
|
3991
|
+
const errorMessage = [
|
|
3992
|
+
"\u274C Configuration validation failed:",
|
|
3993
|
+
"",
|
|
3994
|
+
...errors.map((err, i) => `${i + 1}. ${err}`),
|
|
3995
|
+
"",
|
|
3996
|
+
"\u{1F4A1} Please check your loly.config.ts file and fix the errors above."
|
|
3997
|
+
].join("\n");
|
|
3998
|
+
throw new ConfigValidationError(errorMessage, errors);
|
|
3999
|
+
}
|
|
4000
|
+
}
|
|
3355
4001
|
function loadConfig(projectRoot) {
|
|
3356
4002
|
const configFiles = [
|
|
3357
4003
|
path16.join(projectRoot, "loly.config.ts"),
|
|
@@ -3359,6 +4005,7 @@ function loadConfig(projectRoot) {
|
|
|
3359
4005
|
path16.join(projectRoot, "loly.config.json")
|
|
3360
4006
|
];
|
|
3361
4007
|
let userConfig = {};
|
|
4008
|
+
let loadedConfigFile = null;
|
|
3362
4009
|
for (const configFile of configFiles) {
|
|
3363
4010
|
if (fs14.existsSync(configFile)) {
|
|
3364
4011
|
try {
|
|
@@ -3372,16 +4019,31 @@ function loadConfig(projectRoot) {
|
|
|
3372
4019
|
const mod = __require(configFile);
|
|
3373
4020
|
userConfig = typeof mod.default === "function" ? mod.default(process.env.NODE_ENV) : mod.default || mod.config || mod;
|
|
3374
4021
|
}
|
|
4022
|
+
loadedConfigFile = path16.relative(projectRoot, configFile);
|
|
3375
4023
|
break;
|
|
3376
4024
|
} catch (error) {
|
|
3377
|
-
|
|
4025
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4026
|
+
throw new ConfigValidationError(
|
|
4027
|
+
`Failed to load configuration from ${path16.relative(projectRoot, configFile)}:
|
|
4028
|
+
${errorMessage}
|
|
4029
|
+
\u{1F4A1} Suggestion: Check that your config file exports a valid configuration object`
|
|
4030
|
+
);
|
|
3378
4031
|
}
|
|
3379
4032
|
}
|
|
3380
4033
|
}
|
|
3381
4034
|
const config = deepMerge(DEFAULT_CONFIG, userConfig);
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
4035
|
+
try {
|
|
4036
|
+
validateConfig(config, projectRoot);
|
|
4037
|
+
} catch (error) {
|
|
4038
|
+
if (error instanceof ConfigValidationError) {
|
|
4039
|
+
if (loadedConfigFile) {
|
|
4040
|
+
error.message = `[Configuration Error in ${loadedConfigFile}]
|
|
4041
|
+
|
|
4042
|
+
${error.message}`;
|
|
4043
|
+
}
|
|
4044
|
+
throw error;
|
|
4045
|
+
}
|
|
4046
|
+
throw error;
|
|
3385
4047
|
}
|
|
3386
4048
|
return config;
|
|
3387
4049
|
}
|
|
@@ -3398,14 +4060,14 @@ function getStaticDir(projectRoot, config) {
|
|
|
3398
4060
|
// modules/server/setup.ts
|
|
3399
4061
|
function setupServer(app, options) {
|
|
3400
4062
|
const { projectRoot, appDir, isDev, config } = options;
|
|
3401
|
-
const routeLoader = isDev ? new FilesystemRouteLoader(appDir) : new ManifestRouteLoader(projectRoot);
|
|
4063
|
+
const routeLoader = isDev ? new FilesystemRouteLoader(appDir, projectRoot) : new ManifestRouteLoader(projectRoot);
|
|
3402
4064
|
if (isDev) {
|
|
3403
4065
|
let getRoutes2 = function() {
|
|
3404
4066
|
clearAppRequireCache(appDir);
|
|
3405
|
-
|
|
4067
|
+
sharedLoader.invalidateCache();
|
|
3406
4068
|
return {
|
|
3407
|
-
routes:
|
|
3408
|
-
apiRoutes:
|
|
4069
|
+
routes: sharedLoader.loadRoutes(),
|
|
4070
|
+
apiRoutes: sharedLoader.loadApiRoutes()
|
|
3409
4071
|
};
|
|
3410
4072
|
};
|
|
3411
4073
|
var getRoutes = getRoutes2;
|
|
@@ -3419,19 +4081,20 @@ function setupServer(app, options) {
|
|
|
3419
4081
|
console.log(`[hot-reload] Cleared require cache for: ${rel}`);
|
|
3420
4082
|
}
|
|
3421
4083
|
if (isPageFile) {
|
|
3422
|
-
const loader = new FilesystemRouteLoader(appDir);
|
|
4084
|
+
const loader = new FilesystemRouteLoader(appDir, projectRoot);
|
|
3423
4085
|
const newRoutes = loader.loadRoutes();
|
|
3424
4086
|
writeClientRoutesManifest(newRoutes, projectRoot);
|
|
3425
4087
|
console.log("[hot-reload] Client routes manifest reloaded");
|
|
3426
4088
|
}
|
|
3427
4089
|
};
|
|
3428
|
-
setupHotReload({ app, appDir, waitForBuild, onFileChange });
|
|
4090
|
+
setupHotReload({ app, appDir, projectRoot, waitForBuild, onFileChange });
|
|
3429
4091
|
app.use("/static", express.static(outDir));
|
|
3430
4092
|
const routes = routeLoader.loadRoutes();
|
|
3431
4093
|
const wssRoutes = routeLoader.loadWssRoutes();
|
|
3432
4094
|
const notFoundPage = routeLoader.loadNotFoundRoute();
|
|
3433
4095
|
const errorPage = routeLoader.loadErrorRoute();
|
|
3434
4096
|
writeClientRoutesManifest(routes, projectRoot);
|
|
4097
|
+
const sharedLoader = new FilesystemRouteLoader(appDir, projectRoot);
|
|
3435
4098
|
return {
|
|
3436
4099
|
routes,
|
|
3437
4100
|
wssRoutes,
|
|
@@ -3519,104 +4182,24 @@ function sanitizeQuery(query) {
|
|
|
3519
4182
|
|
|
3520
4183
|
// modules/server/middleware/rate-limit.ts
|
|
3521
4184
|
import rateLimit from "express-rate-limit";
|
|
3522
|
-
|
|
4185
|
+
|
|
4186
|
+
// modules/logger/index.ts
|
|
4187
|
+
import pino from "pino";
|
|
4188
|
+
function createLogger(options = {}) {
|
|
3523
4189
|
const {
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
message: {
|
|
3538
|
-
error: message
|
|
3539
|
-
},
|
|
3540
|
-
standardHeaders,
|
|
3541
|
-
legacyHeaders,
|
|
3542
|
-
skipSuccessfulRequests,
|
|
3543
|
-
skipFailedRequests
|
|
3544
|
-
});
|
|
3545
|
-
}
|
|
3546
|
-
var defaultRateLimiter = createRateLimiter({
|
|
3547
|
-
windowMs: 15 * 60 * 1e3,
|
|
3548
|
-
// 15 minutes
|
|
3549
|
-
max: 100,
|
|
3550
|
-
message: "Too many requests from this IP, please try again later."
|
|
3551
|
-
});
|
|
3552
|
-
var strictRateLimiter = createRateLimiter({
|
|
3553
|
-
windowMs: 15 * 60 * 1e3,
|
|
3554
|
-
// 15 minutes
|
|
3555
|
-
max: 5,
|
|
3556
|
-
message: "Too many authentication attempts, please try again later."
|
|
3557
|
-
});
|
|
3558
|
-
var lenientRateLimiter = createRateLimiter({
|
|
3559
|
-
windowMs: 15 * 60 * 1e3,
|
|
3560
|
-
// 15 minutes
|
|
3561
|
-
max: 200,
|
|
3562
|
-
message: "Too many requests from this IP, please try again later."
|
|
3563
|
-
});
|
|
3564
|
-
|
|
3565
|
-
// modules/server/middleware/auto-rate-limit.ts
|
|
3566
|
-
function matchesStrictPattern(path25, patterns) {
|
|
3567
|
-
for (const pattern of patterns) {
|
|
3568
|
-
const regexPattern = pattern.replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*").replace(/\//g, "\\/");
|
|
3569
|
-
const regex = new RegExp(`^${regexPattern}$`);
|
|
3570
|
-
if (regex.test(path25)) {
|
|
3571
|
-
return true;
|
|
3572
|
-
}
|
|
3573
|
-
}
|
|
3574
|
-
return false;
|
|
3575
|
-
}
|
|
3576
|
-
function isRateLimiter(mw) {
|
|
3577
|
-
if (!mw) return false;
|
|
3578
|
-
if (mw === strictRateLimiter || mw === defaultRateLimiter || mw === lenientRateLimiter) {
|
|
3579
|
-
return true;
|
|
3580
|
-
}
|
|
3581
|
-
if (typeof mw === "function" && mw.name && mw.name.includes("rateLimit")) {
|
|
3582
|
-
return true;
|
|
3583
|
-
}
|
|
3584
|
-
if (mw && typeof mw === "function" && mw.skip || mw.resetKey) {
|
|
3585
|
-
return true;
|
|
3586
|
-
}
|
|
3587
|
-
return false;
|
|
3588
|
-
}
|
|
3589
|
-
function getAutoRateLimiter(route, strictPatterns = []) {
|
|
3590
|
-
const hasRateLimiter = route.middlewares?.some(isRateLimiter) || Object.values(route.methodMiddlewares || {}).some(
|
|
3591
|
-
(mws) => mws?.some(isRateLimiter)
|
|
3592
|
-
);
|
|
3593
|
-
if (hasRateLimiter) {
|
|
3594
|
-
return null;
|
|
3595
|
-
}
|
|
3596
|
-
if (strictPatterns.length > 0 && matchesStrictPattern(route.pattern, strictPatterns)) {
|
|
3597
|
-
console.log(`[rate-limit] Applying strict rate limiter to route: ${route.pattern}`);
|
|
3598
|
-
return strictRateLimiter;
|
|
3599
|
-
}
|
|
3600
|
-
return null;
|
|
3601
|
-
}
|
|
3602
|
-
|
|
3603
|
-
// modules/logger/index.ts
|
|
3604
|
-
import pino from "pino";
|
|
3605
|
-
function createLogger(options = {}) {
|
|
3606
|
-
const {
|
|
3607
|
-
level = process.env.LOG_LEVEL || (process.env.NODE_ENV === "development" ? "debug" : "info"),
|
|
3608
|
-
enabled = process.env.LOG_ENABLED !== "false",
|
|
3609
|
-
pretty = process.env.NODE_ENV === "development",
|
|
3610
|
-
destination
|
|
3611
|
-
} = options;
|
|
3612
|
-
if (!enabled) {
|
|
3613
|
-
return pino({ enabled: false });
|
|
3614
|
-
}
|
|
3615
|
-
const baseConfig = {
|
|
3616
|
-
level,
|
|
3617
|
-
base: {
|
|
3618
|
-
name: "@lolyjs/core",
|
|
3619
|
-
env: process.env.NODE_ENV || "development"
|
|
4190
|
+
level = process.env.LOG_LEVEL || (process.env.NODE_ENV === "development" ? "debug" : "info"),
|
|
4191
|
+
enabled = process.env.LOG_ENABLED !== "false",
|
|
4192
|
+
pretty = process.env.NODE_ENV === "development",
|
|
4193
|
+
destination
|
|
4194
|
+
} = options;
|
|
4195
|
+
if (!enabled) {
|
|
4196
|
+
return pino({ enabled: false });
|
|
4197
|
+
}
|
|
4198
|
+
const baseConfig = {
|
|
4199
|
+
level,
|
|
4200
|
+
base: {
|
|
4201
|
+
name: "@lolyjs/core",
|
|
4202
|
+
env: process.env.NODE_ENV || "development"
|
|
3620
4203
|
},
|
|
3621
4204
|
timestamp: pino.stdTimeFunctions.isoTime,
|
|
3622
4205
|
formatters: {
|
|
@@ -3658,8 +4241,8 @@ function resetLogger() {
|
|
|
3658
4241
|
loggerInstance = null;
|
|
3659
4242
|
}
|
|
3660
4243
|
var Logger = class _Logger {
|
|
3661
|
-
constructor(
|
|
3662
|
-
this.pino =
|
|
4244
|
+
constructor(logger5, context = {}) {
|
|
4245
|
+
this.pino = logger5 || getLogger();
|
|
3663
4246
|
this.context = context;
|
|
3664
4247
|
}
|
|
3665
4248
|
/**
|
|
@@ -3736,12 +4319,12 @@ var DEFAULT_IGNORED_PATHS = [
|
|
|
3736
4319
|
/^\/sockjs-node/
|
|
3737
4320
|
// Hot reload websocket
|
|
3738
4321
|
];
|
|
3739
|
-
function shouldIgnorePath(
|
|
4322
|
+
function shouldIgnorePath(path28, ignoredPaths) {
|
|
3740
4323
|
return ignoredPaths.some((pattern) => {
|
|
3741
4324
|
if (typeof pattern === "string") {
|
|
3742
|
-
return
|
|
4325
|
+
return path28 === pattern || path28.startsWith(pattern);
|
|
3743
4326
|
}
|
|
3744
|
-
return pattern.test(
|
|
4327
|
+
return pattern.test(path28);
|
|
3745
4328
|
});
|
|
3746
4329
|
}
|
|
3747
4330
|
function requestLoggerMiddleware(options = {}) {
|
|
@@ -3792,6 +4375,151 @@ function getRequestLogger(req) {
|
|
|
3792
4375
|
return req.logger || logger.child({ requestId: "unknown" });
|
|
3793
4376
|
}
|
|
3794
4377
|
|
|
4378
|
+
// modules/server/middleware/rate-limit.ts
|
|
4379
|
+
var logger2 = createModuleLogger("rate-limit");
|
|
4380
|
+
function validateRateLimitConfig(config) {
|
|
4381
|
+
if (config.windowMs !== void 0 && (config.windowMs < 1e3 || !Number.isInteger(config.windowMs))) {
|
|
4382
|
+
throw new Error(
|
|
4383
|
+
`Invalid rateLimit.windowMs: ${config.windowMs}. Must be an integer >= 1000 (milliseconds)`
|
|
4384
|
+
);
|
|
4385
|
+
}
|
|
4386
|
+
if (config.max !== void 0 && (config.max < 1 || !Number.isInteger(config.max))) {
|
|
4387
|
+
throw new Error(
|
|
4388
|
+
`Invalid rateLimit.max: ${config.max}. Must be an integer >= 1`
|
|
4389
|
+
);
|
|
4390
|
+
}
|
|
4391
|
+
}
|
|
4392
|
+
function createRateLimiter(config = {}) {
|
|
4393
|
+
validateRateLimitConfig(config);
|
|
4394
|
+
const {
|
|
4395
|
+
windowMs = 15 * 60 * 1e3,
|
|
4396
|
+
// 15 minutes
|
|
4397
|
+
max = 100,
|
|
4398
|
+
// limit each IP to 100 requests per windowMs
|
|
4399
|
+
message = "Too many requests from this IP, please try again later.",
|
|
4400
|
+
standardHeaders = true,
|
|
4401
|
+
legacyHeaders = false,
|
|
4402
|
+
skipSuccessfulRequests = false,
|
|
4403
|
+
skipFailedRequests = false,
|
|
4404
|
+
keyGenerator,
|
|
4405
|
+
skip
|
|
4406
|
+
} = config;
|
|
4407
|
+
const limiter = rateLimit({
|
|
4408
|
+
windowMs,
|
|
4409
|
+
max,
|
|
4410
|
+
message: {
|
|
4411
|
+
error: message,
|
|
4412
|
+
retryAfter: Math.ceil(windowMs / 1e3)
|
|
4413
|
+
// seconds until retry
|
|
4414
|
+
},
|
|
4415
|
+
standardHeaders,
|
|
4416
|
+
legacyHeaders,
|
|
4417
|
+
skipSuccessfulRequests,
|
|
4418
|
+
skipFailedRequests,
|
|
4419
|
+
keyGenerator,
|
|
4420
|
+
skip
|
|
4421
|
+
});
|
|
4422
|
+
const wrappedLimiter = (req, res, next) => {
|
|
4423
|
+
limiter(req, res, (err) => {
|
|
4424
|
+
if (err && res.statusCode === 429) {
|
|
4425
|
+
const ip = req.ip || req.connection?.remoteAddress || "unknown";
|
|
4426
|
+
logger2.warn("Rate limit exceeded", {
|
|
4427
|
+
ip,
|
|
4428
|
+
path: req.path,
|
|
4429
|
+
method: req.method,
|
|
4430
|
+
limit: max,
|
|
4431
|
+
windowMs,
|
|
4432
|
+
retryAfter: Math.ceil(windowMs / 1e3)
|
|
4433
|
+
});
|
|
4434
|
+
}
|
|
4435
|
+
if (err) {
|
|
4436
|
+
return next(err);
|
|
4437
|
+
}
|
|
4438
|
+
next();
|
|
4439
|
+
});
|
|
4440
|
+
};
|
|
4441
|
+
Object.setPrototypeOf(wrappedLimiter, limiter);
|
|
4442
|
+
Object.assign(wrappedLimiter, limiter);
|
|
4443
|
+
return wrappedLimiter;
|
|
4444
|
+
}
|
|
4445
|
+
var defaultRateLimiter = createRateLimiter({
|
|
4446
|
+
windowMs: 15 * 60 * 1e3,
|
|
4447
|
+
// 15 minutes
|
|
4448
|
+
max: 100,
|
|
4449
|
+
message: "Too many requests from this IP, please try again later."
|
|
4450
|
+
});
|
|
4451
|
+
var strictRateLimiter = createRateLimiter({
|
|
4452
|
+
windowMs: 15 * 60 * 1e3,
|
|
4453
|
+
// 15 minutes
|
|
4454
|
+
max: 5,
|
|
4455
|
+
message: "Too many authentication attempts, please try again later."
|
|
4456
|
+
});
|
|
4457
|
+
var lenientRateLimiter = createRateLimiter({
|
|
4458
|
+
windowMs: 15 * 60 * 1e3,
|
|
4459
|
+
// 15 minutes
|
|
4460
|
+
max: 200,
|
|
4461
|
+
message: "Too many requests from this IP, please try again later."
|
|
4462
|
+
});
|
|
4463
|
+
function createRateLimiterFromConfig(config, useApiMax = false) {
|
|
4464
|
+
if (!config) return null;
|
|
4465
|
+
const max = useApiMax ? config.apiMax ?? config.max ?? 100 : config.max ?? 100;
|
|
4466
|
+
const windowMs = config.windowMs ?? 15 * 60 * 1e3;
|
|
4467
|
+
return createRateLimiter({
|
|
4468
|
+
windowMs,
|
|
4469
|
+
max,
|
|
4470
|
+
message: `Too many requests from this IP, please try again after ${Math.ceil(windowMs / 1e3)} seconds.`
|
|
4471
|
+
});
|
|
4472
|
+
}
|
|
4473
|
+
function createStrictRateLimiterFromConfig(config) {
|
|
4474
|
+
if (!config || config.strictMax === void 0) {
|
|
4475
|
+
return strictRateLimiter;
|
|
4476
|
+
}
|
|
4477
|
+
const windowMs = config.windowMs ?? 15 * 60 * 1e3;
|
|
4478
|
+
return createRateLimiter({
|
|
4479
|
+
windowMs,
|
|
4480
|
+
max: config.strictMax,
|
|
4481
|
+
message: `Too many authentication attempts, please try again after ${Math.ceil(windowMs / 1e3)} seconds.`
|
|
4482
|
+
});
|
|
4483
|
+
}
|
|
4484
|
+
|
|
4485
|
+
// modules/server/middleware/auto-rate-limit.ts
|
|
4486
|
+
function matchesStrictPattern(path28, patterns) {
|
|
4487
|
+
for (const pattern of patterns) {
|
|
4488
|
+
const regexPattern = pattern.replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*").replace(/\//g, "\\/");
|
|
4489
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
4490
|
+
if (regex.test(path28)) {
|
|
4491
|
+
return true;
|
|
4492
|
+
}
|
|
4493
|
+
}
|
|
4494
|
+
return false;
|
|
4495
|
+
}
|
|
4496
|
+
function isRateLimiter(mw) {
|
|
4497
|
+
if (!mw) return false;
|
|
4498
|
+
if (mw === strictRateLimiter || mw === defaultRateLimiter || mw === lenientRateLimiter) {
|
|
4499
|
+
return true;
|
|
4500
|
+
}
|
|
4501
|
+
if (typeof mw === "function" && mw.name && mw.name.includes("rateLimit")) {
|
|
4502
|
+
return true;
|
|
4503
|
+
}
|
|
4504
|
+
if (mw && typeof mw === "function" && mw.skip || mw.resetKey) {
|
|
4505
|
+
return true;
|
|
4506
|
+
}
|
|
4507
|
+
return false;
|
|
4508
|
+
}
|
|
4509
|
+
function getAutoRateLimiter(route, strictPatterns = [], rateLimitConfig) {
|
|
4510
|
+
const hasRateLimiter = route.middlewares?.some(isRateLimiter) || Object.values(route.methodMiddlewares || {}).some(
|
|
4511
|
+
(mws) => mws?.some(isRateLimiter)
|
|
4512
|
+
);
|
|
4513
|
+
if (hasRateLimiter) {
|
|
4514
|
+
return null;
|
|
4515
|
+
}
|
|
4516
|
+
if (strictPatterns.length > 0 && matchesStrictPattern(route.pattern, strictPatterns)) {
|
|
4517
|
+
const limiter = rateLimitConfig ? createStrictRateLimiterFromConfig(rateLimitConfig) : strictRateLimiter;
|
|
4518
|
+
return limiter;
|
|
4519
|
+
}
|
|
4520
|
+
return null;
|
|
4521
|
+
}
|
|
4522
|
+
|
|
3795
4523
|
// modules/server/handlers/api.ts
|
|
3796
4524
|
async function handleApiRequest(options) {
|
|
3797
4525
|
const { apiRoutes, urlPath, req, res, env = "dev" } = options;
|
|
@@ -3823,7 +4551,8 @@ async function handleApiRequest(options) {
|
|
|
3823
4551
|
try {
|
|
3824
4552
|
const autoRateLimiter = getAutoRateLimiter(
|
|
3825
4553
|
route,
|
|
3826
|
-
options.strictRateLimitPatterns
|
|
4554
|
+
options.strictRateLimitPatterns,
|
|
4555
|
+
options.rateLimitConfig
|
|
3827
4556
|
);
|
|
3828
4557
|
const reqLogger = getRequestLogger(req);
|
|
3829
4558
|
if (autoRateLimiter) {
|
|
@@ -3835,26 +4564,46 @@ async function handleApiRequest(options) {
|
|
|
3835
4564
|
const globalMws = route.middlewares ?? [];
|
|
3836
4565
|
const perMethodMws = route.methodMiddlewares?.[method] ?? [];
|
|
3837
4566
|
const chain = autoRateLimiter ? [autoRateLimiter, ...globalMws, ...perMethodMws] : [...globalMws, ...perMethodMws];
|
|
3838
|
-
for (
|
|
3839
|
-
const
|
|
3840
|
-
if (
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
4567
|
+
for (let i = 0; i < chain.length; i++) {
|
|
4568
|
+
const mw = chain[i];
|
|
4569
|
+
if (typeof mw !== "function") {
|
|
4570
|
+
reqLogger.warn("Invalid middleware in chain", {
|
|
4571
|
+
route: route.pattern,
|
|
4572
|
+
method,
|
|
4573
|
+
middlewareIndex: i,
|
|
4574
|
+
middlewareType: typeof mw
|
|
4575
|
+
});
|
|
4576
|
+
continue;
|
|
4577
|
+
}
|
|
4578
|
+
try {
|
|
4579
|
+
const isExpressRateLimit = mw.skip || mw.resetKey || mw.name?.includes("rateLimit");
|
|
4580
|
+
if (isExpressRateLimit) {
|
|
4581
|
+
await new Promise((resolve3, reject) => {
|
|
4582
|
+
const next = (err) => {
|
|
4583
|
+
if (err) reject(err);
|
|
4584
|
+
else resolve3();
|
|
4585
|
+
};
|
|
4586
|
+
try {
|
|
4587
|
+
const result = mw(req, res, next);
|
|
4588
|
+
if (result && typeof result.then === "function") {
|
|
4589
|
+
result.then(() => resolve3()).catch(reject);
|
|
4590
|
+
}
|
|
4591
|
+
} catch (err) {
|
|
4592
|
+
reject(err);
|
|
3850
4593
|
}
|
|
3851
|
-
}
|
|
3852
|
-
|
|
3853
|
-
|
|
4594
|
+
});
|
|
4595
|
+
} else {
|
|
4596
|
+
await Promise.resolve(mw(ctx, async () => {
|
|
4597
|
+
}));
|
|
4598
|
+
}
|
|
4599
|
+
} catch (error) {
|
|
4600
|
+
reqLogger.error("API middleware failed", error instanceof Error ? error : new Error(String(error)), {
|
|
4601
|
+
route: route.pattern,
|
|
4602
|
+
method,
|
|
4603
|
+
middlewareIndex: i,
|
|
4604
|
+
middlewareName: mw.name || "anonymous"
|
|
3854
4605
|
});
|
|
3855
|
-
|
|
3856
|
-
await Promise.resolve(mw(ctx, async () => {
|
|
3857
|
-
}));
|
|
4606
|
+
throw error;
|
|
3858
4607
|
}
|
|
3859
4608
|
if (res.headersSent) {
|
|
3860
4609
|
return;
|
|
@@ -3905,42 +4654,289 @@ function createDocumentTree(options) {
|
|
|
3905
4654
|
titleFallback,
|
|
3906
4655
|
descriptionFallback,
|
|
3907
4656
|
chunkHref,
|
|
4657
|
+
entrypointFiles = [],
|
|
3908
4658
|
theme,
|
|
3909
4659
|
clientJsPath = "/static/client.js",
|
|
3910
4660
|
clientCssPath = "/static/client.css",
|
|
3911
|
-
nonce
|
|
4661
|
+
nonce,
|
|
4662
|
+
includeInlineScripts = true
|
|
4663
|
+
// Default true - scripts inline in body for both SSR and SSG
|
|
3912
4664
|
} = options;
|
|
3913
|
-
const metaObj = meta ??
|
|
3914
|
-
const title = metaObj
|
|
3915
|
-
const lang = metaObj
|
|
3916
|
-
const description = metaObj
|
|
4665
|
+
const metaObj = meta ?? null;
|
|
4666
|
+
const title = metaObj?.title ?? titleFallback ?? "My Framework Dev";
|
|
4667
|
+
const lang = metaObj?.lang ?? "en";
|
|
4668
|
+
const description = metaObj?.description ?? descriptionFallback ?? "Demo Loly framework";
|
|
3917
4669
|
const extraMetaTags = [];
|
|
4670
|
+
const linkTags = [];
|
|
3918
4671
|
if (description) {
|
|
3919
4672
|
extraMetaTags.push(
|
|
3920
4673
|
React.createElement("meta", {
|
|
4674
|
+
key: "meta-description",
|
|
3921
4675
|
name: "description",
|
|
3922
4676
|
content: description
|
|
3923
4677
|
})
|
|
3924
4678
|
);
|
|
3925
4679
|
}
|
|
3926
|
-
if (
|
|
3927
|
-
|
|
4680
|
+
if (metaObj?.robots) {
|
|
4681
|
+
extraMetaTags.push(
|
|
4682
|
+
React.createElement("meta", {
|
|
4683
|
+
key: "meta-robots",
|
|
4684
|
+
name: "robots",
|
|
4685
|
+
content: metaObj.robots
|
|
4686
|
+
})
|
|
4687
|
+
);
|
|
4688
|
+
}
|
|
4689
|
+
if (metaObj?.themeColor) {
|
|
4690
|
+
extraMetaTags.push(
|
|
4691
|
+
React.createElement("meta", {
|
|
4692
|
+
key: "meta-theme-color",
|
|
4693
|
+
name: "theme-color",
|
|
4694
|
+
content: metaObj.themeColor
|
|
4695
|
+
})
|
|
4696
|
+
);
|
|
4697
|
+
}
|
|
4698
|
+
if (metaObj?.viewport) {
|
|
4699
|
+
extraMetaTags.push(
|
|
4700
|
+
React.createElement("meta", {
|
|
4701
|
+
key: "meta-viewport",
|
|
4702
|
+
name: "viewport",
|
|
4703
|
+
content: metaObj.viewport
|
|
4704
|
+
})
|
|
4705
|
+
);
|
|
4706
|
+
}
|
|
4707
|
+
if (metaObj?.canonical) {
|
|
4708
|
+
linkTags.push(
|
|
4709
|
+
React.createElement("link", {
|
|
4710
|
+
key: "link-canonical",
|
|
4711
|
+
rel: "canonical",
|
|
4712
|
+
href: metaObj.canonical
|
|
4713
|
+
})
|
|
4714
|
+
);
|
|
4715
|
+
}
|
|
4716
|
+
if (metaObj?.openGraph) {
|
|
4717
|
+
const og = metaObj.openGraph;
|
|
4718
|
+
if (og.title) {
|
|
4719
|
+
extraMetaTags.push(
|
|
4720
|
+
React.createElement("meta", {
|
|
4721
|
+
key: "og-title",
|
|
4722
|
+
property: "og:title",
|
|
4723
|
+
content: og.title
|
|
4724
|
+
})
|
|
4725
|
+
);
|
|
4726
|
+
}
|
|
4727
|
+
if (og.description) {
|
|
4728
|
+
extraMetaTags.push(
|
|
4729
|
+
React.createElement("meta", {
|
|
4730
|
+
key: "og-description",
|
|
4731
|
+
property: "og:description",
|
|
4732
|
+
content: og.description
|
|
4733
|
+
})
|
|
4734
|
+
);
|
|
4735
|
+
}
|
|
4736
|
+
if (og.type) {
|
|
4737
|
+
extraMetaTags.push(
|
|
4738
|
+
React.createElement("meta", {
|
|
4739
|
+
key: "og-type",
|
|
4740
|
+
property: "og:type",
|
|
4741
|
+
content: og.type
|
|
4742
|
+
})
|
|
4743
|
+
);
|
|
4744
|
+
}
|
|
4745
|
+
if (og.url) {
|
|
4746
|
+
extraMetaTags.push(
|
|
4747
|
+
React.createElement("meta", {
|
|
4748
|
+
key: "og-url",
|
|
4749
|
+
property: "og:url",
|
|
4750
|
+
content: og.url
|
|
4751
|
+
})
|
|
4752
|
+
);
|
|
4753
|
+
}
|
|
4754
|
+
if (og.image) {
|
|
4755
|
+
if (typeof og.image === "string") {
|
|
4756
|
+
extraMetaTags.push(
|
|
4757
|
+
React.createElement("meta", {
|
|
4758
|
+
key: "og-image",
|
|
4759
|
+
property: "og:image",
|
|
4760
|
+
content: og.image
|
|
4761
|
+
})
|
|
4762
|
+
);
|
|
4763
|
+
} else {
|
|
4764
|
+
extraMetaTags.push(
|
|
4765
|
+
React.createElement("meta", {
|
|
4766
|
+
key: "og-image",
|
|
4767
|
+
property: "og:image",
|
|
4768
|
+
content: og.image.url
|
|
4769
|
+
})
|
|
4770
|
+
);
|
|
4771
|
+
if (og.image.width) {
|
|
4772
|
+
extraMetaTags.push(
|
|
4773
|
+
React.createElement("meta", {
|
|
4774
|
+
key: "og-image-width",
|
|
4775
|
+
property: "og:image:width",
|
|
4776
|
+
content: String(og.image.width)
|
|
4777
|
+
})
|
|
4778
|
+
);
|
|
4779
|
+
}
|
|
4780
|
+
if (og.image.height) {
|
|
4781
|
+
extraMetaTags.push(
|
|
4782
|
+
React.createElement("meta", {
|
|
4783
|
+
key: "og-image-height",
|
|
4784
|
+
property: "og:image:height",
|
|
4785
|
+
content: String(og.image.height)
|
|
4786
|
+
})
|
|
4787
|
+
);
|
|
4788
|
+
}
|
|
4789
|
+
if (og.image.alt) {
|
|
4790
|
+
extraMetaTags.push(
|
|
4791
|
+
React.createElement("meta", {
|
|
4792
|
+
key: "og-image-alt",
|
|
4793
|
+
property: "og:image:alt",
|
|
4794
|
+
content: og.image.alt
|
|
4795
|
+
})
|
|
4796
|
+
);
|
|
4797
|
+
}
|
|
4798
|
+
}
|
|
4799
|
+
}
|
|
4800
|
+
if (og.siteName) {
|
|
4801
|
+
extraMetaTags.push(
|
|
4802
|
+
React.createElement("meta", {
|
|
4803
|
+
key: "og-site-name",
|
|
4804
|
+
property: "og:site_name",
|
|
4805
|
+
content: og.siteName
|
|
4806
|
+
})
|
|
4807
|
+
);
|
|
4808
|
+
}
|
|
4809
|
+
if (og.locale) {
|
|
4810
|
+
extraMetaTags.push(
|
|
4811
|
+
React.createElement("meta", {
|
|
4812
|
+
key: "og-locale",
|
|
4813
|
+
property: "og:locale",
|
|
4814
|
+
content: og.locale
|
|
4815
|
+
})
|
|
4816
|
+
);
|
|
4817
|
+
}
|
|
4818
|
+
}
|
|
4819
|
+
if (metaObj?.twitter) {
|
|
4820
|
+
const twitter = metaObj.twitter;
|
|
4821
|
+
if (twitter.card) {
|
|
3928
4822
|
extraMetaTags.push(
|
|
3929
4823
|
React.createElement("meta", {
|
|
4824
|
+
key: "twitter-card",
|
|
4825
|
+
name: "twitter:card",
|
|
4826
|
+
content: twitter.card
|
|
4827
|
+
})
|
|
4828
|
+
);
|
|
4829
|
+
}
|
|
4830
|
+
if (twitter.title) {
|
|
4831
|
+
extraMetaTags.push(
|
|
4832
|
+
React.createElement("meta", {
|
|
4833
|
+
key: "twitter-title",
|
|
4834
|
+
name: "twitter:title",
|
|
4835
|
+
content: twitter.title
|
|
4836
|
+
})
|
|
4837
|
+
);
|
|
4838
|
+
}
|
|
4839
|
+
if (twitter.description) {
|
|
4840
|
+
extraMetaTags.push(
|
|
4841
|
+
React.createElement("meta", {
|
|
4842
|
+
key: "twitter-description",
|
|
4843
|
+
name: "twitter:description",
|
|
4844
|
+
content: twitter.description
|
|
4845
|
+
})
|
|
4846
|
+
);
|
|
4847
|
+
}
|
|
4848
|
+
if (twitter.image) {
|
|
4849
|
+
extraMetaTags.push(
|
|
4850
|
+
React.createElement("meta", {
|
|
4851
|
+
key: "twitter-image",
|
|
4852
|
+
name: "twitter:image",
|
|
4853
|
+
content: twitter.image
|
|
4854
|
+
})
|
|
4855
|
+
);
|
|
4856
|
+
}
|
|
4857
|
+
if (twitter.imageAlt) {
|
|
4858
|
+
extraMetaTags.push(
|
|
4859
|
+
React.createElement("meta", {
|
|
4860
|
+
key: "twitter-image-alt",
|
|
4861
|
+
name: "twitter:image:alt",
|
|
4862
|
+
content: twitter.imageAlt
|
|
4863
|
+
})
|
|
4864
|
+
);
|
|
4865
|
+
}
|
|
4866
|
+
if (twitter.site) {
|
|
4867
|
+
extraMetaTags.push(
|
|
4868
|
+
React.createElement("meta", {
|
|
4869
|
+
key: "twitter-site",
|
|
4870
|
+
name: "twitter:site",
|
|
4871
|
+
content: twitter.site
|
|
4872
|
+
})
|
|
4873
|
+
);
|
|
4874
|
+
}
|
|
4875
|
+
if (twitter.creator) {
|
|
4876
|
+
extraMetaTags.push(
|
|
4877
|
+
React.createElement("meta", {
|
|
4878
|
+
key: "twitter-creator",
|
|
4879
|
+
name: "twitter:creator",
|
|
4880
|
+
content: twitter.creator
|
|
4881
|
+
})
|
|
4882
|
+
);
|
|
4883
|
+
}
|
|
4884
|
+
}
|
|
4885
|
+
if (metaObj?.metaTags && Array.isArray(metaObj.metaTags)) {
|
|
4886
|
+
metaObj.metaTags.forEach((tag, index) => {
|
|
4887
|
+
extraMetaTags.push(
|
|
4888
|
+
React.createElement("meta", {
|
|
4889
|
+
key: `meta-custom-${index}`,
|
|
3930
4890
|
name: tag.name,
|
|
3931
4891
|
property: tag.property,
|
|
4892
|
+
httpEquiv: tag.httpEquiv,
|
|
3932
4893
|
content: tag.content
|
|
3933
4894
|
})
|
|
3934
4895
|
);
|
|
3935
|
-
}
|
|
4896
|
+
});
|
|
4897
|
+
}
|
|
4898
|
+
if (metaObj?.links && Array.isArray(metaObj.links)) {
|
|
4899
|
+
metaObj.links.forEach((link, index) => {
|
|
4900
|
+
linkTags.push(
|
|
4901
|
+
React.createElement("link", {
|
|
4902
|
+
key: `link-custom-${index}`,
|
|
4903
|
+
rel: link.rel,
|
|
4904
|
+
href: link.href,
|
|
4905
|
+
as: link.as,
|
|
4906
|
+
crossOrigin: link.crossorigin,
|
|
4907
|
+
type: link.type
|
|
4908
|
+
})
|
|
4909
|
+
);
|
|
4910
|
+
});
|
|
4911
|
+
}
|
|
4912
|
+
const serialized = JSON.stringify({
|
|
4913
|
+
...initialData,
|
|
4914
|
+
theme
|
|
4915
|
+
});
|
|
4916
|
+
const routerSerialized = JSON.stringify({
|
|
4917
|
+
...routerData
|
|
4918
|
+
});
|
|
4919
|
+
const bodyChildren = [
|
|
4920
|
+
React.createElement("div", { id: APP_CONTAINER_ID }, appTree)
|
|
4921
|
+
];
|
|
4922
|
+
if (includeInlineScripts) {
|
|
4923
|
+
bodyChildren.push(
|
|
4924
|
+
React.createElement("script", {
|
|
4925
|
+
key: "initial-data",
|
|
4926
|
+
nonce,
|
|
4927
|
+
dangerouslySetInnerHTML: {
|
|
4928
|
+
__html: `window.${WINDOW_DATA_KEY} = ${serialized};`
|
|
4929
|
+
}
|
|
4930
|
+
}),
|
|
4931
|
+
React.createElement("script", {
|
|
4932
|
+
key: "router-data",
|
|
4933
|
+
nonce,
|
|
4934
|
+
dangerouslySetInnerHTML: {
|
|
4935
|
+
__html: `window.${ROUTER_DATA_KEY} = ${routerSerialized};`
|
|
4936
|
+
}
|
|
4937
|
+
})
|
|
4938
|
+
);
|
|
3936
4939
|
}
|
|
3937
|
-
const serialized = JSON.stringify({
|
|
3938
|
-
...initialData,
|
|
3939
|
-
theme
|
|
3940
|
-
});
|
|
3941
|
-
const routerSerialized = JSON.stringify({
|
|
3942
|
-
...routerData
|
|
3943
|
-
});
|
|
3944
4940
|
const documentTree = React.createElement(
|
|
3945
4941
|
"html",
|
|
3946
4942
|
{ lang },
|
|
@@ -3949,13 +4945,25 @@ function createDocumentTree(options) {
|
|
|
3949
4945
|
null,
|
|
3950
4946
|
React.createElement("meta", { charSet: "utf-8" }),
|
|
3951
4947
|
React.createElement("title", null, title),
|
|
4948
|
+
// Viewport: use custom if provided, otherwise default
|
|
3952
4949
|
React.createElement("meta", {
|
|
3953
4950
|
name: "viewport",
|
|
3954
|
-
content: "width=device-width, initial-scale=1"
|
|
4951
|
+
content: metaObj?.viewport ?? "width=device-width, initial-scale=1"
|
|
3955
4952
|
}),
|
|
3956
4953
|
...extraMetaTags,
|
|
4954
|
+
...linkTags,
|
|
4955
|
+
...entrypointFiles.length > 0 ? entrypointFiles.slice(0, -1).map(
|
|
4956
|
+
(file) => React.createElement("link", {
|
|
4957
|
+
key: `preload-${file}`,
|
|
4958
|
+
rel: "preload",
|
|
4959
|
+
href: file,
|
|
4960
|
+
as: "script"
|
|
4961
|
+
})
|
|
4962
|
+
) : [],
|
|
4963
|
+
// Preload route-specific chunk if available
|
|
3957
4964
|
chunkHref && React.createElement("link", {
|
|
3958
|
-
|
|
4965
|
+
key: `preload-${chunkHref}`,
|
|
4966
|
+
rel: "preload",
|
|
3959
4967
|
href: chunkHref,
|
|
3960
4968
|
as: "script"
|
|
3961
4969
|
}),
|
|
@@ -3968,33 +4976,34 @@ function createDocumentTree(options) {
|
|
|
3968
4976
|
rel: "stylesheet",
|
|
3969
4977
|
href: clientCssPath
|
|
3970
4978
|
}),
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
4979
|
+
...entrypointFiles.length > 0 ? entrypointFiles.map(
|
|
4980
|
+
(file) => React.createElement("script", {
|
|
4981
|
+
key: file,
|
|
4982
|
+
src: file,
|
|
4983
|
+
defer: true,
|
|
4984
|
+
nonce
|
|
4985
|
+
// CSP nonce for external scripts
|
|
4986
|
+
})
|
|
4987
|
+
) : [
|
|
4988
|
+
React.createElement("script", {
|
|
4989
|
+
key: "client",
|
|
4990
|
+
src: clientJsPath,
|
|
4991
|
+
defer: true,
|
|
4992
|
+
nonce
|
|
4993
|
+
// CSP nonce for external scripts
|
|
4994
|
+
})
|
|
4995
|
+
]
|
|
3975
4996
|
),
|
|
3976
4997
|
React.createElement(
|
|
3977
4998
|
"body",
|
|
3978
4999
|
{
|
|
3979
5000
|
style: { margin: 0 },
|
|
3980
|
-
className: [initialData.className, theme].filter(Boolean).join(" "),
|
|
5001
|
+
className: [initialData.className || "", theme].filter(Boolean).join(" "),
|
|
3981
5002
|
suppressHydrationWarning: true
|
|
3982
5003
|
// Allow theme class to differ between server and client initially
|
|
3983
5004
|
},
|
|
3984
|
-
|
|
3985
|
-
)
|
|
3986
|
-
React.createElement("script", {
|
|
3987
|
-
nonce,
|
|
3988
|
-
dangerouslySetInnerHTML: {
|
|
3989
|
-
__html: `window.${WINDOW_DATA_KEY} = ${serialized};`
|
|
3990
|
-
}
|
|
3991
|
-
}),
|
|
3992
|
-
React.createElement("script", {
|
|
3993
|
-
nonce,
|
|
3994
|
-
dangerouslySetInnerHTML: {
|
|
3995
|
-
__html: `window.${ROUTER_DATA_KEY} = ${routerSerialized};`
|
|
3996
|
-
}
|
|
3997
|
-
})
|
|
5005
|
+
...bodyChildren
|
|
5006
|
+
)
|
|
3998
5007
|
);
|
|
3999
5008
|
return documentTree;
|
|
4000
5009
|
}
|
|
@@ -4010,7 +5019,7 @@ function buildInitialData(urlPath, params, loaderResult) {
|
|
|
4010
5019
|
params,
|
|
4011
5020
|
props,
|
|
4012
5021
|
metadata: loaderResult.metadata ?? null,
|
|
4013
|
-
className: loaderResult.className
|
|
5022
|
+
className: loaderResult.className,
|
|
4014
5023
|
error: false,
|
|
4015
5024
|
notFound: false
|
|
4016
5025
|
};
|
|
@@ -4026,12 +5035,25 @@ var buildRouterData = (req) => {
|
|
|
4026
5035
|
};
|
|
4027
5036
|
|
|
4028
5037
|
// modules/server/handlers/middleware.ts
|
|
5038
|
+
import path18 from "path";
|
|
4029
5039
|
async function runRouteMiddlewares(route, ctx) {
|
|
4030
|
-
for (
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
5040
|
+
for (let i = 0; i < route.middlewares.length; i++) {
|
|
5041
|
+
const mw = route.middlewares[i];
|
|
5042
|
+
try {
|
|
5043
|
+
await Promise.resolve(
|
|
5044
|
+
mw(ctx, async () => {
|
|
5045
|
+
})
|
|
5046
|
+
);
|
|
5047
|
+
} catch (error) {
|
|
5048
|
+
const reqLogger = getRequestLogger(ctx.req);
|
|
5049
|
+
const relativePath = route.pageFile ? path18.relative(process.cwd(), route.pageFile) : route.pattern;
|
|
5050
|
+
reqLogger.error("Route middleware failed", error instanceof Error ? error : new Error(String(error)), {
|
|
5051
|
+
route: route.pattern,
|
|
5052
|
+
middlewareIndex: i,
|
|
5053
|
+
pageFile: relativePath
|
|
5054
|
+
});
|
|
5055
|
+
throw error;
|
|
5056
|
+
}
|
|
4035
5057
|
if (ctx.res.headersSent) {
|
|
4036
5058
|
return;
|
|
4037
5059
|
}
|
|
@@ -4039,11 +5061,57 @@ async function runRouteMiddlewares(route, ctx) {
|
|
|
4039
5061
|
}
|
|
4040
5062
|
|
|
4041
5063
|
// modules/server/handlers/server-hook.ts
|
|
5064
|
+
import path19 from "path";
|
|
5065
|
+
function createServerHookErrorMessage(error, hookType, routePattern, filePath) {
|
|
5066
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
5067
|
+
const errorStack = error instanceof Error ? error.stack : void 0;
|
|
5068
|
+
let message = `[${hookType.toUpperCase()} SERVER HOOK ERROR]
|
|
5069
|
+
`;
|
|
5070
|
+
message += `Route: ${routePattern}
|
|
5071
|
+
`;
|
|
5072
|
+
if (filePath) {
|
|
5073
|
+
const relativePath = path19.relative(process.cwd(), filePath);
|
|
5074
|
+
message += `File: ${relativePath}
|
|
5075
|
+
`;
|
|
5076
|
+
}
|
|
5077
|
+
message += `Error: ${errorMessage}
|
|
5078
|
+
`;
|
|
5079
|
+
if (errorMessage.includes("Cannot find module")) {
|
|
5080
|
+
message += `
|
|
5081
|
+
\u{1F4A1} Suggestion: Check that all imports in your ${hookType}.server.hook.ts are correct.
|
|
5082
|
+
`;
|
|
5083
|
+
} else if (errorMessage.includes("is not defined") || errorMessage.includes("Cannot read property")) {
|
|
5084
|
+
message += `
|
|
5085
|
+
\u{1F4A1} Suggestion: Verify that all variables and properties exist in your ${hookType}.server.hook.ts.
|
|
5086
|
+
`;
|
|
5087
|
+
} else if (errorMessage.includes("async") || errorMessage.includes("await")) {
|
|
5088
|
+
message += `
|
|
5089
|
+
\u{1F4A1} Suggestion: Make sure getServerSideProps is an async function and all promises are awaited.
|
|
5090
|
+
`;
|
|
5091
|
+
}
|
|
5092
|
+
if (errorStack) {
|
|
5093
|
+
message += `
|
|
5094
|
+
Stack trace:
|
|
5095
|
+
${errorStack}`;
|
|
5096
|
+
}
|
|
5097
|
+
return message;
|
|
5098
|
+
}
|
|
4042
5099
|
async function runRouteServerHook(route, ctx) {
|
|
4043
5100
|
if (!route.loader) {
|
|
4044
5101
|
return { props: {} };
|
|
4045
5102
|
}
|
|
4046
|
-
|
|
5103
|
+
try {
|
|
5104
|
+
return await route.loader(ctx);
|
|
5105
|
+
} catch (error) {
|
|
5106
|
+
const detailedError = new Error(
|
|
5107
|
+
createServerHookErrorMessage(error, "page", route.pattern, route.pageFile)
|
|
5108
|
+
);
|
|
5109
|
+
if (error instanceof Error && error.stack) {
|
|
5110
|
+
detailedError.stack = error.stack;
|
|
5111
|
+
}
|
|
5112
|
+
detailedError.originalError = error;
|
|
5113
|
+
throw detailedError;
|
|
5114
|
+
}
|
|
4047
5115
|
}
|
|
4048
5116
|
|
|
4049
5117
|
// modules/server/handlers/response.ts
|
|
@@ -4084,26 +5152,26 @@ function handleNotFound(res, urlPath) {
|
|
|
4084
5152
|
|
|
4085
5153
|
// modules/server/handlers/ssg.ts
|
|
4086
5154
|
import fs15 from "fs";
|
|
4087
|
-
import
|
|
4088
|
-
var
|
|
5155
|
+
import path20 from "path";
|
|
5156
|
+
var logger3 = createModuleLogger("ssg");
|
|
4089
5157
|
function getSsgDirForPath(baseDir, urlPath) {
|
|
4090
5158
|
const clean = urlPath === "/" ? "" : urlPath.replace(/^\/+/, "");
|
|
4091
|
-
return
|
|
5159
|
+
return path20.join(baseDir, clean);
|
|
4092
5160
|
}
|
|
4093
5161
|
function getSsgHtmlPath(baseDir, urlPath) {
|
|
4094
5162
|
const dir = getSsgDirForPath(baseDir, urlPath);
|
|
4095
|
-
return
|
|
5163
|
+
return path20.join(dir, "index.html");
|
|
4096
5164
|
}
|
|
4097
5165
|
function getSsgDataPath(baseDir, urlPath) {
|
|
4098
5166
|
const dir = getSsgDirForPath(baseDir, urlPath);
|
|
4099
|
-
return
|
|
5167
|
+
return path20.join(dir, "data.json");
|
|
4100
5168
|
}
|
|
4101
5169
|
function tryServeSsgHtml(res, ssgOutDir, urlPath) {
|
|
4102
5170
|
const ssgHtmlPath = getSsgHtmlPath(ssgOutDir, urlPath);
|
|
4103
5171
|
if (!fs15.existsSync(ssgHtmlPath)) {
|
|
4104
5172
|
return false;
|
|
4105
5173
|
}
|
|
4106
|
-
|
|
5174
|
+
logger3.info("Serving SSG HTML", { urlPath, ssgHtmlPath });
|
|
4107
5175
|
res.setHeader(
|
|
4108
5176
|
"Content-Security-Policy",
|
|
4109
5177
|
"default-src 'self'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; script-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self' https:; font-src 'self' data: https://fonts.gstatic.com; object-src 'none'; media-src 'self' https:; frame-src 'none';"
|
|
@@ -4125,13 +5193,43 @@ function tryServeSsgData(res, ssgOutDir, urlPath) {
|
|
|
4125
5193
|
res.status(200).end(raw);
|
|
4126
5194
|
return true;
|
|
4127
5195
|
} catch (err) {
|
|
4128
|
-
|
|
5196
|
+
logger3.error("Error reading SSG data", err, { urlPath, ssgDataPath });
|
|
4129
5197
|
return false;
|
|
4130
5198
|
}
|
|
4131
5199
|
}
|
|
4132
5200
|
|
|
4133
5201
|
// modules/server/handlers/pages.ts
|
|
4134
5202
|
init_globals();
|
|
5203
|
+
import path21 from "path";
|
|
5204
|
+
function mergeMetadata(base, override) {
|
|
5205
|
+
if (!base && !override) return null;
|
|
5206
|
+
if (!base) return override;
|
|
5207
|
+
if (!override) return base;
|
|
5208
|
+
return {
|
|
5209
|
+
// Simple fields: override wins
|
|
5210
|
+
title: override.title ?? base.title,
|
|
5211
|
+
description: override.description ?? base.description,
|
|
5212
|
+
lang: override.lang ?? base.lang,
|
|
5213
|
+
canonical: override.canonical ?? base.canonical,
|
|
5214
|
+
robots: override.robots ?? base.robots,
|
|
5215
|
+
themeColor: override.themeColor ?? base.themeColor,
|
|
5216
|
+
viewport: override.viewport ?? base.viewport,
|
|
5217
|
+
// Nested objects: shallow merge (override wins for each field)
|
|
5218
|
+
openGraph: override.openGraph ? {
|
|
5219
|
+
...base.openGraph,
|
|
5220
|
+
...override.openGraph,
|
|
5221
|
+
// For image, if override has image, use it entirely (don't merge)
|
|
5222
|
+
image: override.openGraph.image ?? base.openGraph?.image
|
|
5223
|
+
} : base.openGraph,
|
|
5224
|
+
twitter: override.twitter ? {
|
|
5225
|
+
...base.twitter,
|
|
5226
|
+
...override.twitter
|
|
5227
|
+
} : base.twitter,
|
|
5228
|
+
// Arrays: override replaces base entirely (not merged)
|
|
5229
|
+
metaTags: override.metaTags ?? base.metaTags,
|
|
5230
|
+
links: override.links ?? base.links
|
|
5231
|
+
};
|
|
5232
|
+
}
|
|
4135
5233
|
function isDataRequest(req) {
|
|
4136
5234
|
return req.query && req.query.__fw_data === "1" || req.headers["x-fw-data"] === "1";
|
|
4137
5235
|
}
|
|
@@ -4208,9 +5306,13 @@ async function handlePageRequestInternal(options) {
|
|
|
4208
5306
|
}
|
|
4209
5307
|
} catch (error) {
|
|
4210
5308
|
const reqLogger2 = getRequestLogger(req);
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
5309
|
+
const layoutFile = notFoundPage.layoutFiles[i];
|
|
5310
|
+
const relativeLayoutPath = layoutFile ? path21.relative(projectRoot || process.cwd(), layoutFile) : "unknown";
|
|
5311
|
+
reqLogger2.warn("Layout server hook failed for not-found page", {
|
|
5312
|
+
error: error instanceof Error ? error.message : String(error),
|
|
5313
|
+
stack: error instanceof Error ? error.stack : void 0,
|
|
5314
|
+
layoutFile: relativeLayoutPath,
|
|
5315
|
+
layoutIndex: i
|
|
4214
5316
|
});
|
|
4215
5317
|
}
|
|
4216
5318
|
}
|
|
@@ -4232,6 +5334,10 @@ async function handlePageRequestInternal(options) {
|
|
|
4232
5334
|
const appTree2 = buildAppTree(notFoundPage, {}, initialData2.props);
|
|
4233
5335
|
initialData2.notFound = true;
|
|
4234
5336
|
const nonce2 = res.locals.nonce || void 0;
|
|
5337
|
+
const entrypointFiles2 = [];
|
|
5338
|
+
if (assetManifest?.entrypoints?.client) {
|
|
5339
|
+
entrypointFiles2.push(...assetManifest.entrypoints.client.map((file) => `${STATIC_PATH}/${file}`));
|
|
5340
|
+
}
|
|
4235
5341
|
const documentTree2 = createDocumentTree({
|
|
4236
5342
|
appTree: appTree2,
|
|
4237
5343
|
initialData: initialData2,
|
|
@@ -4240,6 +5346,7 @@ async function handlePageRequestInternal(options) {
|
|
|
4240
5346
|
titleFallback: "Not found",
|
|
4241
5347
|
descriptionFallback: "Loly demo",
|
|
4242
5348
|
chunkHref: null,
|
|
5349
|
+
entrypointFiles: entrypointFiles2,
|
|
4243
5350
|
theme,
|
|
4244
5351
|
clientJsPath,
|
|
4245
5352
|
clientCssPath,
|
|
@@ -4292,6 +5399,7 @@ async function handlePageRequestInternal(options) {
|
|
|
4292
5399
|
return;
|
|
4293
5400
|
}
|
|
4294
5401
|
const layoutProps = {};
|
|
5402
|
+
const layoutMetadata = [];
|
|
4295
5403
|
const reqLogger = getRequestLogger(req);
|
|
4296
5404
|
if (route.layoutServerHooks && route.layoutServerHooks.length > 0) {
|
|
4297
5405
|
for (let i = 0; i < route.layoutServerHooks.length; i++) {
|
|
@@ -4302,11 +5410,19 @@ async function handlePageRequestInternal(options) {
|
|
|
4302
5410
|
if (layoutResult.props) {
|
|
4303
5411
|
Object.assign(layoutProps, layoutResult.props);
|
|
4304
5412
|
}
|
|
5413
|
+
if (layoutResult.metadata) {
|
|
5414
|
+
layoutMetadata.push(layoutResult.metadata);
|
|
5415
|
+
}
|
|
4305
5416
|
} catch (error) {
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
|
|
5417
|
+
const layoutFile = route.layoutFiles[i];
|
|
5418
|
+
const relativeLayoutPath = layoutFile ? path21.relative(projectRoot || process.cwd(), layoutFile) : "unknown";
|
|
5419
|
+
reqLogger.warn("Layout server hook failed", {
|
|
5420
|
+
error: error instanceof Error ? error.message : String(error),
|
|
5421
|
+
stack: error instanceof Error ? error.stack : void 0,
|
|
5422
|
+
layoutFile: relativeLayoutPath,
|
|
5423
|
+
route: route.pattern,
|
|
5424
|
+
layoutIndex: i,
|
|
5425
|
+
suggestion: "Check your layout.server.hook.ts file for errors"
|
|
4310
5426
|
});
|
|
4311
5427
|
}
|
|
4312
5428
|
}
|
|
@@ -4319,10 +5435,25 @@ async function handlePageRequestInternal(options) {
|
|
|
4319
5435
|
loaderResult.theme = theme;
|
|
4320
5436
|
}
|
|
4321
5437
|
} catch (error) {
|
|
5438
|
+
const relativePagePath = route.pageFile ? path21.relative(projectRoot || process.cwd(), route.pageFile) : "unknown";
|
|
5439
|
+
reqLogger.error("Page server hook failed", {
|
|
5440
|
+
error: error instanceof Error ? error.message : String(error),
|
|
5441
|
+
stack: error instanceof Error ? error.stack : void 0,
|
|
5442
|
+
pageFile: relativePagePath,
|
|
5443
|
+
route: route.pattern,
|
|
5444
|
+
pathname: urlPath,
|
|
5445
|
+
suggestion: "Check your page.server.hook.ts (or server.hook.ts) file for errors"
|
|
5446
|
+
});
|
|
4322
5447
|
if (isDataReq) {
|
|
4323
5448
|
res.statusCode = 500;
|
|
4324
5449
|
res.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
4325
|
-
|
|
5450
|
+
const errorResponse = {
|
|
5451
|
+
error: true,
|
|
5452
|
+
message: error instanceof Error ? error.message : String(error),
|
|
5453
|
+
route: route.pattern,
|
|
5454
|
+
pageFile: relativePagePath
|
|
5455
|
+
};
|
|
5456
|
+
res.end(JSON.stringify(errorResponse, null, 2));
|
|
4326
5457
|
return;
|
|
4327
5458
|
} else {
|
|
4328
5459
|
if (errorPage) {
|
|
@@ -4339,9 +5470,19 @@ async function handlePageRequestInternal(options) {
|
|
|
4339
5470
|
...loaderResult.props || {}
|
|
4340
5471
|
// Props from page (overrides layout)
|
|
4341
5472
|
};
|
|
5473
|
+
let combinedMetadata = null;
|
|
5474
|
+
for (const layoutMeta of layoutMetadata) {
|
|
5475
|
+
if (layoutMeta) {
|
|
5476
|
+
combinedMetadata = mergeMetadata(combinedMetadata, layoutMeta);
|
|
5477
|
+
}
|
|
5478
|
+
}
|
|
5479
|
+
if (loaderResult.metadata) {
|
|
5480
|
+
combinedMetadata = mergeMetadata(combinedMetadata, loaderResult.metadata);
|
|
5481
|
+
}
|
|
4342
5482
|
const combinedLoaderResult = {
|
|
4343
5483
|
...loaderResult,
|
|
4344
|
-
props: combinedProps
|
|
5484
|
+
props: combinedProps,
|
|
5485
|
+
metadata: combinedMetadata
|
|
4345
5486
|
};
|
|
4346
5487
|
if (isDataReq) {
|
|
4347
5488
|
handleDataResponse(res, combinedLoaderResult, theme);
|
|
@@ -4371,6 +5512,10 @@ async function handlePageRequestInternal(options) {
|
|
|
4371
5512
|
}
|
|
4372
5513
|
}
|
|
4373
5514
|
const nonce = res.locals.nonce || void 0;
|
|
5515
|
+
const entrypointFiles = [];
|
|
5516
|
+
if (assetManifest?.entrypoints?.client) {
|
|
5517
|
+
entrypointFiles.push(...assetManifest.entrypoints.client.map((file) => `${STATIC_PATH}/${file}`));
|
|
5518
|
+
}
|
|
4374
5519
|
const documentTree = createDocumentTree({
|
|
4375
5520
|
appTree,
|
|
4376
5521
|
initialData,
|
|
@@ -4379,6 +5524,7 @@ async function handlePageRequestInternal(options) {
|
|
|
4379
5524
|
titleFallback: "Loly framework",
|
|
4380
5525
|
descriptionFallback: "Loly demo",
|
|
4381
5526
|
chunkHref,
|
|
5527
|
+
entrypointFiles,
|
|
4382
5528
|
theme,
|
|
4383
5529
|
clientJsPath,
|
|
4384
5530
|
clientCssPath,
|
|
@@ -4397,7 +5543,16 @@ async function handlePageRequestInternal(options) {
|
|
|
4397
5543
|
onShellError(err) {
|
|
4398
5544
|
didError = true;
|
|
4399
5545
|
const reqLogger2 = getRequestLogger(req);
|
|
4400
|
-
|
|
5546
|
+
const routePattern = matched?.route?.pattern || "unknown";
|
|
5547
|
+
reqLogger2.error("SSR shell error", err, { route: routePattern });
|
|
5548
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
5549
|
+
console.error(`
|
|
5550
|
+
\u274C [framework][ssr] Shell error for route "${routePattern}":`);
|
|
5551
|
+
console.error(` ${errorMessage}`);
|
|
5552
|
+
if (err instanceof Error && err.stack) {
|
|
5553
|
+
console.error(` Stack: ${err.stack.split("\n").slice(0, 3).join("\n ")}`);
|
|
5554
|
+
}
|
|
5555
|
+
console.error("\u{1F4A1} This usually indicates a React rendering error\n");
|
|
4401
5556
|
if (!res.headersSent && errorPage) {
|
|
4402
5557
|
renderErrorPageWithStream(errorPage, req, res, err, routeChunks, theme, projectRoot, env);
|
|
4403
5558
|
} else if (!res.headersSent) {
|
|
@@ -4410,7 +5565,11 @@ async function handlePageRequestInternal(options) {
|
|
|
4410
5565
|
onError(err) {
|
|
4411
5566
|
didError = true;
|
|
4412
5567
|
const reqLogger2 = getRequestLogger(req);
|
|
4413
|
-
|
|
5568
|
+
const routePattern = matched?.route?.pattern || "unknown";
|
|
5569
|
+
reqLogger2.error("SSR error", err, { route: routePattern });
|
|
5570
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
5571
|
+
console.error(`\u26A0\uFE0F [framework][ssr] Error during streaming for route "${routePattern}":`);
|
|
5572
|
+
console.error(` ${errorMessage}`);
|
|
4414
5573
|
}
|
|
4415
5574
|
});
|
|
4416
5575
|
req.on("close", () => {
|
|
@@ -4439,9 +5598,13 @@ async function renderErrorPageWithStream(errorPage, req, res, error, routeChunks
|
|
|
4439
5598
|
Object.assign(layoutProps, layoutResult.props);
|
|
4440
5599
|
}
|
|
4441
5600
|
} catch (err) {
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
5601
|
+
const layoutFile = errorPage.layoutFiles[i];
|
|
5602
|
+
const relativeLayoutPath = layoutFile ? path21.relative(projectRoot || process.cwd(), layoutFile) : "unknown";
|
|
5603
|
+
reqLogger.warn("Layout server hook failed for error page", {
|
|
5604
|
+
error: err instanceof Error ? err.message : String(err),
|
|
5605
|
+
stack: err instanceof Error ? err.stack : void 0,
|
|
5606
|
+
layoutFile: relativeLayoutPath,
|
|
5607
|
+
layoutIndex: i
|
|
4445
5608
|
});
|
|
4446
5609
|
}
|
|
4447
5610
|
}
|
|
@@ -4488,6 +5651,10 @@ async function renderErrorPageWithStream(errorPage, req, res, error, routeChunks
|
|
|
4488
5651
|
}
|
|
4489
5652
|
}
|
|
4490
5653
|
const nonce = res.locals.nonce || void 0;
|
|
5654
|
+
const entrypointFiles = [];
|
|
5655
|
+
if (assetManifest?.entrypoints?.client) {
|
|
5656
|
+
entrypointFiles.push(...assetManifest.entrypoints.client.map((file) => `${STATIC_PATH}/${file}`));
|
|
5657
|
+
}
|
|
4491
5658
|
const documentTree = createDocumentTree({
|
|
4492
5659
|
appTree,
|
|
4493
5660
|
initialData,
|
|
@@ -4613,7 +5780,7 @@ async function getServerConfig(projectRoot) {
|
|
|
4613
5780
|
}
|
|
4614
5781
|
|
|
4615
5782
|
// modules/server/routes.ts
|
|
4616
|
-
import
|
|
5783
|
+
import path22 from "path";
|
|
4617
5784
|
function setupRoutes(options) {
|
|
4618
5785
|
const {
|
|
4619
5786
|
app,
|
|
@@ -4628,21 +5795,23 @@ function setupRoutes(options) {
|
|
|
4628
5795
|
config
|
|
4629
5796
|
} = options;
|
|
4630
5797
|
const routeChunks = routeLoader.loadRouteChunks();
|
|
4631
|
-
const ssgOutDir =
|
|
4632
|
-
config ? getBuildDir(projectRoot, config) :
|
|
5798
|
+
const ssgOutDir = path22.join(
|
|
5799
|
+
config ? getBuildDir(projectRoot, config) : path22.join(projectRoot, BUILD_FOLDER_NAME),
|
|
4633
5800
|
"ssg"
|
|
4634
5801
|
);
|
|
4635
5802
|
app.all("/api/*", async (req, res) => {
|
|
4636
5803
|
const apiRoutes = isDev && getRoutes ? getRoutes().apiRoutes : initialApiRoutes;
|
|
4637
5804
|
const serverConfig = await getServerConfig(projectRoot);
|
|
4638
5805
|
const strictPatterns = serverConfig.rateLimit?.strictPatterns || [];
|
|
5806
|
+
const rateLimitConfig = serverConfig.rateLimit;
|
|
4639
5807
|
await handleApiRequest({
|
|
4640
5808
|
apiRoutes,
|
|
4641
5809
|
urlPath: req.path,
|
|
4642
5810
|
req,
|
|
4643
5811
|
res,
|
|
4644
5812
|
env: isDev ? "dev" : "prod",
|
|
4645
|
-
strictRateLimitPatterns: strictPatterns
|
|
5813
|
+
strictRateLimitPatterns: strictPatterns,
|
|
5814
|
+
rateLimitConfig
|
|
4646
5815
|
});
|
|
4647
5816
|
});
|
|
4648
5817
|
app.get("*", async (req, res) => {
|
|
@@ -4895,12 +6064,29 @@ var setupApplication = async ({
|
|
|
4895
6064
|
corsOptions.origin = process.env.NODE_ENV === "development";
|
|
4896
6065
|
}
|
|
4897
6066
|
app.use(cors(corsOptions));
|
|
4898
|
-
if (rateLimit2
|
|
4899
|
-
const
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
6067
|
+
if (rateLimit2) {
|
|
6068
|
+
const shouldApply = process.env.NODE_ENV !== "development" || process.env.ENABLE_RATE_LIMIT === "true";
|
|
6069
|
+
if (shouldApply) {
|
|
6070
|
+
try {
|
|
6071
|
+
const generalLimiter = createRateLimiterFromConfig(rateLimit2, false);
|
|
6072
|
+
if (generalLimiter) {
|
|
6073
|
+
app.use(generalLimiter);
|
|
6074
|
+
const logger5 = createModuleLogger("server");
|
|
6075
|
+
logger5.info("Rate limiting enabled", {
|
|
6076
|
+
windowMs: rateLimit2.windowMs ?? 15 * 60 * 1e3,
|
|
6077
|
+
max: rateLimit2.max ?? 100,
|
|
6078
|
+
apiMax: rateLimit2.apiMax,
|
|
6079
|
+
strictMax: rateLimit2.strictMax,
|
|
6080
|
+
strictPatterns: rateLimit2.strictPatterns?.length ?? 0
|
|
6081
|
+
});
|
|
6082
|
+
}
|
|
6083
|
+
} catch (error) {
|
|
6084
|
+
const logger5 = createModuleLogger("server");
|
|
6085
|
+
logger5.error("Failed to setup rate limiting", {
|
|
6086
|
+
error: error instanceof Error ? error.message : String(error)
|
|
6087
|
+
});
|
|
6088
|
+
}
|
|
6089
|
+
}
|
|
4904
6090
|
}
|
|
4905
6091
|
app.use(cookieParser());
|
|
4906
6092
|
app.use(express2.json({ limit: bodyLimit }));
|
|
@@ -4915,22 +6101,31 @@ var setupApplication = async ({
|
|
|
4915
6101
|
|
|
4916
6102
|
// src/server.ts
|
|
4917
6103
|
import dotenv2 from "dotenv";
|
|
4918
|
-
var envPath =
|
|
6104
|
+
var envPath = path23.join(process.cwd(), ".env");
|
|
4919
6105
|
if (fs16.existsSync(envPath)) {
|
|
4920
6106
|
dotenv2.config({ path: envPath });
|
|
4921
6107
|
} else {
|
|
4922
6108
|
dotenv2.config();
|
|
4923
6109
|
}
|
|
4924
|
-
var
|
|
6110
|
+
var logger4 = createModuleLogger("server");
|
|
4925
6111
|
async function startServer(options = {}) {
|
|
4926
6112
|
const isDev = options.isDev ?? process.env.NODE_ENV === "development";
|
|
4927
6113
|
const projectRoot = options.rootDir ?? process.cwd();
|
|
4928
|
-
|
|
6114
|
+
let config;
|
|
6115
|
+
try {
|
|
6116
|
+
config = options.config ?? loadConfig(projectRoot);
|
|
6117
|
+
} catch (error) {
|
|
6118
|
+
if (error instanceof ConfigValidationError) {
|
|
6119
|
+
console.error("\n" + error.message + "\n");
|
|
6120
|
+
process.exit(1);
|
|
6121
|
+
}
|
|
6122
|
+
throw error;
|
|
6123
|
+
}
|
|
4929
6124
|
const port = options.port ?? (process.env.PORT ? parseInt(process.env.PORT, 10) : void 0) ?? config.server.port;
|
|
4930
6125
|
const host = process.env.HOST ?? (!isDev ? "0.0.0.0" : void 0) ?? config.server.host;
|
|
4931
|
-
const appDir = options.appDir ?? (isDev ? getAppDir(projectRoot, config) :
|
|
6126
|
+
const appDir = options.appDir ?? (isDev ? getAppDir(projectRoot, config) : path23.join(getBuildDir(projectRoot, config), "server"));
|
|
4932
6127
|
if (!isDev && !fs16.existsSync(appDir)) {
|
|
4933
|
-
|
|
6128
|
+
logger4.error("Compiled directory not found", void 0, {
|
|
4934
6129
|
buildDir: config.directories.build,
|
|
4935
6130
|
appDir,
|
|
4936
6131
|
environment: "production"
|
|
@@ -4947,7 +6142,7 @@ async function startServer(options = {}) {
|
|
|
4947
6142
|
isDev,
|
|
4948
6143
|
config
|
|
4949
6144
|
});
|
|
4950
|
-
const routeLoader = isDev ? new FilesystemRouteLoader(appDir) : new ManifestRouteLoader(projectRoot);
|
|
6145
|
+
const routeLoader = isDev ? new FilesystemRouteLoader(appDir, projectRoot) : new ManifestRouteLoader(projectRoot);
|
|
4951
6146
|
setupWssEvents({
|
|
4952
6147
|
httpServer,
|
|
4953
6148
|
wssRoutes
|
|
@@ -4999,10 +6194,10 @@ async function startProdServer(options = {}) {
|
|
|
4999
6194
|
}
|
|
5000
6195
|
|
|
5001
6196
|
// modules/build/ssg/builder.ts
|
|
5002
|
-
import
|
|
6197
|
+
import path26 from "path";
|
|
5003
6198
|
|
|
5004
6199
|
// modules/build/ssg/path.ts
|
|
5005
|
-
import
|
|
6200
|
+
import path24 from "path";
|
|
5006
6201
|
function buildPathFromPattern(pattern, params) {
|
|
5007
6202
|
const segments = pattern.split("/").filter(Boolean);
|
|
5008
6203
|
const parts = [];
|
|
@@ -5031,12 +6226,12 @@ function buildPathFromPattern(pattern, params) {
|
|
|
5031
6226
|
}
|
|
5032
6227
|
function pathToOutDir(baseDir, urlPath) {
|
|
5033
6228
|
const clean = urlPath === "/" ? "" : urlPath.replace(/^\/+/, "");
|
|
5034
|
-
return
|
|
6229
|
+
return path24.join(baseDir, clean);
|
|
5035
6230
|
}
|
|
5036
6231
|
|
|
5037
6232
|
// modules/build/ssg/renderer.ts
|
|
5038
6233
|
import fs17 from "fs";
|
|
5039
|
-
import
|
|
6234
|
+
import path25 from "path";
|
|
5040
6235
|
import { renderToString } from "react-dom/server";
|
|
5041
6236
|
init_globals();
|
|
5042
6237
|
async function renderStaticRoute(projectRoot, ssgOutDir, route, urlPath, params) {
|
|
@@ -5053,6 +6248,10 @@ async function renderStaticRoute(projectRoot, ssgOutDir, route, urlPath, params)
|
|
|
5053
6248
|
chunkHref = `${STATIC_PATH}/${chunkName}.js`;
|
|
5054
6249
|
}
|
|
5055
6250
|
}
|
|
6251
|
+
const entrypointFiles = [];
|
|
6252
|
+
if (assetManifest?.entrypoints?.client) {
|
|
6253
|
+
entrypointFiles.push(...assetManifest.entrypoints.client.map((file) => `${STATIC_PATH}/${file}`));
|
|
6254
|
+
}
|
|
5056
6255
|
const req = {
|
|
5057
6256
|
method: "GET",
|
|
5058
6257
|
headers: {},
|
|
@@ -5082,32 +6281,79 @@ async function renderStaticRoute(projectRoot, ssgOutDir, route, urlPath, params)
|
|
|
5082
6281
|
})
|
|
5083
6282
|
);
|
|
5084
6283
|
}
|
|
6284
|
+
const layoutProps = {};
|
|
6285
|
+
const layoutMetadata = [];
|
|
6286
|
+
if (route.layoutServerHooks && route.layoutServerHooks.length > 0) {
|
|
6287
|
+
for (let i = 0; i < route.layoutServerHooks.length; i++) {
|
|
6288
|
+
const layoutServerHook = route.layoutServerHooks[i];
|
|
6289
|
+
if (layoutServerHook) {
|
|
6290
|
+
try {
|
|
6291
|
+
const layoutResult = await layoutServerHook(ctx);
|
|
6292
|
+
if (layoutResult.props) {
|
|
6293
|
+
Object.assign(layoutProps, layoutResult.props);
|
|
6294
|
+
}
|
|
6295
|
+
if (layoutResult.metadata) {
|
|
6296
|
+
layoutMetadata.push(layoutResult.metadata);
|
|
6297
|
+
}
|
|
6298
|
+
} catch (error) {
|
|
6299
|
+
console.warn(
|
|
6300
|
+
`\u26A0\uFE0F [framework][ssg] Layout server hook ${i} failed for route ${route.pattern}:`,
|
|
6301
|
+
error instanceof Error ? error.message : String(error)
|
|
6302
|
+
);
|
|
6303
|
+
if (error instanceof Error && error.stack) {
|
|
6304
|
+
console.warn(` Stack: ${error.stack.split("\n").slice(0, 3).join("\n ")}`);
|
|
6305
|
+
}
|
|
6306
|
+
}
|
|
6307
|
+
}
|
|
6308
|
+
}
|
|
6309
|
+
}
|
|
5085
6310
|
let loaderResult = { props: {} };
|
|
5086
6311
|
if (route.loader) {
|
|
5087
6312
|
loaderResult = await route.loader(ctx);
|
|
5088
6313
|
}
|
|
5089
|
-
|
|
6314
|
+
const combinedProps = {
|
|
6315
|
+
...layoutProps,
|
|
6316
|
+
...loaderResult.props || {}
|
|
6317
|
+
};
|
|
6318
|
+
let combinedMetadata = null;
|
|
6319
|
+
for (const layoutMeta of layoutMetadata) {
|
|
6320
|
+
if (layoutMeta) {
|
|
6321
|
+
combinedMetadata = mergeMetadata(combinedMetadata, layoutMeta);
|
|
6322
|
+
}
|
|
6323
|
+
}
|
|
6324
|
+
if (loaderResult.metadata) {
|
|
6325
|
+
combinedMetadata = mergeMetadata(combinedMetadata, loaderResult.metadata);
|
|
6326
|
+
}
|
|
6327
|
+
const combinedLoaderResult = {
|
|
6328
|
+
...loaderResult,
|
|
6329
|
+
props: combinedProps,
|
|
6330
|
+
metadata: combinedMetadata
|
|
6331
|
+
};
|
|
6332
|
+
if (combinedLoaderResult.redirect || combinedLoaderResult.notFound) {
|
|
5090
6333
|
return;
|
|
5091
6334
|
}
|
|
5092
|
-
const initialData = buildInitialData(urlPath, params,
|
|
6335
|
+
const initialData = buildInitialData(urlPath, params, combinedLoaderResult);
|
|
5093
6336
|
const routerData = buildRouterData(req);
|
|
5094
6337
|
const appTree = buildAppTree(route, params, initialData.props);
|
|
5095
6338
|
const documentTree = createDocumentTree({
|
|
5096
6339
|
appTree,
|
|
5097
6340
|
initialData,
|
|
5098
6341
|
routerData,
|
|
5099
|
-
meta:
|
|
6342
|
+
meta: combinedLoaderResult.metadata,
|
|
5100
6343
|
titleFallback: "My Framework Dev",
|
|
5101
6344
|
descriptionFallback: "Static page generated by @lolyjs/core.",
|
|
5102
6345
|
chunkHref,
|
|
6346
|
+
entrypointFiles,
|
|
5103
6347
|
clientJsPath,
|
|
5104
|
-
clientCssPath
|
|
6348
|
+
clientCssPath,
|
|
6349
|
+
includeInlineScripts: true
|
|
6350
|
+
// SSG needs inline scripts (renderToString doesn't support bootstrapScripts)
|
|
5105
6351
|
});
|
|
5106
6352
|
const html = "<!DOCTYPE html>" + renderToString(documentTree);
|
|
5107
6353
|
const dir = pathToOutDir(ssgOutDir, urlPath);
|
|
5108
6354
|
ensureDir(dir);
|
|
5109
|
-
const htmlFile =
|
|
5110
|
-
const dataFile =
|
|
6355
|
+
const htmlFile = path25.join(dir, "index.html");
|
|
6356
|
+
const dataFile = path25.join(dir, "data.json");
|
|
5111
6357
|
fs17.writeFileSync(htmlFile, html, "utf-8");
|
|
5112
6358
|
fs17.writeFileSync(dataFile, JSON.stringify(initialData, null, 2), "utf-8");
|
|
5113
6359
|
}
|
|
@@ -5115,7 +6361,7 @@ async function renderStaticRoute(projectRoot, ssgOutDir, route, urlPath, params)
|
|
|
5115
6361
|
// modules/build/ssg/builder.ts
|
|
5116
6362
|
init_globals();
|
|
5117
6363
|
async function buildStaticPages(projectRoot, routes) {
|
|
5118
|
-
const ssgOutDir =
|
|
6364
|
+
const ssgOutDir = path26.join(projectRoot, BUILD_FOLDER_NAME, "ssg");
|
|
5119
6365
|
ensureDir(ssgOutDir);
|
|
5120
6366
|
for (const route of routes) {
|
|
5121
6367
|
if (route.dynamic !== "force-static") continue;
|
|
@@ -5125,12 +6371,15 @@ async function buildStaticPages(projectRoot, routes) {
|
|
|
5125
6371
|
} else {
|
|
5126
6372
|
if (!route.generateStaticParams) {
|
|
5127
6373
|
console.warn(
|
|
5128
|
-
|
|
6374
|
+
`\u26A0\uFE0F [framework][ssg] Route "${route.pattern}" is marked as force-static but has no generateStaticParams function`
|
|
5129
6375
|
);
|
|
6376
|
+
console.warn(` \u{1F4A1} Add a generateStaticParams export to enable static generation for this route`);
|
|
6377
|
+
console.warn(` Skipping this route...
|
|
6378
|
+
`);
|
|
5130
6379
|
continue;
|
|
5131
6380
|
}
|
|
5132
6381
|
try {
|
|
5133
|
-
console.log(
|
|
6382
|
+
console.log(`\u{1F4E6} [framework][ssg] Generating static params for route: ${route.pattern}`);
|
|
5134
6383
|
let timeoutId = null;
|
|
5135
6384
|
const timeoutPromise = new Promise((_, reject) => {
|
|
5136
6385
|
timeoutId = setTimeout(() => {
|
|
@@ -5145,12 +6394,16 @@ async function buildStaticPages(projectRoot, routes) {
|
|
|
5145
6394
|
clearTimeout(timeoutId);
|
|
5146
6395
|
}
|
|
5147
6396
|
allParams = sp;
|
|
5148
|
-
console.log(`
|
|
6397
|
+
console.log(` \u2705 Generated ${sp.length} static params for route: ${route.pattern}`);
|
|
5149
6398
|
} catch (error) {
|
|
5150
|
-
console.error(
|
|
5151
|
-
|
|
5152
|
-
|
|
5153
|
-
)
|
|
6399
|
+
console.error(`
|
|
6400
|
+
\u274C [framework][ssg] Error generating static params for route "${route.pattern}":`);
|
|
6401
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
6402
|
+
if (error instanceof Error && error.stack) {
|
|
6403
|
+
console.error(` Stack: ${error.stack.split("\n").slice(0, 3).join("\n ")}`);
|
|
6404
|
+
}
|
|
6405
|
+
console.error(`\u{1F4A1} Check your generateStaticParams function for this route
|
|
6406
|
+
`);
|
|
5154
6407
|
throw error;
|
|
5155
6408
|
}
|
|
5156
6409
|
}
|
|
@@ -5159,11 +6412,11 @@ async function buildStaticPages(projectRoot, routes) {
|
|
|
5159
6412
|
await renderStaticRoute(projectRoot, ssgOutDir, route, urlPath, params);
|
|
5160
6413
|
}
|
|
5161
6414
|
}
|
|
5162
|
-
console.log(
|
|
6415
|
+
console.log(`\u2705 [framework][ssg] Finished building all static pages`);
|
|
5163
6416
|
}
|
|
5164
6417
|
|
|
5165
6418
|
// modules/build/bundler/server.ts
|
|
5166
|
-
import
|
|
6419
|
+
import path27 from "path";
|
|
5167
6420
|
import fs18 from "fs";
|
|
5168
6421
|
import esbuild from "esbuild";
|
|
5169
6422
|
init_globals();
|
|
@@ -5173,7 +6426,7 @@ function collectAppSources(appDir) {
|
|
|
5173
6426
|
function walk(dir) {
|
|
5174
6427
|
const items = fs18.readdirSync(dir, { withFileTypes: true });
|
|
5175
6428
|
for (const item of items) {
|
|
5176
|
-
const full =
|
|
6429
|
+
const full = path27.join(dir, item.name);
|
|
5177
6430
|
if (item.isDirectory()) {
|
|
5178
6431
|
walk(full);
|
|
5179
6432
|
continue;
|
|
@@ -5190,7 +6443,7 @@ function collectAppSources(appDir) {
|
|
|
5190
6443
|
return entries;
|
|
5191
6444
|
}
|
|
5192
6445
|
async function buildServerApp(projectRoot, appDir) {
|
|
5193
|
-
const outDir =
|
|
6446
|
+
const outDir = path27.join(projectRoot, BUILD_FOLDER_NAME, "server");
|
|
5194
6447
|
const entryPoints = collectAppSources(appDir);
|
|
5195
6448
|
ensureDir(outDir);
|
|
5196
6449
|
if (entryPoints.length === 0) {
|
|
@@ -5208,12 +6461,12 @@ async function buildServerApp(projectRoot, appDir) {
|
|
|
5208
6461
|
bundle: true,
|
|
5209
6462
|
splitting: false,
|
|
5210
6463
|
logLevel: "info",
|
|
5211
|
-
tsconfig:
|
|
6464
|
+
tsconfig: path27.join(projectRoot, "tsconfig.json"),
|
|
5212
6465
|
packages: "external"
|
|
5213
6466
|
});
|
|
5214
6467
|
for (const fileName of SERVER_FILES) {
|
|
5215
|
-
const initTS =
|
|
5216
|
-
const initJS =
|
|
6468
|
+
const initTS = path27.join(projectRoot, `${fileName}.ts`);
|
|
6469
|
+
const initJS = path27.join(outDir, `${fileName}.js`);
|
|
5217
6470
|
if (fs18.existsSync(initTS)) {
|
|
5218
6471
|
await esbuild.build({
|
|
5219
6472
|
entryPoints: [initTS],
|
|
@@ -5225,7 +6478,7 @@ async function buildServerApp(projectRoot, appDir) {
|
|
|
5225
6478
|
sourcemap: true,
|
|
5226
6479
|
bundle: false,
|
|
5227
6480
|
logLevel: "info",
|
|
5228
|
-
tsconfig:
|
|
6481
|
+
tsconfig: path27.join(projectRoot, "tsconfig.json")
|
|
5229
6482
|
});
|
|
5230
6483
|
}
|
|
5231
6484
|
}
|
|
@@ -5368,22 +6621,158 @@ function matchRouteClient(pathWithSearch, routes) {
|
|
|
5368
6621
|
}
|
|
5369
6622
|
|
|
5370
6623
|
// modules/runtime/client/metadata.ts
|
|
6624
|
+
function getOrCreateMeta(selector, attributes) {
|
|
6625
|
+
let meta = document.querySelector(selector);
|
|
6626
|
+
if (!meta) {
|
|
6627
|
+
meta = document.createElement("meta");
|
|
6628
|
+
if (attributes.name) meta.name = attributes.name;
|
|
6629
|
+
if (attributes.property) meta.setAttribute("property", attributes.property);
|
|
6630
|
+
if (attributes.httpEquiv) meta.httpEquiv = attributes.httpEquiv;
|
|
6631
|
+
document.head.appendChild(meta);
|
|
6632
|
+
}
|
|
6633
|
+
return meta;
|
|
6634
|
+
}
|
|
6635
|
+
function getOrCreateLink(rel, href) {
|
|
6636
|
+
const selector = `link[rel="${rel}"]`;
|
|
6637
|
+
let link = document.querySelector(selector);
|
|
6638
|
+
if (!link) {
|
|
6639
|
+
link = document.createElement("link");
|
|
6640
|
+
link.rel = rel;
|
|
6641
|
+
link.href = href;
|
|
6642
|
+
document.head.appendChild(link);
|
|
6643
|
+
} else {
|
|
6644
|
+
link.href = href;
|
|
6645
|
+
}
|
|
6646
|
+
return link;
|
|
6647
|
+
}
|
|
5371
6648
|
function applyMetadata(md) {
|
|
5372
6649
|
if (!md) return;
|
|
5373
6650
|
if (md.title) {
|
|
5374
6651
|
document.title = md.title;
|
|
5375
6652
|
}
|
|
5376
6653
|
if (md.description) {
|
|
5377
|
-
|
|
5378
|
-
'meta[name="description"]'
|
|
5379
|
-
);
|
|
5380
|
-
if (!meta) {
|
|
5381
|
-
meta = document.createElement("meta");
|
|
5382
|
-
meta.name = "description";
|
|
5383
|
-
document.head.appendChild(meta);
|
|
5384
|
-
}
|
|
6654
|
+
const meta = getOrCreateMeta('meta[name="description"]', { name: "description" });
|
|
5385
6655
|
meta.content = md.description;
|
|
5386
6656
|
}
|
|
6657
|
+
if (md.robots) {
|
|
6658
|
+
const meta = getOrCreateMeta('meta[name="robots"]', { name: "robots" });
|
|
6659
|
+
meta.content = md.robots;
|
|
6660
|
+
}
|
|
6661
|
+
if (md.themeColor) {
|
|
6662
|
+
const meta = getOrCreateMeta('meta[name="theme-color"]', { name: "theme-color" });
|
|
6663
|
+
meta.content = md.themeColor;
|
|
6664
|
+
}
|
|
6665
|
+
if (md.viewport) {
|
|
6666
|
+
const meta = getOrCreateMeta('meta[name="viewport"]', { name: "viewport" });
|
|
6667
|
+
meta.content = md.viewport;
|
|
6668
|
+
}
|
|
6669
|
+
if (md.canonical) {
|
|
6670
|
+
getOrCreateLink("canonical", md.canonical);
|
|
6671
|
+
}
|
|
6672
|
+
if (md.openGraph) {
|
|
6673
|
+
const og = md.openGraph;
|
|
6674
|
+
if (og.title) {
|
|
6675
|
+
const meta = getOrCreateMeta('meta[property="og:title"]', { property: "og:title" });
|
|
6676
|
+
meta.content = og.title;
|
|
6677
|
+
}
|
|
6678
|
+
if (og.description) {
|
|
6679
|
+
const meta = getOrCreateMeta('meta[property="og:description"]', { property: "og:description" });
|
|
6680
|
+
meta.content = og.description;
|
|
6681
|
+
}
|
|
6682
|
+
if (og.type) {
|
|
6683
|
+
const meta = getOrCreateMeta('meta[property="og:type"]', { property: "og:type" });
|
|
6684
|
+
meta.content = og.type;
|
|
6685
|
+
}
|
|
6686
|
+
if (og.url) {
|
|
6687
|
+
const meta = getOrCreateMeta('meta[property="og:url"]', { property: "og:url" });
|
|
6688
|
+
meta.content = og.url;
|
|
6689
|
+
}
|
|
6690
|
+
if (og.image) {
|
|
6691
|
+
if (typeof og.image === "string") {
|
|
6692
|
+
const meta = getOrCreateMeta('meta[property="og:image"]', { property: "og:image" });
|
|
6693
|
+
meta.content = og.image;
|
|
6694
|
+
} else {
|
|
6695
|
+
const meta = getOrCreateMeta('meta[property="og:image"]', { property: "og:image" });
|
|
6696
|
+
meta.content = og.image.url;
|
|
6697
|
+
if (og.image.width) {
|
|
6698
|
+
const metaWidth = getOrCreateMeta('meta[property="og:image:width"]', { property: "og:image:width" });
|
|
6699
|
+
metaWidth.content = String(og.image.width);
|
|
6700
|
+
}
|
|
6701
|
+
if (og.image.height) {
|
|
6702
|
+
const metaHeight = getOrCreateMeta('meta[property="og:image:height"]', { property: "og:image:height" });
|
|
6703
|
+
metaHeight.content = String(og.image.height);
|
|
6704
|
+
}
|
|
6705
|
+
if (og.image.alt) {
|
|
6706
|
+
const metaAlt = getOrCreateMeta('meta[property="og:image:alt"]', { property: "og:image:alt" });
|
|
6707
|
+
metaAlt.content = og.image.alt;
|
|
6708
|
+
}
|
|
6709
|
+
}
|
|
6710
|
+
}
|
|
6711
|
+
if (og.siteName) {
|
|
6712
|
+
const meta = getOrCreateMeta('meta[property="og:site_name"]', { property: "og:site_name" });
|
|
6713
|
+
meta.content = og.siteName;
|
|
6714
|
+
}
|
|
6715
|
+
if (og.locale) {
|
|
6716
|
+
const meta = getOrCreateMeta('meta[property="og:locale"]', { property: "og:locale" });
|
|
6717
|
+
meta.content = og.locale;
|
|
6718
|
+
}
|
|
6719
|
+
}
|
|
6720
|
+
if (md.twitter) {
|
|
6721
|
+
const twitter = md.twitter;
|
|
6722
|
+
if (twitter.card) {
|
|
6723
|
+
const meta = getOrCreateMeta('meta[name="twitter:card"]', { name: "twitter:card" });
|
|
6724
|
+
meta.content = twitter.card;
|
|
6725
|
+
}
|
|
6726
|
+
if (twitter.title) {
|
|
6727
|
+
const meta = getOrCreateMeta('meta[name="twitter:title"]', { name: "twitter:title" });
|
|
6728
|
+
meta.content = twitter.title;
|
|
6729
|
+
}
|
|
6730
|
+
if (twitter.description) {
|
|
6731
|
+
const meta = getOrCreateMeta('meta[name="twitter:description"]', { name: "twitter:description" });
|
|
6732
|
+
meta.content = twitter.description;
|
|
6733
|
+
}
|
|
6734
|
+
if (twitter.image) {
|
|
6735
|
+
const meta = getOrCreateMeta('meta[name="twitter:image"]', { name: "twitter:image" });
|
|
6736
|
+
meta.content = twitter.image;
|
|
6737
|
+
}
|
|
6738
|
+
if (twitter.imageAlt) {
|
|
6739
|
+
const meta = getOrCreateMeta('meta[name="twitter:image:alt"]', { name: "twitter:image:alt" });
|
|
6740
|
+
meta.content = twitter.imageAlt;
|
|
6741
|
+
}
|
|
6742
|
+
if (twitter.site) {
|
|
6743
|
+
const meta = getOrCreateMeta('meta[name="twitter:site"]', { name: "twitter:site" });
|
|
6744
|
+
meta.content = twitter.site;
|
|
6745
|
+
}
|
|
6746
|
+
if (twitter.creator) {
|
|
6747
|
+
const meta = getOrCreateMeta('meta[name="twitter:creator"]', { name: "twitter:creator" });
|
|
6748
|
+
meta.content = twitter.creator;
|
|
6749
|
+
}
|
|
6750
|
+
}
|
|
6751
|
+
if (md.metaTags && Array.isArray(md.metaTags)) {
|
|
6752
|
+
md.metaTags.forEach((tag) => {
|
|
6753
|
+
let selector = "";
|
|
6754
|
+
if (tag.name) {
|
|
6755
|
+
selector = `meta[name="${tag.name}"]`;
|
|
6756
|
+
} else if (tag.property) {
|
|
6757
|
+
selector = `meta[property="${tag.property}"]`;
|
|
6758
|
+
} else if (tag.httpEquiv) {
|
|
6759
|
+
selector = `meta[http-equiv="${tag.httpEquiv}"]`;
|
|
6760
|
+
}
|
|
6761
|
+
if (selector) {
|
|
6762
|
+
const meta = getOrCreateMeta(selector, {
|
|
6763
|
+
name: tag.name,
|
|
6764
|
+
property: tag.property,
|
|
6765
|
+
httpEquiv: tag.httpEquiv
|
|
6766
|
+
});
|
|
6767
|
+
meta.content = tag.content;
|
|
6768
|
+
}
|
|
6769
|
+
});
|
|
6770
|
+
}
|
|
6771
|
+
if (md.links && Array.isArray(md.links)) {
|
|
6772
|
+
md.links.forEach((link) => {
|
|
6773
|
+
getOrCreateLink(link.rel, link.href);
|
|
6774
|
+
});
|
|
6775
|
+
}
|
|
5387
6776
|
}
|
|
5388
6777
|
|
|
5389
6778
|
// modules/runtime/client/AppShell.tsx
|
|
@@ -5600,10 +6989,15 @@ async function handleErrorRoute(nextUrl, json, errorRoute, setState) {
|
|
|
5600
6989
|
});
|
|
5601
6990
|
return true;
|
|
5602
6991
|
} catch (loadError) {
|
|
5603
|
-
console.error(
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
6992
|
+
console.error("\n\u274C [client] Error loading error route components:");
|
|
6993
|
+
console.error(loadError);
|
|
6994
|
+
if (loadError instanceof Error) {
|
|
6995
|
+
console.error(` Message: ${loadError.message}`);
|
|
6996
|
+
if (loadError.stack) {
|
|
6997
|
+
console.error(` Stack: ${loadError.stack.split("\n").slice(0, 3).join("\n ")}`);
|
|
6998
|
+
}
|
|
6999
|
+
}
|
|
7000
|
+
console.error("\u{1F4A1} Falling back to full page reload\n");
|
|
5607
7001
|
window.location.href = nextUrl;
|
|
5608
7002
|
return false;
|
|
5609
7003
|
}
|
|
@@ -5708,7 +7102,11 @@ async function handleNormalRoute(nextUrl, json, routes, setState) {
|
|
|
5708
7102
|
searchParams: Object.fromEntries(url.searchParams.entries())
|
|
5709
7103
|
};
|
|
5710
7104
|
setRouterData(routerData);
|
|
5711
|
-
const
|
|
7105
|
+
const prefetched = prefetchedRoutes.get(matched.route);
|
|
7106
|
+
const components = prefetched ? await prefetched : await matched.route.load();
|
|
7107
|
+
if (!prefetched) {
|
|
7108
|
+
prefetchedRoutes.set(matched.route, Promise.resolve(components));
|
|
7109
|
+
}
|
|
5712
7110
|
window.scrollTo({
|
|
5713
7111
|
top: 0,
|
|
5714
7112
|
behavior: "smooth"
|
|
@@ -5747,7 +7145,7 @@ async function navigate(nextUrl, handlers, options) {
|
|
|
5747
7145
|
}
|
|
5748
7146
|
}
|
|
5749
7147
|
if (!ok) {
|
|
5750
|
-
if (json
|
|
7148
|
+
if (json?.redirect) {
|
|
5751
7149
|
window.location.href = json.redirect.destination;
|
|
5752
7150
|
return;
|
|
5753
7151
|
}
|
|
@@ -5768,6 +7166,47 @@ async function navigate(nextUrl, handlers, options) {
|
|
|
5768
7166
|
window.location.href = nextUrl;
|
|
5769
7167
|
}
|
|
5770
7168
|
}
|
|
7169
|
+
var prefetchedRoutes = /* @__PURE__ */ new WeakMap();
|
|
7170
|
+
function prefetchRoute(url, routes, notFoundRoute) {
|
|
7171
|
+
const [pathname] = url.split("?");
|
|
7172
|
+
const matched = matchRouteClient(pathname, routes);
|
|
7173
|
+
if (!matched) {
|
|
7174
|
+
if (notFoundRoute) {
|
|
7175
|
+
const existing2 = prefetchedRoutes.get(notFoundRoute);
|
|
7176
|
+
if (!existing2) {
|
|
7177
|
+
const promise = notFoundRoute.load();
|
|
7178
|
+
prefetchedRoutes.set(notFoundRoute, promise);
|
|
7179
|
+
}
|
|
7180
|
+
}
|
|
7181
|
+
return;
|
|
7182
|
+
}
|
|
7183
|
+
const existing = prefetchedRoutes.get(matched.route);
|
|
7184
|
+
if (!existing) {
|
|
7185
|
+
const promise = matched.route.load();
|
|
7186
|
+
prefetchedRoutes.set(matched.route, promise);
|
|
7187
|
+
}
|
|
7188
|
+
}
|
|
7189
|
+
function createHoverHandler(routes, notFoundRoute) {
|
|
7190
|
+
return function handleHover(ev) {
|
|
7191
|
+
try {
|
|
7192
|
+
const target = ev.target;
|
|
7193
|
+
if (!target) return;
|
|
7194
|
+
const anchor = target.closest("a[href]");
|
|
7195
|
+
if (!anchor) return;
|
|
7196
|
+
const href = anchor.getAttribute("href");
|
|
7197
|
+
if (!href) return;
|
|
7198
|
+
if (href.startsWith("#")) return;
|
|
7199
|
+
const url = new URL(href, window.location.href);
|
|
7200
|
+
if (url.origin !== window.location.origin) return;
|
|
7201
|
+
if (anchor.target && anchor.target !== "_self") return;
|
|
7202
|
+
const nextUrl = url.pathname + url.search;
|
|
7203
|
+
const currentUrl = window.location.pathname + window.location.search;
|
|
7204
|
+
if (nextUrl === currentUrl) return;
|
|
7205
|
+
prefetchRoute(nextUrl, routes, notFoundRoute);
|
|
7206
|
+
} catch (error) {
|
|
7207
|
+
}
|
|
7208
|
+
};
|
|
7209
|
+
}
|
|
5771
7210
|
function createClickHandler(navigate2) {
|
|
5772
7211
|
return function handleClick(ev) {
|
|
5773
7212
|
try {
|
|
@@ -5878,17 +7317,20 @@ function AppShell({
|
|
|
5878
7317
|
}
|
|
5879
7318
|
const handleClick = createClickHandler(handleNavigateInternal);
|
|
5880
7319
|
const handlePopState = createPopStateHandler(handleNavigateInternal);
|
|
7320
|
+
const handleHover = createHoverHandler(routes, notFoundRoute);
|
|
5881
7321
|
window.addEventListener("click", handleClick, false);
|
|
5882
7322
|
window.addEventListener("popstate", handlePopState, false);
|
|
7323
|
+
window.addEventListener("mouseover", handleHover, false);
|
|
5883
7324
|
return () => {
|
|
5884
7325
|
isMounted = false;
|
|
5885
7326
|
window.removeEventListener("click", handleClick, false);
|
|
5886
7327
|
window.removeEventListener("popstate", handlePopState, false);
|
|
7328
|
+
window.removeEventListener("mouseover", handleHover, false);
|
|
5887
7329
|
};
|
|
5888
|
-
}, []);
|
|
7330
|
+
}, [routes, notFoundRoute]);
|
|
5889
7331
|
useEffect(() => {
|
|
5890
7332
|
const handleDataRefresh = () => {
|
|
5891
|
-
const freshData = window
|
|
7333
|
+
const freshData = window[WINDOW_DATA_KEY2];
|
|
5892
7334
|
if (!freshData) return;
|
|
5893
7335
|
const currentPathname = window.location.pathname;
|
|
5894
7336
|
const freshPathname = freshData.pathname;
|
|
@@ -5915,6 +7357,91 @@ function AppShell({
|
|
|
5915
7357
|
return /* @__PURE__ */ jsx2(RouterContext.Provider, { value: { navigate: handleNavigate }, children: /* @__PURE__ */ jsx2(RouterView, { state }, routeKey) });
|
|
5916
7358
|
}
|
|
5917
7359
|
|
|
7360
|
+
// modules/runtime/client/hot-reload.ts
|
|
7361
|
+
function setupHotReload2() {
|
|
7362
|
+
const nodeEnv = process.env.NODE_ENV || "production";
|
|
7363
|
+
const isDev = nodeEnv === "development";
|
|
7364
|
+
console.log(`[hot-reload] NODE_ENV: ${nodeEnv}, isDev: ${isDev}`);
|
|
7365
|
+
if (!isDev) {
|
|
7366
|
+
console.log("[hot-reload] Skipping hot reload setup (not in development mode)");
|
|
7367
|
+
return;
|
|
7368
|
+
}
|
|
7369
|
+
console.log("[hot-reload] Setting up hot reload client...");
|
|
7370
|
+
let eventSource = null;
|
|
7371
|
+
let reloadTimeout = null;
|
|
7372
|
+
let reconnectTimeout = null;
|
|
7373
|
+
let reconnectAttempts = 0;
|
|
7374
|
+
const MAX_RECONNECT_ATTEMPTS = 10;
|
|
7375
|
+
const RECONNECT_DELAY = 1e3;
|
|
7376
|
+
const RELOAD_DELAY = 100;
|
|
7377
|
+
function connect() {
|
|
7378
|
+
try {
|
|
7379
|
+
if (eventSource) {
|
|
7380
|
+
console.log("[hot-reload] Closing existing EventSource connection");
|
|
7381
|
+
eventSource.close();
|
|
7382
|
+
}
|
|
7383
|
+
const endpoint = "/__fw/hot";
|
|
7384
|
+
eventSource = new EventSource(endpoint);
|
|
7385
|
+
eventSource.addEventListener("ping", (event) => {
|
|
7386
|
+
if ("data" in event) {
|
|
7387
|
+
console.log("[hot-reload] \u2705 Connected to hot reload server");
|
|
7388
|
+
}
|
|
7389
|
+
reconnectAttempts = 0;
|
|
7390
|
+
});
|
|
7391
|
+
eventSource.addEventListener("message", (event) => {
|
|
7392
|
+
const data = event.data;
|
|
7393
|
+
if (data && typeof data === "string" && data.startsWith("reload:")) {
|
|
7394
|
+
const filePath = data.slice(7);
|
|
7395
|
+
console.log(`[hot-reload] \u{1F4DD} File changed: ${filePath}, reloading...`);
|
|
7396
|
+
if (reloadTimeout) {
|
|
7397
|
+
clearTimeout(reloadTimeout);
|
|
7398
|
+
}
|
|
7399
|
+
reloadTimeout = setTimeout(() => {
|
|
7400
|
+
try {
|
|
7401
|
+
window.location.reload();
|
|
7402
|
+
} catch (error) {
|
|
7403
|
+
console.error("[hot-reload] \u274C Error reloading page:", error);
|
|
7404
|
+
setTimeout(() => window.location.reload(), 100);
|
|
7405
|
+
}
|
|
7406
|
+
}, RELOAD_DELAY);
|
|
7407
|
+
}
|
|
7408
|
+
});
|
|
7409
|
+
eventSource.onopen = () => {
|
|
7410
|
+
reconnectAttempts = 0;
|
|
7411
|
+
};
|
|
7412
|
+
eventSource.onerror = (error) => {
|
|
7413
|
+
const states = ["CONNECTING", "OPEN", "CLOSED"];
|
|
7414
|
+
const state = states[eventSource?.readyState ?? 0] || "UNKNOWN";
|
|
7415
|
+
if (eventSource?.readyState === EventSource.CONNECTING) {
|
|
7416
|
+
console.log("[hot-reload] \u23F3 Still connecting...");
|
|
7417
|
+
return;
|
|
7418
|
+
} else if (eventSource?.readyState === EventSource.OPEN) {
|
|
7419
|
+
console.warn("[hot-reload] \u26A0\uFE0F Connection error (but connection is open):", error);
|
|
7420
|
+
} else {
|
|
7421
|
+
console.warn(`[hot-reload] \u274C Connection closed (readyState: ${state})`);
|
|
7422
|
+
if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
|
|
7423
|
+
reconnectAttempts++;
|
|
7424
|
+
const delay = RECONNECT_DELAY * reconnectAttempts;
|
|
7425
|
+
if (reconnectTimeout) {
|
|
7426
|
+
clearTimeout(reconnectTimeout);
|
|
7427
|
+
}
|
|
7428
|
+
reconnectTimeout = setTimeout(() => {
|
|
7429
|
+
console.log(`[hot-reload] \u{1F504} Reconnecting... (attempt ${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})`);
|
|
7430
|
+
connect();
|
|
7431
|
+
}, delay);
|
|
7432
|
+
} else {
|
|
7433
|
+
console.error("[hot-reload] \u274C Max reconnect attempts reached. Please refresh the page manually.");
|
|
7434
|
+
}
|
|
7435
|
+
}
|
|
7436
|
+
};
|
|
7437
|
+
} catch (error) {
|
|
7438
|
+
console.error("[hot-reload] \u274C Failed to create EventSource:", error);
|
|
7439
|
+
console.error("[hot-reload] EventSource may not be supported in this browser.");
|
|
7440
|
+
}
|
|
7441
|
+
}
|
|
7442
|
+
connect();
|
|
7443
|
+
}
|
|
7444
|
+
|
|
5918
7445
|
// modules/runtime/client/bootstrap.tsx
|
|
5919
7446
|
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
5920
7447
|
async function loadInitialRoute(initialUrl, initialData, routes, notFoundRoute, errorRoute) {
|
|
@@ -5956,101 +7483,91 @@ async function loadInitialRoute(initialUrl, initialData, routes, notFoundRoute,
|
|
|
5956
7483
|
props: initialData?.props ?? {}
|
|
5957
7484
|
};
|
|
5958
7485
|
}
|
|
5959
|
-
function
|
|
5960
|
-
|
|
5961
|
-
|
|
5962
|
-
|
|
5963
|
-
|
|
7486
|
+
function initializeRouterData(initialUrl, initialData) {
|
|
7487
|
+
let routerData = getRouterData();
|
|
7488
|
+
if (!routerData) {
|
|
7489
|
+
const url = new URL(initialUrl, window.location.origin);
|
|
7490
|
+
routerData = {
|
|
7491
|
+
pathname: url.pathname,
|
|
7492
|
+
params: initialData?.params || {},
|
|
7493
|
+
searchParams: Object.fromEntries(url.searchParams.entries())
|
|
7494
|
+
};
|
|
7495
|
+
setRouterData(routerData);
|
|
5964
7496
|
}
|
|
7497
|
+
}
|
|
7498
|
+
async function hydrateInitialRoute(container, initialUrl, initialData, routes, notFoundRoute, errorRoute) {
|
|
5965
7499
|
try {
|
|
5966
|
-
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
|
|
5972
|
-
|
|
5973
|
-
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
console.log("[hot-reload] Reloading page...");
|
|
5979
|
-
window.location.reload();
|
|
5980
|
-
}, 500);
|
|
5981
|
-
}
|
|
5982
|
-
});
|
|
5983
|
-
eventSource.addEventListener("ping", () => {
|
|
5984
|
-
console.log("[hot-reload] \u2713 Connected to hot reload server");
|
|
5985
|
-
});
|
|
5986
|
-
eventSource.onopen = () => {
|
|
5987
|
-
console.log("[hot-reload] \u2713 SSE connection opened");
|
|
5988
|
-
};
|
|
5989
|
-
eventSource.onerror = (error) => {
|
|
5990
|
-
const states = ["CONNECTING", "OPEN", "CLOSED"];
|
|
5991
|
-
const state = states[eventSource.readyState] || "UNKNOWN";
|
|
5992
|
-
if (eventSource.readyState === EventSource.CONNECTING) {
|
|
5993
|
-
console.log("[hot-reload] Connecting...");
|
|
5994
|
-
} else if (eventSource.readyState === EventSource.OPEN) {
|
|
5995
|
-
console.warn("[hot-reload] Connection error (but connection is open):", error);
|
|
5996
|
-
} else {
|
|
5997
|
-
console.log("[hot-reload] Connection closed (readyState:", state, ")");
|
|
7500
|
+
const initialState = await loadInitialRoute(
|
|
7501
|
+
initialUrl,
|
|
7502
|
+
initialData,
|
|
7503
|
+
routes,
|
|
7504
|
+
notFoundRoute,
|
|
7505
|
+
errorRoute
|
|
7506
|
+
);
|
|
7507
|
+
if (initialData?.metadata) {
|
|
7508
|
+
try {
|
|
7509
|
+
applyMetadata(initialData.metadata);
|
|
7510
|
+
} catch (metadataError) {
|
|
7511
|
+
console.warn("[client] Error applying metadata:", metadataError);
|
|
5998
7512
|
}
|
|
5999
|
-
}
|
|
7513
|
+
}
|
|
7514
|
+
hydrateRoot(
|
|
7515
|
+
container,
|
|
7516
|
+
/* @__PURE__ */ jsx3(
|
|
7517
|
+
AppShell,
|
|
7518
|
+
{
|
|
7519
|
+
initialState,
|
|
7520
|
+
routes,
|
|
7521
|
+
notFoundRoute,
|
|
7522
|
+
errorRoute
|
|
7523
|
+
}
|
|
7524
|
+
)
|
|
7525
|
+
);
|
|
6000
7526
|
} catch (error) {
|
|
6001
|
-
console.
|
|
7527
|
+
console.error(
|
|
7528
|
+
"[client] Error loading initial route components for",
|
|
7529
|
+
initialUrl,
|
|
7530
|
+
error
|
|
7531
|
+
);
|
|
7532
|
+
throw error;
|
|
6002
7533
|
}
|
|
6003
7534
|
}
|
|
6004
7535
|
function bootstrapClient(routes, notFoundRoute, errorRoute = null) {
|
|
6005
|
-
console.log("[client] Bootstrap starting, setting up hot reload...");
|
|
6006
7536
|
setupHotReload2();
|
|
6007
|
-
(async
|
|
6008
|
-
const container = document.getElementById(APP_CONTAINER_ID2);
|
|
6009
|
-
const initialData = getWindowData();
|
|
6010
|
-
if (!container) {
|
|
6011
|
-
console.error(`Container #${APP_CONTAINER_ID2} not found for hydration`);
|
|
6012
|
-
return;
|
|
6013
|
-
}
|
|
6014
|
-
const initialUrl = window.location.pathname + window.location.search;
|
|
6015
|
-
let routerData = getRouterData();
|
|
6016
|
-
if (!routerData) {
|
|
6017
|
-
const url = new URL(initialUrl, window.location.origin);
|
|
6018
|
-
routerData = {
|
|
6019
|
-
pathname: url.pathname,
|
|
6020
|
-
params: initialData?.params || {},
|
|
6021
|
-
searchParams: Object.fromEntries(url.searchParams.entries())
|
|
6022
|
-
};
|
|
6023
|
-
setRouterData(routerData);
|
|
6024
|
-
}
|
|
7537
|
+
(async () => {
|
|
6025
7538
|
try {
|
|
6026
|
-
const
|
|
7539
|
+
const container = document.getElementById(APP_CONTAINER_ID2);
|
|
7540
|
+
if (!container) {
|
|
7541
|
+
console.error(`
|
|
7542
|
+
\u274C [client] Hydration failed: Container #${APP_CONTAINER_ID2} not found`);
|
|
7543
|
+
console.error("\u{1F4A1} This usually means:");
|
|
7544
|
+
console.error(" \u2022 The HTML structure doesn't match what React expects");
|
|
7545
|
+
console.error(" \u2022 The container was removed before hydration");
|
|
7546
|
+
console.error(" \u2022 There's a mismatch between SSR and client HTML\n");
|
|
7547
|
+
return;
|
|
7548
|
+
}
|
|
7549
|
+
const initialData = getWindowData();
|
|
7550
|
+
const initialUrl = window.location.pathname + window.location.search;
|
|
7551
|
+
initializeRouterData(initialUrl, initialData);
|
|
7552
|
+
await hydrateInitialRoute(
|
|
7553
|
+
container,
|
|
6027
7554
|
initialUrl,
|
|
6028
7555
|
initialData,
|
|
6029
7556
|
routes,
|
|
6030
7557
|
notFoundRoute,
|
|
6031
7558
|
errorRoute
|
|
6032
7559
|
);
|
|
6033
|
-
if (initialData?.metadata) {
|
|
6034
|
-
applyMetadata(initialData.metadata);
|
|
6035
|
-
}
|
|
6036
|
-
hydrateRoot(
|
|
6037
|
-
container,
|
|
6038
|
-
/* @__PURE__ */ jsx3(
|
|
6039
|
-
AppShell,
|
|
6040
|
-
{
|
|
6041
|
-
initialState,
|
|
6042
|
-
routes,
|
|
6043
|
-
notFoundRoute,
|
|
6044
|
-
errorRoute
|
|
6045
|
-
}
|
|
6046
|
-
)
|
|
6047
|
-
);
|
|
6048
7560
|
} catch (error) {
|
|
6049
|
-
console.error(
|
|
6050
|
-
|
|
6051
|
-
|
|
6052
|
-
error
|
|
6053
|
-
|
|
7561
|
+
console.error("\n\u274C [client] Fatal error during bootstrap:");
|
|
7562
|
+
console.error(error);
|
|
7563
|
+
if (error instanceof Error) {
|
|
7564
|
+
console.error("\nError details:");
|
|
7565
|
+
console.error(` Message: ${error.message}`);
|
|
7566
|
+
if (error.stack) {
|
|
7567
|
+
console.error(` Stack: ${error.stack}`);
|
|
7568
|
+
}
|
|
7569
|
+
}
|
|
7570
|
+
console.error("\n\u{1F4A1} Attempting page reload to recover...\n");
|
|
6054
7571
|
window.location.reload();
|
|
6055
7572
|
}
|
|
6056
7573
|
})();
|
|
@@ -6078,11 +7595,11 @@ var ValidationError = class extends Error {
|
|
|
6078
7595
|
format() {
|
|
6079
7596
|
const formatted = {};
|
|
6080
7597
|
for (const error of this.errors) {
|
|
6081
|
-
const
|
|
6082
|
-
if (!formatted[
|
|
6083
|
-
formatted[
|
|
7598
|
+
const path28 = error.path.join(".");
|
|
7599
|
+
if (!formatted[path28]) {
|
|
7600
|
+
formatted[path28] = [];
|
|
6084
7601
|
}
|
|
6085
|
-
formatted[
|
|
7602
|
+
formatted[path28].push(error.message);
|
|
6086
7603
|
}
|
|
6087
7604
|
return formatted;
|
|
6088
7605
|
}
|