@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.cjs
CHANGED
|
@@ -116,7 +116,7 @@ module.exports = __toCommonJS(src_exports);
|
|
|
116
116
|
|
|
117
117
|
// src/server.ts
|
|
118
118
|
var import_fs18 = __toESM(require("fs"));
|
|
119
|
-
var
|
|
119
|
+
var import_path25 = __toESM(require("path"));
|
|
120
120
|
|
|
121
121
|
// modules/server/utils/server-dir.ts
|
|
122
122
|
var import_fs = __toESM(require("fs"));
|
|
@@ -293,9 +293,26 @@ function loadServerHookForDir(currentDir) {
|
|
|
293
293
|
generateStaticParams: null
|
|
294
294
|
};
|
|
295
295
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
296
|
+
let middlewares = [];
|
|
297
|
+
const rawMiddlewares = mod?.[NAMING.BEFORE_MIDDLEWARES];
|
|
298
|
+
if (rawMiddlewares !== void 0) {
|
|
299
|
+
if (!Array.isArray(rawMiddlewares)) {
|
|
300
|
+
console.warn(
|
|
301
|
+
`[framework][server-hook] ${NAMING.BEFORE_MIDDLEWARES} must be an array in ${file}, ignoring invalid value`
|
|
302
|
+
);
|
|
303
|
+
} else {
|
|
304
|
+
for (let i = 0; i < rawMiddlewares.length; i++) {
|
|
305
|
+
const mw = rawMiddlewares[i];
|
|
306
|
+
if (typeof mw !== "function") {
|
|
307
|
+
console.warn(
|
|
308
|
+
`[framework][server-hook] Middleware at index ${i} in ${NAMING.BEFORE_MIDDLEWARES} is not a function in ${file}, skipping`
|
|
309
|
+
);
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
middlewares.push(mw);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
299
316
|
const serverHook = typeof mod?.[NAMING.GET_SERVER_DATA_FN] === "function" ? mod[NAMING.GET_SERVER_DATA_FN] : null;
|
|
300
317
|
const dynamic = mod?.[NAMING.RENDER_TYPE_CONST] === "force-static" || mod?.[NAMING.RENDER_TYPE_CONST] === "force-dynamic" ? mod.dynamic : "auto";
|
|
301
318
|
const generateStaticParams = typeof mod?.[NAMING.GENERATE_SSG_PARAMS] === "function" ? mod[NAMING.GENERATE_SSG_PARAMS] : null;
|
|
@@ -335,6 +352,50 @@ function loadLayoutServerHook(layoutFile) {
|
|
|
335
352
|
}
|
|
336
353
|
|
|
337
354
|
// modules/router/loader-pages.ts
|
|
355
|
+
function validateRoutes(routes, appDir) {
|
|
356
|
+
const routePatterns = /* @__PURE__ */ new Map();
|
|
357
|
+
const errors = [];
|
|
358
|
+
const warnings = [];
|
|
359
|
+
for (const route of routes) {
|
|
360
|
+
const existing = routePatterns.get(route.pattern) || [];
|
|
361
|
+
existing.push(route);
|
|
362
|
+
routePatterns.set(route.pattern, existing);
|
|
363
|
+
}
|
|
364
|
+
for (const [pattern, duplicateRoutes] of routePatterns.entries()) {
|
|
365
|
+
if (duplicateRoutes.length > 1) {
|
|
366
|
+
const files = duplicateRoutes.map(
|
|
367
|
+
(r) => r.pageFile ? import_path4.default.relative(appDir, r.pageFile) : "unknown"
|
|
368
|
+
).join(", ");
|
|
369
|
+
errors.push(
|
|
370
|
+
`Duplicate route pattern "${pattern}" found in multiple files:
|
|
371
|
+
${files}
|
|
372
|
+
\u{1F4A1} Suggestion: Ensure each route has a unique path pattern`
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
for (const route of routes) {
|
|
377
|
+
if (!route.pageFile || !import_fs4.default.existsSync(route.pageFile)) {
|
|
378
|
+
warnings.push(
|
|
379
|
+
`Route pattern "${route.pattern}" references a missing page file`
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
if (errors.length > 0) {
|
|
384
|
+
const errorMessage = [
|
|
385
|
+
"\u274C Route validation failed:",
|
|
386
|
+
"",
|
|
387
|
+
...errors,
|
|
388
|
+
"",
|
|
389
|
+
"\u{1F4A1} Please fix the errors above before starting the server."
|
|
390
|
+
].join("\n");
|
|
391
|
+
throw new Error(errorMessage);
|
|
392
|
+
}
|
|
393
|
+
if (warnings.length > 0 && process.env.NODE_ENV === "development") {
|
|
394
|
+
console.warn("\n\u26A0\uFE0F Route warnings:");
|
|
395
|
+
warnings.forEach((warning) => console.warn(` \u2022 ${warning}`));
|
|
396
|
+
console.warn("");
|
|
397
|
+
}
|
|
398
|
+
}
|
|
338
399
|
function loadRoutes(appDir) {
|
|
339
400
|
if (!import_fs4.default.existsSync(appDir)) {
|
|
340
401
|
return [];
|
|
@@ -389,6 +450,7 @@ function loadRoutes(appDir) {
|
|
|
389
450
|
}
|
|
390
451
|
}
|
|
391
452
|
walk(appDir);
|
|
453
|
+
validateRoutes(routes, appDir);
|
|
392
454
|
return routes;
|
|
393
455
|
}
|
|
394
456
|
|
|
@@ -752,9 +814,12 @@ function writeClientBoostrapManifest(projectRoot) {
|
|
|
752
814
|
lines.push("");
|
|
753
815
|
lines.push(`import { bootstrapClient } from "@lolyjs/core/runtime"`);
|
|
754
816
|
lines.push("");
|
|
755
|
-
lines.push(
|
|
756
|
-
|
|
757
|
-
);
|
|
817
|
+
lines.push(`try {`);
|
|
818
|
+
lines.push(` bootstrapClient(routes as ClientRouteLoaded[], notFoundRoute, errorRoute);`);
|
|
819
|
+
lines.push(`} catch (error) {`);
|
|
820
|
+
lines.push(` console.error("[bootstrap] Fatal error during bootstrap:", error);`);
|
|
821
|
+
lines.push(` throw error;`);
|
|
822
|
+
lines.push(`}`);
|
|
758
823
|
import_fs7.default.writeFileSync(manifestPath, lines.join("\n"), "utf-8");
|
|
759
824
|
}
|
|
760
825
|
function writeRoutesManifest({
|
|
@@ -1092,24 +1157,210 @@ function loadWssRoutes(appDir) {
|
|
|
1092
1157
|
}
|
|
1093
1158
|
|
|
1094
1159
|
// modules/router/route-loader.ts
|
|
1160
|
+
var SKIP_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
1161
|
+
"node_modules",
|
|
1162
|
+
".git",
|
|
1163
|
+
".loly",
|
|
1164
|
+
"dist",
|
|
1165
|
+
"build",
|
|
1166
|
+
".next",
|
|
1167
|
+
".cache",
|
|
1168
|
+
"coverage",
|
|
1169
|
+
".vscode",
|
|
1170
|
+
".idea",
|
|
1171
|
+
".turbo",
|
|
1172
|
+
".swc"
|
|
1173
|
+
]);
|
|
1174
|
+
function getRelevantFiles(appDir, projectRoot) {
|
|
1175
|
+
const files = [];
|
|
1176
|
+
const projectRootNormalized = import_path12.default.resolve(projectRoot);
|
|
1177
|
+
function walk(currentDir) {
|
|
1178
|
+
if (!import_fs10.default.existsSync(currentDir)) {
|
|
1179
|
+
return;
|
|
1180
|
+
}
|
|
1181
|
+
const entries = import_fs10.default.readdirSync(currentDir, { withFileTypes: true });
|
|
1182
|
+
for (const entry of entries) {
|
|
1183
|
+
const fullPath = import_path12.default.join(currentDir, entry.name);
|
|
1184
|
+
if (entry.isDirectory()) {
|
|
1185
|
+
if (SKIP_DIRECTORIES.has(entry.name)) {
|
|
1186
|
+
continue;
|
|
1187
|
+
}
|
|
1188
|
+
if (entry.name.startsWith(".")) {
|
|
1189
|
+
continue;
|
|
1190
|
+
}
|
|
1191
|
+
walk(fullPath);
|
|
1192
|
+
} else {
|
|
1193
|
+
const ext = import_path12.default.extname(entry.name);
|
|
1194
|
+
if ([".ts", ".tsx", ".js", ".jsx"].includes(ext)) {
|
|
1195
|
+
files.push(fullPath);
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
walk(projectRootNormalized);
|
|
1201
|
+
return files;
|
|
1202
|
+
}
|
|
1203
|
+
function hasFilesChanged(appDir, projectRoot, cachedStats) {
|
|
1204
|
+
const currentFiles = getRelevantFiles(appDir, projectRoot);
|
|
1205
|
+
const currentFilesSet = new Set(currentFiles);
|
|
1206
|
+
for (const [filePath] of cachedStats.entries()) {
|
|
1207
|
+
if (!currentFilesSet.has(filePath)) {
|
|
1208
|
+
return true;
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
for (const filePath of currentFiles) {
|
|
1212
|
+
if (!import_fs10.default.existsSync(filePath)) {
|
|
1213
|
+
continue;
|
|
1214
|
+
}
|
|
1215
|
+
const stats = import_fs10.default.statSync(filePath);
|
|
1216
|
+
const cachedStat = cachedStats.get(filePath);
|
|
1217
|
+
if (!cachedStat) {
|
|
1218
|
+
return true;
|
|
1219
|
+
}
|
|
1220
|
+
if (stats.mtimeMs !== cachedStat.mtime || stats.size !== cachedStat.size) {
|
|
1221
|
+
return true;
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
return false;
|
|
1225
|
+
}
|
|
1226
|
+
function buildFileStats(files) {
|
|
1227
|
+
const statsMap = /* @__PURE__ */ new Map();
|
|
1228
|
+
for (const filePath of files) {
|
|
1229
|
+
if (import_fs10.default.existsSync(filePath)) {
|
|
1230
|
+
const stats = import_fs10.default.statSync(filePath);
|
|
1231
|
+
statsMap.set(filePath, {
|
|
1232
|
+
mtime: stats.mtimeMs,
|
|
1233
|
+
size: stats.size
|
|
1234
|
+
});
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
return statsMap;
|
|
1238
|
+
}
|
|
1095
1239
|
var FilesystemRouteLoader = class {
|
|
1096
|
-
|
|
1240
|
+
// Maximum cache age in ms (1 second fallback)
|
|
1241
|
+
constructor(appDir, projectRoot = appDir) {
|
|
1097
1242
|
this.appDir = appDir;
|
|
1243
|
+
this.projectRoot = projectRoot;
|
|
1244
|
+
this.cache = null;
|
|
1245
|
+
this.cacheMaxAge = 1e3;
|
|
1246
|
+
if (this.projectRoot === this.appDir) {
|
|
1247
|
+
let current = import_path12.default.resolve(this.appDir);
|
|
1248
|
+
while (current !== import_path12.default.dirname(current)) {
|
|
1249
|
+
if (import_fs10.default.existsSync(import_path12.default.join(current, "package.json"))) {
|
|
1250
|
+
this.projectRoot = current;
|
|
1251
|
+
break;
|
|
1252
|
+
}
|
|
1253
|
+
current = import_path12.default.dirname(current);
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
/**
|
|
1258
|
+
* Invalidates the cache, forcing a reload on next access.
|
|
1259
|
+
*/
|
|
1260
|
+
invalidateCache() {
|
|
1261
|
+
this.cache = null;
|
|
1262
|
+
}
|
|
1263
|
+
/**
|
|
1264
|
+
* Checks if cache is still valid, invalidates if files changed.
|
|
1265
|
+
*/
|
|
1266
|
+
ensureCacheValid() {
|
|
1267
|
+
if (!this.cache) {
|
|
1268
|
+
return;
|
|
1269
|
+
}
|
|
1270
|
+
const now = Date.now();
|
|
1271
|
+
if (now - this.cache.timestamp > this.cacheMaxAge) {
|
|
1272
|
+
if (hasFilesChanged(this.appDir, this.projectRoot, this.cache.fileStats)) {
|
|
1273
|
+
this.cache = null;
|
|
1274
|
+
} else {
|
|
1275
|
+
this.cache.timestamp = now;
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1098
1278
|
}
|
|
1099
1279
|
loadRoutes() {
|
|
1100
|
-
|
|
1280
|
+
this.ensureCacheValid();
|
|
1281
|
+
if (!this.cache || hasFilesChanged(this.appDir, this.projectRoot, this.cache.fileStats)) {
|
|
1282
|
+
const routes = loadRoutes(this.appDir);
|
|
1283
|
+
const files = getRelevantFiles(this.appDir, this.projectRoot);
|
|
1284
|
+
const fileStats = buildFileStats(files);
|
|
1285
|
+
this.cache = {
|
|
1286
|
+
routes,
|
|
1287
|
+
apiRoutes: this.cache?.apiRoutes || [],
|
|
1288
|
+
wssRoutes: this.cache?.wssRoutes || [],
|
|
1289
|
+
notFoundRoute: this.cache?.notFoundRoute ?? null,
|
|
1290
|
+
errorRoute: this.cache?.errorRoute ?? null,
|
|
1291
|
+
fileStats,
|
|
1292
|
+
timestamp: Date.now()
|
|
1293
|
+
};
|
|
1294
|
+
}
|
|
1295
|
+
return this.cache.routes;
|
|
1101
1296
|
}
|
|
1102
1297
|
loadApiRoutes() {
|
|
1103
|
-
|
|
1298
|
+
this.ensureCacheValid();
|
|
1299
|
+
if (!this.cache) {
|
|
1300
|
+
this.loadRoutes();
|
|
1301
|
+
}
|
|
1302
|
+
if (!this.cache) {
|
|
1303
|
+
throw new Error("Failed to initialize route cache");
|
|
1304
|
+
}
|
|
1305
|
+
if (hasFilesChanged(this.appDir, this.projectRoot, this.cache.fileStats) || this.cache.apiRoutes.length === 0) {
|
|
1306
|
+
const files = getRelevantFiles(this.appDir, this.projectRoot);
|
|
1307
|
+
const fileStats = buildFileStats(files);
|
|
1308
|
+
this.cache.apiRoutes = loadApiRoutes(this.appDir);
|
|
1309
|
+
this.cache.fileStats = fileStats;
|
|
1310
|
+
this.cache.timestamp = Date.now();
|
|
1311
|
+
}
|
|
1312
|
+
return this.cache.apiRoutes;
|
|
1104
1313
|
}
|
|
1105
1314
|
loadWssRoutes() {
|
|
1106
|
-
|
|
1315
|
+
this.ensureCacheValid();
|
|
1316
|
+
if (!this.cache) {
|
|
1317
|
+
this.loadRoutes();
|
|
1318
|
+
}
|
|
1319
|
+
if (!this.cache) {
|
|
1320
|
+
throw new Error("Failed to initialize route cache");
|
|
1321
|
+
}
|
|
1322
|
+
if (hasFilesChanged(this.appDir, this.projectRoot, this.cache.fileStats) || this.cache.wssRoutes.length === 0) {
|
|
1323
|
+
const files = getRelevantFiles(this.appDir, this.projectRoot);
|
|
1324
|
+
const fileStats = buildFileStats(files);
|
|
1325
|
+
this.cache.wssRoutes = loadWssRoutes(this.appDir);
|
|
1326
|
+
this.cache.fileStats = fileStats;
|
|
1327
|
+
this.cache.timestamp = Date.now();
|
|
1328
|
+
}
|
|
1329
|
+
return this.cache.wssRoutes;
|
|
1107
1330
|
}
|
|
1108
1331
|
loadNotFoundRoute() {
|
|
1109
|
-
|
|
1332
|
+
this.ensureCacheValid();
|
|
1333
|
+
if (!this.cache) {
|
|
1334
|
+
this.loadRoutes();
|
|
1335
|
+
}
|
|
1336
|
+
if (!this.cache) {
|
|
1337
|
+
throw new Error("Failed to initialize route cache");
|
|
1338
|
+
}
|
|
1339
|
+
if (hasFilesChanged(this.appDir, this.projectRoot, this.cache.fileStats) || this.cache.notFoundRoute === void 0) {
|
|
1340
|
+
const files = getRelevantFiles(this.appDir, this.projectRoot);
|
|
1341
|
+
const fileStats = buildFileStats(files);
|
|
1342
|
+
this.cache.notFoundRoute = loadNotFoundRouteFromFilesystem(this.appDir);
|
|
1343
|
+
this.cache.fileStats = fileStats;
|
|
1344
|
+
this.cache.timestamp = Date.now();
|
|
1345
|
+
}
|
|
1346
|
+
return this.cache.notFoundRoute;
|
|
1110
1347
|
}
|
|
1111
1348
|
loadErrorRoute() {
|
|
1112
|
-
|
|
1349
|
+
this.ensureCacheValid();
|
|
1350
|
+
if (!this.cache) {
|
|
1351
|
+
this.loadRoutes();
|
|
1352
|
+
}
|
|
1353
|
+
if (!this.cache) {
|
|
1354
|
+
throw new Error("Failed to initialize route cache");
|
|
1355
|
+
}
|
|
1356
|
+
if (hasFilesChanged(this.appDir, this.projectRoot, this.cache.fileStats) || this.cache.errorRoute === void 0) {
|
|
1357
|
+
const files = getRelevantFiles(this.appDir, this.projectRoot);
|
|
1358
|
+
const fileStats = buildFileStats(files);
|
|
1359
|
+
this.cache.errorRoute = loadErrorRouteFromFilesystem(this.appDir);
|
|
1360
|
+
this.cache.fileStats = fileStats;
|
|
1361
|
+
this.cache.timestamp = Date.now();
|
|
1362
|
+
}
|
|
1363
|
+
return this.cache.errorRoute;
|
|
1113
1364
|
}
|
|
1114
1365
|
loadRouteChunks() {
|
|
1115
1366
|
return {};
|
|
@@ -1118,27 +1369,67 @@ var FilesystemRouteLoader = class {
|
|
|
1118
1369
|
var ManifestRouteLoader = class {
|
|
1119
1370
|
constructor(projectRoot) {
|
|
1120
1371
|
this.projectRoot = projectRoot;
|
|
1372
|
+
this.cache = {};
|
|
1373
|
+
}
|
|
1374
|
+
/**
|
|
1375
|
+
* Gets the manifest, using cache if available.
|
|
1376
|
+
* The manifest is read once and cached for the lifetime of the loader.
|
|
1377
|
+
*/
|
|
1378
|
+
getManifest() {
|
|
1379
|
+
if (this.cache.manifest !== void 0) {
|
|
1380
|
+
return this.cache.manifest;
|
|
1381
|
+
}
|
|
1382
|
+
const manifest = readManifest(this.projectRoot);
|
|
1383
|
+
this.cache.manifest = manifest;
|
|
1384
|
+
return manifest;
|
|
1121
1385
|
}
|
|
1122
1386
|
loadRoutes() {
|
|
1387
|
+
if (this.cache.routes) {
|
|
1388
|
+
return this.cache.routes;
|
|
1389
|
+
}
|
|
1123
1390
|
const { routes } = loadRoutesFromManifest(this.projectRoot);
|
|
1391
|
+
this.cache.routes = routes;
|
|
1124
1392
|
return routes;
|
|
1125
1393
|
}
|
|
1126
1394
|
loadApiRoutes() {
|
|
1395
|
+
if (this.cache.apiRoutes) {
|
|
1396
|
+
return this.cache.apiRoutes;
|
|
1397
|
+
}
|
|
1127
1398
|
const { apiRoutes } = loadRoutesFromManifest(this.projectRoot);
|
|
1399
|
+
this.cache.apiRoutes = apiRoutes;
|
|
1128
1400
|
return apiRoutes;
|
|
1129
1401
|
}
|
|
1130
1402
|
loadWssRoutes() {
|
|
1403
|
+
if (this.cache.wssRoutes) {
|
|
1404
|
+
return this.cache.wssRoutes;
|
|
1405
|
+
}
|
|
1131
1406
|
const { wssRoutes } = loadRoutesFromManifest(this.projectRoot);
|
|
1407
|
+
this.cache.wssRoutes = wssRoutes;
|
|
1132
1408
|
return wssRoutes;
|
|
1133
1409
|
}
|
|
1134
1410
|
loadNotFoundRoute() {
|
|
1135
|
-
|
|
1411
|
+
if (this.cache.notFoundRoute !== void 0) {
|
|
1412
|
+
return this.cache.notFoundRoute;
|
|
1413
|
+
}
|
|
1414
|
+
const route = loadNotFoundFromManifest(this.projectRoot);
|
|
1415
|
+
this.cache.notFoundRoute = route;
|
|
1416
|
+
return route;
|
|
1136
1417
|
}
|
|
1137
1418
|
loadErrorRoute() {
|
|
1138
|
-
|
|
1419
|
+
if (this.cache.errorRoute !== void 0) {
|
|
1420
|
+
return this.cache.errorRoute;
|
|
1421
|
+
}
|
|
1422
|
+
const route = loadErrorFromManifest(this.projectRoot);
|
|
1423
|
+
this.cache.errorRoute = route;
|
|
1424
|
+
return route;
|
|
1139
1425
|
}
|
|
1140
1426
|
loadRouteChunks() {
|
|
1141
|
-
|
|
1427
|
+
if (this.cache.routeChunks) {
|
|
1428
|
+
return this.cache.routeChunks;
|
|
1429
|
+
}
|
|
1430
|
+
const chunks = loadChunksFromManifest(this.projectRoot);
|
|
1431
|
+
this.cache.routeChunks = chunks;
|
|
1432
|
+
return chunks;
|
|
1142
1433
|
}
|
|
1143
1434
|
};
|
|
1144
1435
|
function loadNotFoundRouteFromFilesystem(appDir) {
|
|
@@ -1271,7 +1562,8 @@ function loadAliasesFromTsconfig(projectRoot) {
|
|
|
1271
1562
|
try {
|
|
1272
1563
|
tsconfig = JSON.parse(import_fs11.default.readFileSync(tsconfigPath, "utf-8"));
|
|
1273
1564
|
} catch (err) {
|
|
1274
|
-
console.warn("[framework] Could not read tsconfig.json:", err);
|
|
1565
|
+
console.warn("\u26A0\uFE0F [framework] Could not read tsconfig.json:", err instanceof Error ? err.message : String(err));
|
|
1566
|
+
console.warn("\u{1F4A1} Using default path aliases. For custom aliases, ensure tsconfig.json is valid.");
|
|
1275
1567
|
aliases["@app"] = import_path13.default.resolve(projectRoot, "app");
|
|
1276
1568
|
return aliases;
|
|
1277
1569
|
}
|
|
@@ -1325,7 +1617,7 @@ function copyStaticAssets(projectRoot, outDir) {
|
|
|
1325
1617
|
}
|
|
1326
1618
|
}
|
|
1327
1619
|
}
|
|
1328
|
-
function generateAssetManifest(outDir) {
|
|
1620
|
+
function generateAssetManifest(outDir, stats) {
|
|
1329
1621
|
const manifest = {
|
|
1330
1622
|
client: {
|
|
1331
1623
|
js: "client.js",
|
|
@@ -1337,27 +1629,128 @@ function generateAssetManifest(outDir) {
|
|
|
1337
1629
|
return manifest;
|
|
1338
1630
|
}
|
|
1339
1631
|
const files = import_fs11.default.readdirSync(outDir);
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1632
|
+
if (stats) {
|
|
1633
|
+
try {
|
|
1634
|
+
const statsJson = stats.toJson({
|
|
1635
|
+
all: false,
|
|
1636
|
+
entrypoints: true,
|
|
1637
|
+
assets: true,
|
|
1638
|
+
chunks: true,
|
|
1639
|
+
chunkRelations: true
|
|
1640
|
+
// Include chunk dependencies
|
|
1641
|
+
});
|
|
1642
|
+
const clientEntrypoint = statsJson.entrypoints?.client;
|
|
1643
|
+
if (clientEntrypoint?.assets) {
|
|
1644
|
+
const clientJsFiles = clientEntrypoint.assets.map((asset) => typeof asset === "string" ? asset : asset.name).filter((name) => name.endsWith(".js"));
|
|
1645
|
+
if (statsJson.chunks && clientEntrypoint.chunks) {
|
|
1646
|
+
const entrypointChunkIds = new Set(
|
|
1647
|
+
Array.isArray(clientEntrypoint.chunks) ? clientEntrypoint.chunks : [clientEntrypoint.chunks]
|
|
1648
|
+
);
|
|
1649
|
+
const dependencyChunks = [];
|
|
1650
|
+
for (const chunk of statsJson.chunks) {
|
|
1651
|
+
if (chunk.id && entrypointChunkIds.has(chunk.id)) {
|
|
1652
|
+
if (chunk.files) {
|
|
1653
|
+
const jsFiles = chunk.files.filter((f) => f.endsWith(".js")).filter((f) => !clientJsFiles.includes(f));
|
|
1654
|
+
dependencyChunks.push(...jsFiles);
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
const visitedChunkIds = new Set(entrypointChunkIds);
|
|
1659
|
+
const chunksToCheck = Array.from(entrypointChunkIds);
|
|
1660
|
+
while (chunksToCheck.length > 0) {
|
|
1661
|
+
const chunkId = chunksToCheck.shift();
|
|
1662
|
+
if (!chunkId) continue;
|
|
1663
|
+
const chunk = statsJson.chunks?.find((c) => c.id === chunkId);
|
|
1664
|
+
if (chunk?.children) {
|
|
1665
|
+
const children = Array.isArray(chunk.children) ? chunk.children : [chunk.children];
|
|
1666
|
+
for (const childId of children) {
|
|
1667
|
+
if (!visitedChunkIds.has(childId)) {
|
|
1668
|
+
visitedChunkIds.add(childId);
|
|
1669
|
+
chunksToCheck.push(childId);
|
|
1670
|
+
const childChunk = statsJson.chunks?.find((c) => c.id === childId);
|
|
1671
|
+
if (childChunk?.files) {
|
|
1672
|
+
const jsFiles = childChunk.files.filter((f) => f.endsWith(".js")).filter((f) => !clientJsFiles.includes(f) && !dependencyChunks.includes(f));
|
|
1673
|
+
dependencyChunks.push(...jsFiles);
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
if (dependencyChunks.length > 0) {
|
|
1680
|
+
clientJsFiles.splice(-1, 0, ...dependencyChunks);
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
if (clientJsFiles.length > 0) {
|
|
1684
|
+
manifest.entrypoints = {
|
|
1685
|
+
client: clientJsFiles
|
|
1686
|
+
};
|
|
1687
|
+
manifest.client.js = clientJsFiles[clientJsFiles.length - 1];
|
|
1688
|
+
const clientCssFiles = clientEntrypoint.assets.map((asset) => typeof asset === "string" ? asset : asset.name).filter((name) => name.endsWith(".css"));
|
|
1689
|
+
if (clientCssFiles.length > 0) {
|
|
1690
|
+
manifest.client.css = clientCssFiles[0];
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
} catch (err) {
|
|
1695
|
+
console.warn("[framework] Failed to extract entrypoints from stats, falling back to file scanning:", err);
|
|
1696
|
+
}
|
|
1343
1697
|
}
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1698
|
+
if (!manifest.client.js) {
|
|
1699
|
+
const clientJsMatch = files.find((f) => /^client\.[\w-]+\.js$/.test(f) || f === "client.js");
|
|
1700
|
+
if (clientJsMatch) {
|
|
1701
|
+
manifest.client.js = clientJsMatch;
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
if (!manifest.client.css) {
|
|
1705
|
+
const clientCssMatch = files.find((f) => /^client\.[\w-]+\.css$/.test(f) || f === "client.css");
|
|
1706
|
+
if (clientCssMatch) {
|
|
1707
|
+
manifest.client.css = clientCssMatch;
|
|
1708
|
+
}
|
|
1347
1709
|
}
|
|
1710
|
+
const sharedChunksToAdd = [];
|
|
1348
1711
|
for (const file of files) {
|
|
1349
1712
|
if (!file.endsWith(".js")) continue;
|
|
1350
1713
|
if (file === manifest.client.js) continue;
|
|
1714
|
+
if (manifest.entrypoints?.client?.includes(file)) continue;
|
|
1351
1715
|
const routeMatch = file.match(/^(route-[^.]+)(\.[\w-]+)?\.js$/);
|
|
1352
1716
|
if (routeMatch) {
|
|
1353
1717
|
const chunkName = routeMatch[1];
|
|
1354
1718
|
manifest.chunks[chunkName] = file;
|
|
1355
1719
|
continue;
|
|
1356
1720
|
}
|
|
1721
|
+
const vendorMatch = file.match(/^(vendor)(\.[\w-]+)?\.js$/);
|
|
1722
|
+
if (vendorMatch) {
|
|
1723
|
+
const chunkName = vendorMatch[1];
|
|
1724
|
+
manifest.chunks[chunkName] = file;
|
|
1725
|
+
sharedChunksToAdd.push(file);
|
|
1726
|
+
continue;
|
|
1727
|
+
}
|
|
1728
|
+
const vendorCommonsMatch = file.match(/^(vendor-commons)(\.[\w-]+)?\.js$/);
|
|
1729
|
+
if (vendorCommonsMatch) {
|
|
1730
|
+
const chunkName = vendorCommonsMatch[1];
|
|
1731
|
+
manifest.chunks[chunkName] = file;
|
|
1732
|
+
sharedChunksToAdd.push(file);
|
|
1733
|
+
continue;
|
|
1734
|
+
}
|
|
1357
1735
|
const numericMatch = file.match(/^(\d+)(\.[\w-]+)?\.js$/);
|
|
1358
1736
|
if (numericMatch) {
|
|
1359
1737
|
const chunkName = numericMatch[1];
|
|
1360
1738
|
manifest.chunks[chunkName] = file;
|
|
1739
|
+
sharedChunksToAdd.push(file);
|
|
1740
|
+
continue;
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
if (sharedChunksToAdd.length > 0 && manifest.entrypoints?.client) {
|
|
1744
|
+
const entrypoints = manifest.entrypoints.client;
|
|
1745
|
+
const mainEntry = entrypoints[entrypoints.length - 1];
|
|
1746
|
+
const uniqueShared = sharedChunksToAdd.filter((f) => !entrypoints.includes(f));
|
|
1747
|
+
entrypoints.splice(-1, 0, ...uniqueShared);
|
|
1748
|
+
if (entrypoints[entrypoints.length - 1] !== mainEntry) {
|
|
1749
|
+
const mainIndex = entrypoints.indexOf(mainEntry);
|
|
1750
|
+
if (mainIndex >= 0) {
|
|
1751
|
+
entrypoints.splice(mainIndex, 1);
|
|
1752
|
+
}
|
|
1753
|
+
entrypoints.push(mainEntry);
|
|
1361
1754
|
}
|
|
1362
1755
|
}
|
|
1363
1756
|
return manifest;
|
|
@@ -1397,9 +1790,7 @@ function createClientConfig(projectRoot, mode) {
|
|
|
1397
1790
|
const outDir = import_path14.default.join(buildDir, "client");
|
|
1398
1791
|
const envPath2 = import_path14.default.join(projectRoot, ".env");
|
|
1399
1792
|
if (import_fs12.default.existsSync(envPath2)) {
|
|
1400
|
-
import_dotenv.default.config({
|
|
1401
|
-
path: envPath2
|
|
1402
|
-
});
|
|
1793
|
+
import_dotenv.default.config({ path: envPath2 });
|
|
1403
1794
|
}
|
|
1404
1795
|
const publicEnv = {};
|
|
1405
1796
|
for (const [key, value] of Object.entries(process.env)) {
|
|
@@ -1458,14 +1849,57 @@ function createClientConfig(projectRoot, mode) {
|
|
|
1458
1849
|
},
|
|
1459
1850
|
plugins: [
|
|
1460
1851
|
new import_core.rspack.DefinePlugin({
|
|
1461
|
-
//
|
|
1462
|
-
|
|
1852
|
+
// Use mode directly to ensure development mode is correctly set
|
|
1853
|
+
// This replaces process.env.NODE_ENV in the client bundle with the literal string value
|
|
1854
|
+
"process.env.NODE_ENV": JSON.stringify(mode),
|
|
1463
1855
|
...publicEnv
|
|
1464
1856
|
}),
|
|
1465
1857
|
new import_core.rspack.CssExtractRspackPlugin({
|
|
1466
1858
|
filename: mode === "production" ? "client.[contenthash].css" : "client.css"
|
|
1467
1859
|
})
|
|
1468
1860
|
],
|
|
1861
|
+
optimization: mode === "production" ? {
|
|
1862
|
+
usedExports: true,
|
|
1863
|
+
sideEffects: false,
|
|
1864
|
+
// More aggressive tree shaking - assume no side effects
|
|
1865
|
+
providedExports: true,
|
|
1866
|
+
concatenateModules: true,
|
|
1867
|
+
// Better for tree shaking
|
|
1868
|
+
minimize: true,
|
|
1869
|
+
removeEmptyChunks: true,
|
|
1870
|
+
mergeDuplicateChunks: true,
|
|
1871
|
+
// Improved code splitting: separate vendor chunks for better caching
|
|
1872
|
+
splitChunks: {
|
|
1873
|
+
chunks: "all",
|
|
1874
|
+
cacheGroups: {
|
|
1875
|
+
// Separate React/React-DOM into dedicated vendor chunk
|
|
1876
|
+
// This improves caching: React rarely changes, so users don't need to re-download it
|
|
1877
|
+
vendor: {
|
|
1878
|
+
test: /[\\/]node_modules[\\/](react|react-dom|scheduler)[\\/]/,
|
|
1879
|
+
name: "vendor",
|
|
1880
|
+
priority: 30,
|
|
1881
|
+
enforce: true,
|
|
1882
|
+
// Force separation even if only used once
|
|
1883
|
+
reuseExistingChunk: true
|
|
1884
|
+
},
|
|
1885
|
+
// Other node_modules dependencies in a separate chunk
|
|
1886
|
+
vendorCommons: {
|
|
1887
|
+
test: /[\\/]node_modules[\\/](?!(react|react-dom|scheduler)[\\/])/,
|
|
1888
|
+
name: "vendor-commons",
|
|
1889
|
+
priority: 10,
|
|
1890
|
+
minChunks: 2,
|
|
1891
|
+
// Only create if used in 2+ chunks
|
|
1892
|
+
reuseExistingChunk: true
|
|
1893
|
+
},
|
|
1894
|
+
// Default: shared application code (not in node_modules)
|
|
1895
|
+
default: {
|
|
1896
|
+
minChunks: 2,
|
|
1897
|
+
priority: 5,
|
|
1898
|
+
reuseExistingChunk: true
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
} : void 0,
|
|
1469
1903
|
infrastructureLogging: {
|
|
1470
1904
|
level: "error"
|
|
1471
1905
|
},
|
|
@@ -1494,7 +1928,13 @@ function startClientBundler(projectRoot, mode = "development") {
|
|
|
1494
1928
|
});
|
|
1495
1929
|
compiler.watch({}, (err, stats) => {
|
|
1496
1930
|
if (err) {
|
|
1497
|
-
console.error("[framework][client] Rspack error:"
|
|
1931
|
+
console.error("\n\u274C [framework][client] Rspack compilation error:");
|
|
1932
|
+
console.error(err);
|
|
1933
|
+
console.error("\n\u{1F4A1} Suggestions:");
|
|
1934
|
+
console.error(" \u2022 Check for syntax errors in your code");
|
|
1935
|
+
console.error(" \u2022 Verify all imports are correct");
|
|
1936
|
+
console.error(" \u2022 Ensure all dependencies are installed");
|
|
1937
|
+
console.error(" \u2022 Try deleting .loly folder and rebuilding\n");
|
|
1498
1938
|
isBuilding = false;
|
|
1499
1939
|
lastBuildTime = Date.now();
|
|
1500
1940
|
if (buildResolve) {
|
|
@@ -1505,17 +1945,20 @@ function startClientBundler(projectRoot, mode = "development") {
|
|
|
1505
1945
|
return;
|
|
1506
1946
|
}
|
|
1507
1947
|
if (!stats) {
|
|
1948
|
+
console.warn("\u26A0\uFE0F [framework][client] Build completed but no stats available");
|
|
1508
1949
|
isBuilding = false;
|
|
1509
1950
|
lastBuildTime = Date.now();
|
|
1510
1951
|
return;
|
|
1511
1952
|
}
|
|
1512
1953
|
if (stats.hasErrors()) {
|
|
1513
|
-
console.error(
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
);
|
|
1954
|
+
console.error("\n\u274C [framework][client] Build failed with errors:\n");
|
|
1955
|
+
console.error(stats.toString("errors-only"));
|
|
1956
|
+
console.error("\n\u{1F4A1} Common fixes:");
|
|
1957
|
+
console.error(" \u2022 Fix syntax errors shown above");
|
|
1958
|
+
console.error(" \u2022 Check for missing imports or dependencies");
|
|
1959
|
+
console.error(" \u2022 Verify TypeScript types are correct\n");
|
|
1517
1960
|
} else {
|
|
1518
|
-
console.log("[framework][client]
|
|
1961
|
+
console.log("\u2705 [framework][client] Client bundle rebuilt successfully");
|
|
1519
1962
|
}
|
|
1520
1963
|
isBuilding = false;
|
|
1521
1964
|
lastBuildTime = Date.now();
|
|
@@ -1550,23 +1993,33 @@ function buildClientBundle(projectRoot) {
|
|
|
1550
1993
|
compiler.close(() => {
|
|
1551
1994
|
});
|
|
1552
1995
|
if (err) {
|
|
1553
|
-
console.error("[framework][client]
|
|
1996
|
+
console.error("\n\u274C [framework][client] Production build error:");
|
|
1997
|
+
console.error(err);
|
|
1998
|
+
console.error("\n\u{1F4A1} Suggestions:");
|
|
1999
|
+
console.error(" \u2022 Check for syntax errors in your code");
|
|
2000
|
+
console.error(" \u2022 Verify all imports are correct");
|
|
2001
|
+
console.error(" \u2022 Ensure all dependencies are installed");
|
|
2002
|
+
console.error(" \u2022 Review the error details above\n");
|
|
1554
2003
|
return reject(err);
|
|
1555
2004
|
}
|
|
1556
2005
|
if (!stats) {
|
|
1557
|
-
const error = new Error("No stats from Rspack");
|
|
1558
|
-
console.error("[framework][client] Build error:", error);
|
|
2006
|
+
const error = new Error("No stats from Rspack - build may have failed silently");
|
|
2007
|
+
console.error("\n\u274C [framework][client] Build error:", error.message);
|
|
2008
|
+
console.error("\u{1F4A1} Try rebuilding or check Rspack configuration\n");
|
|
1559
2009
|
return reject(error);
|
|
1560
2010
|
}
|
|
1561
2011
|
if (stats.hasErrors()) {
|
|
1562
|
-
console.error(
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
);
|
|
1566
|
-
|
|
2012
|
+
console.error("\n\u274C [framework][client] Production build failed:\n");
|
|
2013
|
+
console.error(stats.toString("errors-only"));
|
|
2014
|
+
console.error("\n\u{1F4A1} Common fixes:");
|
|
2015
|
+
console.error(" \u2022 Fix syntax errors shown above");
|
|
2016
|
+
console.error(" \u2022 Check for missing imports or dependencies");
|
|
2017
|
+
console.error(" \u2022 Verify TypeScript types are correct");
|
|
2018
|
+
console.error(" \u2022 Review build configuration\n");
|
|
2019
|
+
return reject(new Error("Client build failed - see errors above"));
|
|
1567
2020
|
}
|
|
1568
2021
|
copyStaticAssets(projectRoot, outDir);
|
|
1569
|
-
const assetManifest = generateAssetManifest(outDir);
|
|
2022
|
+
const assetManifest = generateAssetManifest(outDir, stats);
|
|
1570
2023
|
const manifestPath = import_path15.default.join(projectRoot, BUILD_FOLDER_NAME, "asset-manifest.json");
|
|
1571
2024
|
import_fs13.default.writeFileSync(manifestPath, JSON.stringify(assetManifest, null, 2), "utf-8");
|
|
1572
2025
|
resolve3({ outDir });
|
|
@@ -1650,7 +2103,7 @@ var ReaddirpStream = class extends import_node_stream.Readable {
|
|
|
1650
2103
|
this._directoryFilter = normalizeFilter(opts.directoryFilter);
|
|
1651
2104
|
const statMethod = opts.lstat ? import_promises.lstat : import_promises.stat;
|
|
1652
2105
|
if (wantBigintFsStats) {
|
|
1653
|
-
this._stat = (
|
|
2106
|
+
this._stat = (path28) => statMethod(path28, { bigint: true });
|
|
1654
2107
|
} else {
|
|
1655
2108
|
this._stat = statMethod;
|
|
1656
2109
|
}
|
|
@@ -1675,8 +2128,8 @@ var ReaddirpStream = class extends import_node_stream.Readable {
|
|
|
1675
2128
|
const par = this.parent;
|
|
1676
2129
|
const fil = par && par.files;
|
|
1677
2130
|
if (fil && fil.length > 0) {
|
|
1678
|
-
const { path:
|
|
1679
|
-
const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent,
|
|
2131
|
+
const { path: path28, depth } = par;
|
|
2132
|
+
const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path28));
|
|
1680
2133
|
const awaited = await Promise.all(slice);
|
|
1681
2134
|
for (const entry of awaited) {
|
|
1682
2135
|
if (!entry)
|
|
@@ -1716,20 +2169,20 @@ var ReaddirpStream = class extends import_node_stream.Readable {
|
|
|
1716
2169
|
this.reading = false;
|
|
1717
2170
|
}
|
|
1718
2171
|
}
|
|
1719
|
-
async _exploreDir(
|
|
2172
|
+
async _exploreDir(path28, depth) {
|
|
1720
2173
|
let files;
|
|
1721
2174
|
try {
|
|
1722
|
-
files = await (0, import_promises.readdir)(
|
|
2175
|
+
files = await (0, import_promises.readdir)(path28, this._rdOptions);
|
|
1723
2176
|
} catch (error) {
|
|
1724
2177
|
this._onError(error);
|
|
1725
2178
|
}
|
|
1726
|
-
return { files, depth, path:
|
|
2179
|
+
return { files, depth, path: path28 };
|
|
1727
2180
|
}
|
|
1728
|
-
async _formatEntry(dirent,
|
|
2181
|
+
async _formatEntry(dirent, path28) {
|
|
1729
2182
|
let entry;
|
|
1730
2183
|
const basename3 = this._isDirent ? dirent.name : dirent;
|
|
1731
2184
|
try {
|
|
1732
|
-
const fullPath = (0, import_node_path.resolve)((0, import_node_path.join)(
|
|
2185
|
+
const fullPath = (0, import_node_path.resolve)((0, import_node_path.join)(path28, basename3));
|
|
1733
2186
|
entry = { path: (0, import_node_path.relative)(this._root, fullPath), fullPath, basename: basename3 };
|
|
1734
2187
|
entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
|
|
1735
2188
|
} catch (err) {
|
|
@@ -2129,16 +2582,16 @@ var delFromSet = (main, prop, item) => {
|
|
|
2129
2582
|
};
|
|
2130
2583
|
var isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
|
|
2131
2584
|
var FsWatchInstances = /* @__PURE__ */ new Map();
|
|
2132
|
-
function createFsWatchInstance(
|
|
2585
|
+
function createFsWatchInstance(path28, options, listener, errHandler, emitRaw) {
|
|
2133
2586
|
const handleEvent = (rawEvent, evPath) => {
|
|
2134
|
-
listener(
|
|
2135
|
-
emitRaw(rawEvent, evPath, { watchedPath:
|
|
2136
|
-
if (evPath &&
|
|
2137
|
-
fsWatchBroadcast(sysPath.resolve(
|
|
2587
|
+
listener(path28);
|
|
2588
|
+
emitRaw(rawEvent, evPath, { watchedPath: path28 });
|
|
2589
|
+
if (evPath && path28 !== evPath) {
|
|
2590
|
+
fsWatchBroadcast(sysPath.resolve(path28, evPath), KEY_LISTENERS, sysPath.join(path28, evPath));
|
|
2138
2591
|
}
|
|
2139
2592
|
};
|
|
2140
2593
|
try {
|
|
2141
|
-
return (0, import_fs14.watch)(
|
|
2594
|
+
return (0, import_fs14.watch)(path28, {
|
|
2142
2595
|
persistent: options.persistent
|
|
2143
2596
|
}, handleEvent);
|
|
2144
2597
|
} catch (error) {
|
|
@@ -2154,12 +2607,12 @@ var fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {
|
|
|
2154
2607
|
listener(val1, val2, val3);
|
|
2155
2608
|
});
|
|
2156
2609
|
};
|
|
2157
|
-
var setFsWatchListener = (
|
|
2610
|
+
var setFsWatchListener = (path28, fullPath, options, handlers) => {
|
|
2158
2611
|
const { listener, errHandler, rawEmitter } = handlers;
|
|
2159
2612
|
let cont = FsWatchInstances.get(fullPath);
|
|
2160
2613
|
let watcher;
|
|
2161
2614
|
if (!options.persistent) {
|
|
2162
|
-
watcher = createFsWatchInstance(
|
|
2615
|
+
watcher = createFsWatchInstance(path28, options, listener, errHandler, rawEmitter);
|
|
2163
2616
|
if (!watcher)
|
|
2164
2617
|
return;
|
|
2165
2618
|
return watcher.close.bind(watcher);
|
|
@@ -2170,7 +2623,7 @@ var setFsWatchListener = (path25, fullPath, options, handlers) => {
|
|
|
2170
2623
|
addAndConvert(cont, KEY_RAW, rawEmitter);
|
|
2171
2624
|
} else {
|
|
2172
2625
|
watcher = createFsWatchInstance(
|
|
2173
|
-
|
|
2626
|
+
path28,
|
|
2174
2627
|
options,
|
|
2175
2628
|
fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS),
|
|
2176
2629
|
errHandler,
|
|
@@ -2185,7 +2638,7 @@ var setFsWatchListener = (path25, fullPath, options, handlers) => {
|
|
|
2185
2638
|
cont.watcherUnusable = true;
|
|
2186
2639
|
if (isWindows && error.code === "EPERM") {
|
|
2187
2640
|
try {
|
|
2188
|
-
const fd = await (0, import_promises2.open)(
|
|
2641
|
+
const fd = await (0, import_promises2.open)(path28, "r");
|
|
2189
2642
|
await fd.close();
|
|
2190
2643
|
broadcastErr(error);
|
|
2191
2644
|
} catch (err) {
|
|
@@ -2216,7 +2669,7 @@ var setFsWatchListener = (path25, fullPath, options, handlers) => {
|
|
|
2216
2669
|
};
|
|
2217
2670
|
};
|
|
2218
2671
|
var FsWatchFileInstances = /* @__PURE__ */ new Map();
|
|
2219
|
-
var setFsWatchFileListener = (
|
|
2672
|
+
var setFsWatchFileListener = (path28, fullPath, options, handlers) => {
|
|
2220
2673
|
const { listener, rawEmitter } = handlers;
|
|
2221
2674
|
let cont = FsWatchFileInstances.get(fullPath);
|
|
2222
2675
|
const copts = cont && cont.options;
|
|
@@ -2238,7 +2691,7 @@ var setFsWatchFileListener = (path25, fullPath, options, handlers) => {
|
|
|
2238
2691
|
});
|
|
2239
2692
|
const currmtime = curr.mtimeMs;
|
|
2240
2693
|
if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
|
|
2241
|
-
foreach(cont.listeners, (listener2) => listener2(
|
|
2694
|
+
foreach(cont.listeners, (listener2) => listener2(path28, curr));
|
|
2242
2695
|
}
|
|
2243
2696
|
})
|
|
2244
2697
|
};
|
|
@@ -2266,13 +2719,13 @@ var NodeFsHandler = class {
|
|
|
2266
2719
|
* @param listener on fs change
|
|
2267
2720
|
* @returns closer for the watcher instance
|
|
2268
2721
|
*/
|
|
2269
|
-
_watchWithNodeFs(
|
|
2722
|
+
_watchWithNodeFs(path28, listener) {
|
|
2270
2723
|
const opts = this.fsw.options;
|
|
2271
|
-
const directory = sysPath.dirname(
|
|
2272
|
-
const basename3 = sysPath.basename(
|
|
2724
|
+
const directory = sysPath.dirname(path28);
|
|
2725
|
+
const basename3 = sysPath.basename(path28);
|
|
2273
2726
|
const parent = this.fsw._getWatchedDir(directory);
|
|
2274
2727
|
parent.add(basename3);
|
|
2275
|
-
const absolutePath = sysPath.resolve(
|
|
2728
|
+
const absolutePath = sysPath.resolve(path28);
|
|
2276
2729
|
const options = {
|
|
2277
2730
|
persistent: opts.persistent
|
|
2278
2731
|
};
|
|
@@ -2282,12 +2735,12 @@ var NodeFsHandler = class {
|
|
|
2282
2735
|
if (opts.usePolling) {
|
|
2283
2736
|
const enableBin = opts.interval !== opts.binaryInterval;
|
|
2284
2737
|
options.interval = enableBin && isBinaryPath(basename3) ? opts.binaryInterval : opts.interval;
|
|
2285
|
-
closer = setFsWatchFileListener(
|
|
2738
|
+
closer = setFsWatchFileListener(path28, absolutePath, options, {
|
|
2286
2739
|
listener,
|
|
2287
2740
|
rawEmitter: this.fsw._emitRaw
|
|
2288
2741
|
});
|
|
2289
2742
|
} else {
|
|
2290
|
-
closer = setFsWatchListener(
|
|
2743
|
+
closer = setFsWatchListener(path28, absolutePath, options, {
|
|
2291
2744
|
listener,
|
|
2292
2745
|
errHandler: this._boundHandleError,
|
|
2293
2746
|
rawEmitter: this.fsw._emitRaw
|
|
@@ -2309,7 +2762,7 @@ var NodeFsHandler = class {
|
|
|
2309
2762
|
let prevStats = stats;
|
|
2310
2763
|
if (parent.has(basename3))
|
|
2311
2764
|
return;
|
|
2312
|
-
const listener = async (
|
|
2765
|
+
const listener = async (path28, newStats) => {
|
|
2313
2766
|
if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
|
|
2314
2767
|
return;
|
|
2315
2768
|
if (!newStats || newStats.mtimeMs === 0) {
|
|
@@ -2323,11 +2776,11 @@ var NodeFsHandler = class {
|
|
|
2323
2776
|
this.fsw._emit(EV.CHANGE, file, newStats2);
|
|
2324
2777
|
}
|
|
2325
2778
|
if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats2.ino) {
|
|
2326
|
-
this.fsw._closeFile(
|
|
2779
|
+
this.fsw._closeFile(path28);
|
|
2327
2780
|
prevStats = newStats2;
|
|
2328
2781
|
const closer2 = this._watchWithNodeFs(file, listener);
|
|
2329
2782
|
if (closer2)
|
|
2330
|
-
this.fsw._addPathCloser(
|
|
2783
|
+
this.fsw._addPathCloser(path28, closer2);
|
|
2331
2784
|
} else {
|
|
2332
2785
|
prevStats = newStats2;
|
|
2333
2786
|
}
|
|
@@ -2359,7 +2812,7 @@ var NodeFsHandler = class {
|
|
|
2359
2812
|
* @param item basename of this item
|
|
2360
2813
|
* @returns true if no more processing is needed for this entry.
|
|
2361
2814
|
*/
|
|
2362
|
-
async _handleSymlink(entry, directory,
|
|
2815
|
+
async _handleSymlink(entry, directory, path28, item) {
|
|
2363
2816
|
if (this.fsw.closed) {
|
|
2364
2817
|
return;
|
|
2365
2818
|
}
|
|
@@ -2369,7 +2822,7 @@ var NodeFsHandler = class {
|
|
|
2369
2822
|
this.fsw._incrReadyCount();
|
|
2370
2823
|
let linkPath;
|
|
2371
2824
|
try {
|
|
2372
|
-
linkPath = await (0, import_promises2.realpath)(
|
|
2825
|
+
linkPath = await (0, import_promises2.realpath)(path28);
|
|
2373
2826
|
} catch (e) {
|
|
2374
2827
|
this.fsw._emitReady();
|
|
2375
2828
|
return true;
|
|
@@ -2379,12 +2832,12 @@ var NodeFsHandler = class {
|
|
|
2379
2832
|
if (dir.has(item)) {
|
|
2380
2833
|
if (this.fsw._symlinkPaths.get(full) !== linkPath) {
|
|
2381
2834
|
this.fsw._symlinkPaths.set(full, linkPath);
|
|
2382
|
-
this.fsw._emit(EV.CHANGE,
|
|
2835
|
+
this.fsw._emit(EV.CHANGE, path28, entry.stats);
|
|
2383
2836
|
}
|
|
2384
2837
|
} else {
|
|
2385
2838
|
dir.add(item);
|
|
2386
2839
|
this.fsw._symlinkPaths.set(full, linkPath);
|
|
2387
|
-
this.fsw._emit(EV.ADD,
|
|
2840
|
+
this.fsw._emit(EV.ADD, path28, entry.stats);
|
|
2388
2841
|
}
|
|
2389
2842
|
this.fsw._emitReady();
|
|
2390
2843
|
return true;
|
|
@@ -2413,9 +2866,9 @@ var NodeFsHandler = class {
|
|
|
2413
2866
|
return;
|
|
2414
2867
|
}
|
|
2415
2868
|
const item = entry.path;
|
|
2416
|
-
let
|
|
2869
|
+
let path28 = sysPath.join(directory, item);
|
|
2417
2870
|
current.add(item);
|
|
2418
|
-
if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory,
|
|
2871
|
+
if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path28, item)) {
|
|
2419
2872
|
return;
|
|
2420
2873
|
}
|
|
2421
2874
|
if (this.fsw.closed) {
|
|
@@ -2424,8 +2877,8 @@ var NodeFsHandler = class {
|
|
|
2424
2877
|
}
|
|
2425
2878
|
if (item === target || !target && !previous.has(item)) {
|
|
2426
2879
|
this.fsw._incrReadyCount();
|
|
2427
|
-
|
|
2428
|
-
this._addToNodeFs(
|
|
2880
|
+
path28 = sysPath.join(dir, sysPath.relative(dir, path28));
|
|
2881
|
+
this._addToNodeFs(path28, initialAdd, wh, depth + 1);
|
|
2429
2882
|
}
|
|
2430
2883
|
}).on(EV.ERROR, this._boundHandleError);
|
|
2431
2884
|
return new Promise((resolve3, reject) => {
|
|
@@ -2494,13 +2947,13 @@ var NodeFsHandler = class {
|
|
|
2494
2947
|
* @param depth Child path actually targeted for watch
|
|
2495
2948
|
* @param target Child path actually targeted for watch
|
|
2496
2949
|
*/
|
|
2497
|
-
async _addToNodeFs(
|
|
2950
|
+
async _addToNodeFs(path28, initialAdd, priorWh, depth, target) {
|
|
2498
2951
|
const ready = this.fsw._emitReady;
|
|
2499
|
-
if (this.fsw._isIgnored(
|
|
2952
|
+
if (this.fsw._isIgnored(path28) || this.fsw.closed) {
|
|
2500
2953
|
ready();
|
|
2501
2954
|
return false;
|
|
2502
2955
|
}
|
|
2503
|
-
const wh = this.fsw._getWatchHelpers(
|
|
2956
|
+
const wh = this.fsw._getWatchHelpers(path28);
|
|
2504
2957
|
if (priorWh) {
|
|
2505
2958
|
wh.filterPath = (entry) => priorWh.filterPath(entry);
|
|
2506
2959
|
wh.filterDir = (entry) => priorWh.filterDir(entry);
|
|
@@ -2516,8 +2969,8 @@ var NodeFsHandler = class {
|
|
|
2516
2969
|
const follow = this.fsw.options.followSymlinks;
|
|
2517
2970
|
let closer;
|
|
2518
2971
|
if (stats.isDirectory()) {
|
|
2519
|
-
const absPath = sysPath.resolve(
|
|
2520
|
-
const targetPath = follow ? await (0, import_promises2.realpath)(
|
|
2972
|
+
const absPath = sysPath.resolve(path28);
|
|
2973
|
+
const targetPath = follow ? await (0, import_promises2.realpath)(path28) : path28;
|
|
2521
2974
|
if (this.fsw.closed)
|
|
2522
2975
|
return;
|
|
2523
2976
|
closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
|
|
@@ -2527,29 +2980,29 @@ var NodeFsHandler = class {
|
|
|
2527
2980
|
this.fsw._symlinkPaths.set(absPath, targetPath);
|
|
2528
2981
|
}
|
|
2529
2982
|
} else if (stats.isSymbolicLink()) {
|
|
2530
|
-
const targetPath = follow ? await (0, import_promises2.realpath)(
|
|
2983
|
+
const targetPath = follow ? await (0, import_promises2.realpath)(path28) : path28;
|
|
2531
2984
|
if (this.fsw.closed)
|
|
2532
2985
|
return;
|
|
2533
2986
|
const parent = sysPath.dirname(wh.watchPath);
|
|
2534
2987
|
this.fsw._getWatchedDir(parent).add(wh.watchPath);
|
|
2535
2988
|
this.fsw._emit(EV.ADD, wh.watchPath, stats);
|
|
2536
|
-
closer = await this._handleDir(parent, stats, initialAdd, depth,
|
|
2989
|
+
closer = await this._handleDir(parent, stats, initialAdd, depth, path28, wh, targetPath);
|
|
2537
2990
|
if (this.fsw.closed)
|
|
2538
2991
|
return;
|
|
2539
2992
|
if (targetPath !== void 0) {
|
|
2540
|
-
this.fsw._symlinkPaths.set(sysPath.resolve(
|
|
2993
|
+
this.fsw._symlinkPaths.set(sysPath.resolve(path28), targetPath);
|
|
2541
2994
|
}
|
|
2542
2995
|
} else {
|
|
2543
2996
|
closer = this._handleFile(wh.watchPath, stats, initialAdd);
|
|
2544
2997
|
}
|
|
2545
2998
|
ready();
|
|
2546
2999
|
if (closer)
|
|
2547
|
-
this.fsw._addPathCloser(
|
|
3000
|
+
this.fsw._addPathCloser(path28, closer);
|
|
2548
3001
|
return false;
|
|
2549
3002
|
} catch (error) {
|
|
2550
3003
|
if (this.fsw._handleError(error)) {
|
|
2551
3004
|
ready();
|
|
2552
|
-
return
|
|
3005
|
+
return path28;
|
|
2553
3006
|
}
|
|
2554
3007
|
}
|
|
2555
3008
|
}
|
|
@@ -2592,26 +3045,26 @@ function createPattern(matcher) {
|
|
|
2592
3045
|
}
|
|
2593
3046
|
return () => false;
|
|
2594
3047
|
}
|
|
2595
|
-
function normalizePath(
|
|
2596
|
-
if (typeof
|
|
3048
|
+
function normalizePath(path28) {
|
|
3049
|
+
if (typeof path28 !== "string")
|
|
2597
3050
|
throw new Error("string expected");
|
|
2598
|
-
|
|
2599
|
-
|
|
3051
|
+
path28 = sysPath2.normalize(path28);
|
|
3052
|
+
path28 = path28.replace(/\\/g, "/");
|
|
2600
3053
|
let prepend = false;
|
|
2601
|
-
if (
|
|
3054
|
+
if (path28.startsWith("//"))
|
|
2602
3055
|
prepend = true;
|
|
2603
3056
|
const DOUBLE_SLASH_RE2 = /\/\//;
|
|
2604
|
-
while (
|
|
2605
|
-
|
|
3057
|
+
while (path28.match(DOUBLE_SLASH_RE2))
|
|
3058
|
+
path28 = path28.replace(DOUBLE_SLASH_RE2, "/");
|
|
2606
3059
|
if (prepend)
|
|
2607
|
-
|
|
2608
|
-
return
|
|
3060
|
+
path28 = "/" + path28;
|
|
3061
|
+
return path28;
|
|
2609
3062
|
}
|
|
2610
3063
|
function matchPatterns(patterns, testString, stats) {
|
|
2611
|
-
const
|
|
3064
|
+
const path28 = normalizePath(testString);
|
|
2612
3065
|
for (let index = 0; index < patterns.length; index++) {
|
|
2613
3066
|
const pattern = patterns[index];
|
|
2614
|
-
if (pattern(
|
|
3067
|
+
if (pattern(path28, stats)) {
|
|
2615
3068
|
return true;
|
|
2616
3069
|
}
|
|
2617
3070
|
}
|
|
@@ -2651,19 +3104,19 @@ var toUnix = (string) => {
|
|
|
2651
3104
|
}
|
|
2652
3105
|
return str;
|
|
2653
3106
|
};
|
|
2654
|
-
var normalizePathToUnix = (
|
|
2655
|
-
var normalizeIgnored = (cwd = "") => (
|
|
2656
|
-
if (typeof
|
|
2657
|
-
return normalizePathToUnix(sysPath2.isAbsolute(
|
|
3107
|
+
var normalizePathToUnix = (path28) => toUnix(sysPath2.normalize(toUnix(path28)));
|
|
3108
|
+
var normalizeIgnored = (cwd = "") => (path28) => {
|
|
3109
|
+
if (typeof path28 === "string") {
|
|
3110
|
+
return normalizePathToUnix(sysPath2.isAbsolute(path28) ? path28 : sysPath2.join(cwd, path28));
|
|
2658
3111
|
} else {
|
|
2659
|
-
return
|
|
3112
|
+
return path28;
|
|
2660
3113
|
}
|
|
2661
3114
|
};
|
|
2662
|
-
var getAbsolutePath = (
|
|
2663
|
-
if (sysPath2.isAbsolute(
|
|
2664
|
-
return
|
|
3115
|
+
var getAbsolutePath = (path28, cwd) => {
|
|
3116
|
+
if (sysPath2.isAbsolute(path28)) {
|
|
3117
|
+
return path28;
|
|
2665
3118
|
}
|
|
2666
|
-
return sysPath2.join(cwd,
|
|
3119
|
+
return sysPath2.join(cwd, path28);
|
|
2667
3120
|
};
|
|
2668
3121
|
var EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set());
|
|
2669
3122
|
var DirEntry = class {
|
|
@@ -2718,10 +3171,10 @@ var DirEntry = class {
|
|
|
2718
3171
|
var STAT_METHOD_F = "stat";
|
|
2719
3172
|
var STAT_METHOD_L = "lstat";
|
|
2720
3173
|
var WatchHelper = class {
|
|
2721
|
-
constructor(
|
|
3174
|
+
constructor(path28, follow, fsw) {
|
|
2722
3175
|
this.fsw = fsw;
|
|
2723
|
-
const watchPath =
|
|
2724
|
-
this.path =
|
|
3176
|
+
const watchPath = path28;
|
|
3177
|
+
this.path = path28 = path28.replace(REPLACER_RE, "");
|
|
2725
3178
|
this.watchPath = watchPath;
|
|
2726
3179
|
this.fullWatchPath = sysPath2.resolve(watchPath);
|
|
2727
3180
|
this.dirParts = [];
|
|
@@ -2843,20 +3296,20 @@ var FSWatcher = class extends import_events.EventEmitter {
|
|
|
2843
3296
|
this._closePromise = void 0;
|
|
2844
3297
|
let paths = unifyPaths(paths_);
|
|
2845
3298
|
if (cwd) {
|
|
2846
|
-
paths = paths.map((
|
|
2847
|
-
const absPath = getAbsolutePath(
|
|
3299
|
+
paths = paths.map((path28) => {
|
|
3300
|
+
const absPath = getAbsolutePath(path28, cwd);
|
|
2848
3301
|
return absPath;
|
|
2849
3302
|
});
|
|
2850
3303
|
}
|
|
2851
|
-
paths.forEach((
|
|
2852
|
-
this._removeIgnoredPath(
|
|
3304
|
+
paths.forEach((path28) => {
|
|
3305
|
+
this._removeIgnoredPath(path28);
|
|
2853
3306
|
});
|
|
2854
3307
|
this._userIgnored = void 0;
|
|
2855
3308
|
if (!this._readyCount)
|
|
2856
3309
|
this._readyCount = 0;
|
|
2857
3310
|
this._readyCount += paths.length;
|
|
2858
|
-
Promise.all(paths.map(async (
|
|
2859
|
-
const res = await this._nodeFsHandler._addToNodeFs(
|
|
3311
|
+
Promise.all(paths.map(async (path28) => {
|
|
3312
|
+
const res = await this._nodeFsHandler._addToNodeFs(path28, !_internal, void 0, 0, _origAdd);
|
|
2860
3313
|
if (res)
|
|
2861
3314
|
this._emitReady();
|
|
2862
3315
|
return res;
|
|
@@ -2878,17 +3331,17 @@ var FSWatcher = class extends import_events.EventEmitter {
|
|
|
2878
3331
|
return this;
|
|
2879
3332
|
const paths = unifyPaths(paths_);
|
|
2880
3333
|
const { cwd } = this.options;
|
|
2881
|
-
paths.forEach((
|
|
2882
|
-
if (!sysPath2.isAbsolute(
|
|
3334
|
+
paths.forEach((path28) => {
|
|
3335
|
+
if (!sysPath2.isAbsolute(path28) && !this._closers.has(path28)) {
|
|
2883
3336
|
if (cwd)
|
|
2884
|
-
|
|
2885
|
-
|
|
3337
|
+
path28 = sysPath2.join(cwd, path28);
|
|
3338
|
+
path28 = sysPath2.resolve(path28);
|
|
2886
3339
|
}
|
|
2887
|
-
this._closePath(
|
|
2888
|
-
this._addIgnoredPath(
|
|
2889
|
-
if (this._watched.has(
|
|
3340
|
+
this._closePath(path28);
|
|
3341
|
+
this._addIgnoredPath(path28);
|
|
3342
|
+
if (this._watched.has(path28)) {
|
|
2890
3343
|
this._addIgnoredPath({
|
|
2891
|
-
path:
|
|
3344
|
+
path: path28,
|
|
2892
3345
|
recursive: true
|
|
2893
3346
|
});
|
|
2894
3347
|
}
|
|
@@ -2952,38 +3405,38 @@ var FSWatcher = class extends import_events.EventEmitter {
|
|
|
2952
3405
|
* @param stats arguments to be passed with event
|
|
2953
3406
|
* @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
|
|
2954
3407
|
*/
|
|
2955
|
-
async _emit(event,
|
|
3408
|
+
async _emit(event, path28, stats) {
|
|
2956
3409
|
if (this.closed)
|
|
2957
3410
|
return;
|
|
2958
3411
|
const opts = this.options;
|
|
2959
3412
|
if (isWindows)
|
|
2960
|
-
|
|
3413
|
+
path28 = sysPath2.normalize(path28);
|
|
2961
3414
|
if (opts.cwd)
|
|
2962
|
-
|
|
2963
|
-
const args = [
|
|
3415
|
+
path28 = sysPath2.relative(opts.cwd, path28);
|
|
3416
|
+
const args = [path28];
|
|
2964
3417
|
if (stats != null)
|
|
2965
3418
|
args.push(stats);
|
|
2966
3419
|
const awf = opts.awaitWriteFinish;
|
|
2967
3420
|
let pw;
|
|
2968
|
-
if (awf && (pw = this._pendingWrites.get(
|
|
3421
|
+
if (awf && (pw = this._pendingWrites.get(path28))) {
|
|
2969
3422
|
pw.lastChange = /* @__PURE__ */ new Date();
|
|
2970
3423
|
return this;
|
|
2971
3424
|
}
|
|
2972
3425
|
if (opts.atomic) {
|
|
2973
3426
|
if (event === EVENTS.UNLINK) {
|
|
2974
|
-
this._pendingUnlinks.set(
|
|
3427
|
+
this._pendingUnlinks.set(path28, [event, ...args]);
|
|
2975
3428
|
setTimeout(() => {
|
|
2976
|
-
this._pendingUnlinks.forEach((entry,
|
|
3429
|
+
this._pendingUnlinks.forEach((entry, path29) => {
|
|
2977
3430
|
this.emit(...entry);
|
|
2978
3431
|
this.emit(EVENTS.ALL, ...entry);
|
|
2979
|
-
this._pendingUnlinks.delete(
|
|
3432
|
+
this._pendingUnlinks.delete(path29);
|
|
2980
3433
|
});
|
|
2981
3434
|
}, typeof opts.atomic === "number" ? opts.atomic : 100);
|
|
2982
3435
|
return this;
|
|
2983
3436
|
}
|
|
2984
|
-
if (event === EVENTS.ADD && this._pendingUnlinks.has(
|
|
3437
|
+
if (event === EVENTS.ADD && this._pendingUnlinks.has(path28)) {
|
|
2985
3438
|
event = EVENTS.CHANGE;
|
|
2986
|
-
this._pendingUnlinks.delete(
|
|
3439
|
+
this._pendingUnlinks.delete(path28);
|
|
2987
3440
|
}
|
|
2988
3441
|
}
|
|
2989
3442
|
if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
|
|
@@ -3001,16 +3454,16 @@ var FSWatcher = class extends import_events.EventEmitter {
|
|
|
3001
3454
|
this.emitWithAll(event, args);
|
|
3002
3455
|
}
|
|
3003
3456
|
};
|
|
3004
|
-
this._awaitWriteFinish(
|
|
3457
|
+
this._awaitWriteFinish(path28, awf.stabilityThreshold, event, awfEmit);
|
|
3005
3458
|
return this;
|
|
3006
3459
|
}
|
|
3007
3460
|
if (event === EVENTS.CHANGE) {
|
|
3008
|
-
const isThrottled = !this._throttle(EVENTS.CHANGE,
|
|
3461
|
+
const isThrottled = !this._throttle(EVENTS.CHANGE, path28, 50);
|
|
3009
3462
|
if (isThrottled)
|
|
3010
3463
|
return this;
|
|
3011
3464
|
}
|
|
3012
3465
|
if (opts.alwaysStat && stats === void 0 && (event === EVENTS.ADD || event === EVENTS.ADD_DIR || event === EVENTS.CHANGE)) {
|
|
3013
|
-
const fullPath = opts.cwd ? sysPath2.join(opts.cwd,
|
|
3466
|
+
const fullPath = opts.cwd ? sysPath2.join(opts.cwd, path28) : path28;
|
|
3014
3467
|
let stats2;
|
|
3015
3468
|
try {
|
|
3016
3469
|
stats2 = await (0, import_promises3.stat)(fullPath);
|
|
@@ -3041,23 +3494,23 @@ var FSWatcher = class extends import_events.EventEmitter {
|
|
|
3041
3494
|
* @param timeout duration of time to suppress duplicate actions
|
|
3042
3495
|
* @returns tracking object or false if action should be suppressed
|
|
3043
3496
|
*/
|
|
3044
|
-
_throttle(actionType,
|
|
3497
|
+
_throttle(actionType, path28, timeout) {
|
|
3045
3498
|
if (!this._throttled.has(actionType)) {
|
|
3046
3499
|
this._throttled.set(actionType, /* @__PURE__ */ new Map());
|
|
3047
3500
|
}
|
|
3048
3501
|
const action = this._throttled.get(actionType);
|
|
3049
3502
|
if (!action)
|
|
3050
3503
|
throw new Error("invalid throttle");
|
|
3051
|
-
const actionPath = action.get(
|
|
3504
|
+
const actionPath = action.get(path28);
|
|
3052
3505
|
if (actionPath) {
|
|
3053
3506
|
actionPath.count++;
|
|
3054
3507
|
return false;
|
|
3055
3508
|
}
|
|
3056
3509
|
let timeoutObject;
|
|
3057
3510
|
const clear = () => {
|
|
3058
|
-
const item = action.get(
|
|
3511
|
+
const item = action.get(path28);
|
|
3059
3512
|
const count = item ? item.count : 0;
|
|
3060
|
-
action.delete(
|
|
3513
|
+
action.delete(path28);
|
|
3061
3514
|
clearTimeout(timeoutObject);
|
|
3062
3515
|
if (item)
|
|
3063
3516
|
clearTimeout(item.timeoutObject);
|
|
@@ -3065,7 +3518,7 @@ var FSWatcher = class extends import_events.EventEmitter {
|
|
|
3065
3518
|
};
|
|
3066
3519
|
timeoutObject = setTimeout(clear, timeout);
|
|
3067
3520
|
const thr = { timeoutObject, clear, count: 0 };
|
|
3068
|
-
action.set(
|
|
3521
|
+
action.set(path28, thr);
|
|
3069
3522
|
return thr;
|
|
3070
3523
|
}
|
|
3071
3524
|
_incrReadyCount() {
|
|
@@ -3079,44 +3532,44 @@ var FSWatcher = class extends import_events.EventEmitter {
|
|
|
3079
3532
|
* @param event
|
|
3080
3533
|
* @param awfEmit Callback to be called when ready for event to be emitted.
|
|
3081
3534
|
*/
|
|
3082
|
-
_awaitWriteFinish(
|
|
3535
|
+
_awaitWriteFinish(path28, threshold, event, awfEmit) {
|
|
3083
3536
|
const awf = this.options.awaitWriteFinish;
|
|
3084
3537
|
if (typeof awf !== "object")
|
|
3085
3538
|
return;
|
|
3086
3539
|
const pollInterval = awf.pollInterval;
|
|
3087
3540
|
let timeoutHandler;
|
|
3088
|
-
let fullPath =
|
|
3089
|
-
if (this.options.cwd && !sysPath2.isAbsolute(
|
|
3090
|
-
fullPath = sysPath2.join(this.options.cwd,
|
|
3541
|
+
let fullPath = path28;
|
|
3542
|
+
if (this.options.cwd && !sysPath2.isAbsolute(path28)) {
|
|
3543
|
+
fullPath = sysPath2.join(this.options.cwd, path28);
|
|
3091
3544
|
}
|
|
3092
3545
|
const now = /* @__PURE__ */ new Date();
|
|
3093
3546
|
const writes = this._pendingWrites;
|
|
3094
3547
|
function awaitWriteFinishFn(prevStat) {
|
|
3095
3548
|
(0, import_fs15.stat)(fullPath, (err, curStat) => {
|
|
3096
|
-
if (err || !writes.has(
|
|
3549
|
+
if (err || !writes.has(path28)) {
|
|
3097
3550
|
if (err && err.code !== "ENOENT")
|
|
3098
3551
|
awfEmit(err);
|
|
3099
3552
|
return;
|
|
3100
3553
|
}
|
|
3101
3554
|
const now2 = Number(/* @__PURE__ */ new Date());
|
|
3102
3555
|
if (prevStat && curStat.size !== prevStat.size) {
|
|
3103
|
-
writes.get(
|
|
3556
|
+
writes.get(path28).lastChange = now2;
|
|
3104
3557
|
}
|
|
3105
|
-
const pw = writes.get(
|
|
3558
|
+
const pw = writes.get(path28);
|
|
3106
3559
|
const df = now2 - pw.lastChange;
|
|
3107
3560
|
if (df >= threshold) {
|
|
3108
|
-
writes.delete(
|
|
3561
|
+
writes.delete(path28);
|
|
3109
3562
|
awfEmit(void 0, curStat);
|
|
3110
3563
|
} else {
|
|
3111
3564
|
timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
|
|
3112
3565
|
}
|
|
3113
3566
|
});
|
|
3114
3567
|
}
|
|
3115
|
-
if (!writes.has(
|
|
3116
|
-
writes.set(
|
|
3568
|
+
if (!writes.has(path28)) {
|
|
3569
|
+
writes.set(path28, {
|
|
3117
3570
|
lastChange: now,
|
|
3118
3571
|
cancelWait: () => {
|
|
3119
|
-
writes.delete(
|
|
3572
|
+
writes.delete(path28);
|
|
3120
3573
|
clearTimeout(timeoutHandler);
|
|
3121
3574
|
return event;
|
|
3122
3575
|
}
|
|
@@ -3127,8 +3580,8 @@ var FSWatcher = class extends import_events.EventEmitter {
|
|
|
3127
3580
|
/**
|
|
3128
3581
|
* Determines whether user has asked to ignore this path.
|
|
3129
3582
|
*/
|
|
3130
|
-
_isIgnored(
|
|
3131
|
-
if (this.options.atomic && DOT_RE.test(
|
|
3583
|
+
_isIgnored(path28, stats) {
|
|
3584
|
+
if (this.options.atomic && DOT_RE.test(path28))
|
|
3132
3585
|
return true;
|
|
3133
3586
|
if (!this._userIgnored) {
|
|
3134
3587
|
const { cwd } = this.options;
|
|
@@ -3138,17 +3591,17 @@ var FSWatcher = class extends import_events.EventEmitter {
|
|
|
3138
3591
|
const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
|
|
3139
3592
|
this._userIgnored = anymatch(list, void 0);
|
|
3140
3593
|
}
|
|
3141
|
-
return this._userIgnored(
|
|
3594
|
+
return this._userIgnored(path28, stats);
|
|
3142
3595
|
}
|
|
3143
|
-
_isntIgnored(
|
|
3144
|
-
return !this._isIgnored(
|
|
3596
|
+
_isntIgnored(path28, stat4) {
|
|
3597
|
+
return !this._isIgnored(path28, stat4);
|
|
3145
3598
|
}
|
|
3146
3599
|
/**
|
|
3147
3600
|
* Provides a set of common helpers and properties relating to symlink handling.
|
|
3148
3601
|
* @param path file or directory pattern being watched
|
|
3149
3602
|
*/
|
|
3150
|
-
_getWatchHelpers(
|
|
3151
|
-
return new WatchHelper(
|
|
3603
|
+
_getWatchHelpers(path28) {
|
|
3604
|
+
return new WatchHelper(path28, this.options.followSymlinks, this);
|
|
3152
3605
|
}
|
|
3153
3606
|
// Directory helpers
|
|
3154
3607
|
// -----------------
|
|
@@ -3180,63 +3633,63 @@ var FSWatcher = class extends import_events.EventEmitter {
|
|
|
3180
3633
|
* @param item base path of item/directory
|
|
3181
3634
|
*/
|
|
3182
3635
|
_remove(directory, item, isDirectory) {
|
|
3183
|
-
const
|
|
3184
|
-
const fullPath = sysPath2.resolve(
|
|
3185
|
-
isDirectory = isDirectory != null ? isDirectory : this._watched.has(
|
|
3186
|
-
if (!this._throttle("remove",
|
|
3636
|
+
const path28 = sysPath2.join(directory, item);
|
|
3637
|
+
const fullPath = sysPath2.resolve(path28);
|
|
3638
|
+
isDirectory = isDirectory != null ? isDirectory : this._watched.has(path28) || this._watched.has(fullPath);
|
|
3639
|
+
if (!this._throttle("remove", path28, 100))
|
|
3187
3640
|
return;
|
|
3188
3641
|
if (!isDirectory && this._watched.size === 1) {
|
|
3189
3642
|
this.add(directory, item, true);
|
|
3190
3643
|
}
|
|
3191
|
-
const wp = this._getWatchedDir(
|
|
3644
|
+
const wp = this._getWatchedDir(path28);
|
|
3192
3645
|
const nestedDirectoryChildren = wp.getChildren();
|
|
3193
|
-
nestedDirectoryChildren.forEach((nested) => this._remove(
|
|
3646
|
+
nestedDirectoryChildren.forEach((nested) => this._remove(path28, nested));
|
|
3194
3647
|
const parent = this._getWatchedDir(directory);
|
|
3195
3648
|
const wasTracked = parent.has(item);
|
|
3196
3649
|
parent.remove(item);
|
|
3197
3650
|
if (this._symlinkPaths.has(fullPath)) {
|
|
3198
3651
|
this._symlinkPaths.delete(fullPath);
|
|
3199
3652
|
}
|
|
3200
|
-
let relPath =
|
|
3653
|
+
let relPath = path28;
|
|
3201
3654
|
if (this.options.cwd)
|
|
3202
|
-
relPath = sysPath2.relative(this.options.cwd,
|
|
3655
|
+
relPath = sysPath2.relative(this.options.cwd, path28);
|
|
3203
3656
|
if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
|
|
3204
3657
|
const event = this._pendingWrites.get(relPath).cancelWait();
|
|
3205
3658
|
if (event === EVENTS.ADD)
|
|
3206
3659
|
return;
|
|
3207
3660
|
}
|
|
3208
|
-
this._watched.delete(
|
|
3661
|
+
this._watched.delete(path28);
|
|
3209
3662
|
this._watched.delete(fullPath);
|
|
3210
3663
|
const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
|
|
3211
|
-
if (wasTracked && !this._isIgnored(
|
|
3212
|
-
this._emit(eventName,
|
|
3213
|
-
this._closePath(
|
|
3664
|
+
if (wasTracked && !this._isIgnored(path28))
|
|
3665
|
+
this._emit(eventName, path28);
|
|
3666
|
+
this._closePath(path28);
|
|
3214
3667
|
}
|
|
3215
3668
|
/**
|
|
3216
3669
|
* Closes all watchers for a path
|
|
3217
3670
|
*/
|
|
3218
|
-
_closePath(
|
|
3219
|
-
this._closeFile(
|
|
3220
|
-
const dir = sysPath2.dirname(
|
|
3221
|
-
this._getWatchedDir(dir).remove(sysPath2.basename(
|
|
3671
|
+
_closePath(path28) {
|
|
3672
|
+
this._closeFile(path28);
|
|
3673
|
+
const dir = sysPath2.dirname(path28);
|
|
3674
|
+
this._getWatchedDir(dir).remove(sysPath2.basename(path28));
|
|
3222
3675
|
}
|
|
3223
3676
|
/**
|
|
3224
3677
|
* Closes only file-specific watchers
|
|
3225
3678
|
*/
|
|
3226
|
-
_closeFile(
|
|
3227
|
-
const closers = this._closers.get(
|
|
3679
|
+
_closeFile(path28) {
|
|
3680
|
+
const closers = this._closers.get(path28);
|
|
3228
3681
|
if (!closers)
|
|
3229
3682
|
return;
|
|
3230
3683
|
closers.forEach((closer) => closer());
|
|
3231
|
-
this._closers.delete(
|
|
3684
|
+
this._closers.delete(path28);
|
|
3232
3685
|
}
|
|
3233
|
-
_addPathCloser(
|
|
3686
|
+
_addPathCloser(path28, closer) {
|
|
3234
3687
|
if (!closer)
|
|
3235
3688
|
return;
|
|
3236
|
-
let list = this._closers.get(
|
|
3689
|
+
let list = this._closers.get(path28);
|
|
3237
3690
|
if (!list) {
|
|
3238
3691
|
list = [];
|
|
3239
|
-
this._closers.set(
|
|
3692
|
+
this._closers.set(path28, list);
|
|
3240
3693
|
}
|
|
3241
3694
|
list.push(closer);
|
|
3242
3695
|
}
|
|
@@ -3271,54 +3724,132 @@ init_globals();
|
|
|
3271
3724
|
function setupHotReload({
|
|
3272
3725
|
app,
|
|
3273
3726
|
appDir,
|
|
3727
|
+
projectRoot,
|
|
3274
3728
|
route = "/__fw/hot",
|
|
3275
3729
|
waitForBuild,
|
|
3276
3730
|
onFileChange
|
|
3277
3731
|
}) {
|
|
3278
3732
|
const clients = /* @__PURE__ */ new Set();
|
|
3279
3733
|
app.get(route, (req, res) => {
|
|
3280
|
-
res.setHeader("Content-Type", "text/event-stream");
|
|
3281
|
-
res.setHeader("Cache-Control", "no-cache");
|
|
3734
|
+
res.setHeader("Content-Type", "text/event-stream; charset=utf-8");
|
|
3735
|
+
res.setHeader("Cache-Control", "no-cache, no-transform");
|
|
3282
3736
|
res.setHeader("Connection", "keep-alive");
|
|
3283
|
-
res.
|
|
3284
|
-
res.
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3737
|
+
res.setHeader("X-Accel-Buffering", "no");
|
|
3738
|
+
if (res.flushHeaders) {
|
|
3739
|
+
res.flushHeaders();
|
|
3740
|
+
} else {
|
|
3741
|
+
res.writeHead(200, {
|
|
3742
|
+
"Content-Type": "text/event-stream; charset=utf-8",
|
|
3743
|
+
"Cache-Control": "no-cache, no-transform",
|
|
3744
|
+
"Connection": "keep-alive",
|
|
3745
|
+
"X-Accel-Buffering": "no"
|
|
3746
|
+
});
|
|
3747
|
+
}
|
|
3288
3748
|
clients.add(res);
|
|
3749
|
+
const pingMessage = "event: ping\ndata: connected\n\n";
|
|
3750
|
+
try {
|
|
3751
|
+
res.write(pingMessage, "utf-8");
|
|
3752
|
+
} catch (error) {
|
|
3753
|
+
console.error(`[hot-reload-server] \u274C Error sending ping:`, error);
|
|
3754
|
+
clients.delete(res);
|
|
3755
|
+
}
|
|
3289
3756
|
req.on("close", () => {
|
|
3290
3757
|
clients.delete(res);
|
|
3291
3758
|
});
|
|
3759
|
+
req.on("aborted", () => {
|
|
3760
|
+
clients.delete(res);
|
|
3761
|
+
});
|
|
3292
3762
|
});
|
|
3293
|
-
|
|
3763
|
+
console.log(`[hot-reload-server] \u2705 SSE endpoint registered at ${route}`);
|
|
3764
|
+
const resolvedProjectRoot = projectRoot ? import_path16.default.resolve(projectRoot) : import_path16.default.dirname(import_path16.default.resolve(appDir));
|
|
3765
|
+
const watcher = esm_default.watch(resolvedProjectRoot, {
|
|
3294
3766
|
ignoreInitial: true,
|
|
3295
|
-
ignored: [
|
|
3767
|
+
ignored: [
|
|
3768
|
+
"**/node_modules/**",
|
|
3769
|
+
`**/${BUILD_FOLDER_NAME}/**`,
|
|
3770
|
+
"**/.loly/**",
|
|
3771
|
+
// Ignore build output directory completely
|
|
3772
|
+
"**/.git/**",
|
|
3773
|
+
"**/dist/**",
|
|
3774
|
+
"**/build/**",
|
|
3775
|
+
"**/.next/**",
|
|
3776
|
+
"**/.cache/**",
|
|
3777
|
+
"**/.turbo/**",
|
|
3778
|
+
"**/.swc/**",
|
|
3779
|
+
"**/coverage/**",
|
|
3780
|
+
// Ignore generated files that trigger unnecessary reloads
|
|
3781
|
+
"**/*.map",
|
|
3782
|
+
// Source maps
|
|
3783
|
+
"**/*.log",
|
|
3784
|
+
// Log files
|
|
3785
|
+
"**/.DS_Store",
|
|
3786
|
+
// macOS
|
|
3787
|
+
"**/Thumbs.db"
|
|
3788
|
+
// Windows
|
|
3789
|
+
],
|
|
3790
|
+
persistent: true,
|
|
3791
|
+
ignorePermissionErrors: true,
|
|
3792
|
+
// Only watch relevant source files (TypeScript, JavaScript, CSS)
|
|
3793
|
+
// Filter out JSON files in build directories
|
|
3794
|
+
awaitWriteFinish: {
|
|
3795
|
+
stabilityThreshold: 150,
|
|
3796
|
+
// Wait 150ms after file change to trigger event (debounce)
|
|
3797
|
+
pollInterval: 50
|
|
3798
|
+
// Check every 50ms
|
|
3799
|
+
}
|
|
3296
3800
|
});
|
|
3801
|
+
let broadcastTimeout = null;
|
|
3802
|
+
const BROADCAST_DEBOUNCE_MS = 300;
|
|
3297
3803
|
async function broadcastReload(reason, filePath) {
|
|
3804
|
+
const normalizedPath = import_path16.default.normalize(filePath);
|
|
3805
|
+
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-")) {
|
|
3806
|
+
return;
|
|
3807
|
+
}
|
|
3298
3808
|
const rel = import_path16.default.relative(appDir, filePath);
|
|
3299
3809
|
console.log(`[hot-reload] ${reason}: ${rel}`);
|
|
3300
|
-
if (
|
|
3301
|
-
|
|
3302
|
-
await onFileChange(filePath);
|
|
3303
|
-
} catch (error) {
|
|
3304
|
-
console.warn("[hot-reload] Error in onFileChange callback:", error);
|
|
3305
|
-
}
|
|
3810
|
+
if (broadcastTimeout) {
|
|
3811
|
+
clearTimeout(broadcastTimeout);
|
|
3306
3812
|
}
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3813
|
+
broadcastTimeout = setTimeout(async () => {
|
|
3814
|
+
if (onFileChange) {
|
|
3815
|
+
try {
|
|
3816
|
+
await onFileChange(filePath);
|
|
3817
|
+
} catch (error) {
|
|
3818
|
+
console.warn("[hot-reload] Error in onFileChange callback:", error);
|
|
3819
|
+
}
|
|
3314
3820
|
}
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3821
|
+
if (waitForBuild) {
|
|
3822
|
+
try {
|
|
3823
|
+
console.log("[hot-reload] Waiting for client bundle to finish...");
|
|
3824
|
+
await waitForBuild();
|
|
3825
|
+
console.log("[hot-reload] Client bundle ready, sending reload event");
|
|
3826
|
+
} catch (error) {
|
|
3827
|
+
console.warn("[hot-reload] Error waiting for build:", error);
|
|
3828
|
+
}
|
|
3829
|
+
}
|
|
3830
|
+
const message = `event: message
|
|
3318
3831
|
data: reload:${rel}
|
|
3319
3832
|
|
|
3320
|
-
|
|
3321
|
-
|
|
3833
|
+
`;
|
|
3834
|
+
console.log(`[hot-reload-server] \u{1F4E4} Broadcasting reload event to ${clients.size} client(s)`);
|
|
3835
|
+
let sentCount = 0;
|
|
3836
|
+
for (const res of clients) {
|
|
3837
|
+
try {
|
|
3838
|
+
if (res.writableEnded || res.destroyed) {
|
|
3839
|
+
clients.delete(res);
|
|
3840
|
+
continue;
|
|
3841
|
+
}
|
|
3842
|
+
res.write(message, "utf-8");
|
|
3843
|
+
sentCount++;
|
|
3844
|
+
} catch (error) {
|
|
3845
|
+
console.error(`[hot-reload-server] \u2717 Error sending to client:`, error);
|
|
3846
|
+
clients.delete(res);
|
|
3847
|
+
}
|
|
3848
|
+
}
|
|
3849
|
+
if (sentCount > 0) {
|
|
3850
|
+
console.log(`[hot-reload-server] \u2705 Reload event sent to ${sentCount} client(s)`);
|
|
3851
|
+
}
|
|
3852
|
+
}, BROADCAST_DEBOUNCE_MS);
|
|
3322
3853
|
}
|
|
3323
3854
|
watcher.on("add", (filePath) => broadcastReload("add", filePath)).on("change", (filePath) => broadcastReload("change", filePath)).on("unlink", (filePath) => broadcastReload("unlink", filePath));
|
|
3324
3855
|
}
|
|
@@ -3394,6 +3925,121 @@ function deepMerge(target, source) {
|
|
|
3394
3925
|
}
|
|
3395
3926
|
return result;
|
|
3396
3927
|
}
|
|
3928
|
+
var ConfigValidationError = class extends Error {
|
|
3929
|
+
constructor(message, errors = []) {
|
|
3930
|
+
super(message);
|
|
3931
|
+
this.errors = errors;
|
|
3932
|
+
this.name = "ConfigValidationError";
|
|
3933
|
+
}
|
|
3934
|
+
};
|
|
3935
|
+
function validateConfig(config, projectRoot) {
|
|
3936
|
+
const errors = [];
|
|
3937
|
+
if (!config.directories.app || typeof config.directories.app !== "string") {
|
|
3938
|
+
errors.push("config.directories.app must be a non-empty string");
|
|
3939
|
+
} else {
|
|
3940
|
+
const appDir = import_path18.default.join(projectRoot, config.directories.app);
|
|
3941
|
+
if (!import_fs16.default.existsSync(appDir) && process.env.NODE_ENV !== "test") {
|
|
3942
|
+
errors.push(
|
|
3943
|
+
`App directory not found: ${config.directories.app}
|
|
3944
|
+
Expected at: ${appDir}
|
|
3945
|
+
\u{1F4A1} Suggestion: Create the directory or update config.directories.app`
|
|
3946
|
+
);
|
|
3947
|
+
}
|
|
3948
|
+
}
|
|
3949
|
+
if (!config.directories.build || typeof config.directories.build !== "string") {
|
|
3950
|
+
errors.push("config.directories.build must be a non-empty string");
|
|
3951
|
+
}
|
|
3952
|
+
if (!config.directories.static || typeof config.directories.static !== "string") {
|
|
3953
|
+
errors.push("config.directories.static must be a non-empty string");
|
|
3954
|
+
}
|
|
3955
|
+
const conventionKeys = ["page", "layout", "notFound", "error", "api"];
|
|
3956
|
+
for (const key of conventionKeys) {
|
|
3957
|
+
if (!config.conventions[key] || typeof config.conventions[key] !== "string") {
|
|
3958
|
+
errors.push(`config.conventions.${key} must be a non-empty string`);
|
|
3959
|
+
}
|
|
3960
|
+
}
|
|
3961
|
+
if (!["always", "never", "ignore"].includes(config.routing.trailingSlash)) {
|
|
3962
|
+
errors.push(
|
|
3963
|
+
`config.routing.trailingSlash must be 'always', 'never', or 'ignore'
|
|
3964
|
+
Received: ${JSON.stringify(config.routing.trailingSlash)}
|
|
3965
|
+
\u{1F4A1} Suggestion: Use one of the valid values: 'always' | 'never' | 'ignore'`
|
|
3966
|
+
);
|
|
3967
|
+
}
|
|
3968
|
+
if (typeof config.routing.caseSensitive !== "boolean") {
|
|
3969
|
+
errors.push("config.routing.caseSensitive must be a boolean");
|
|
3970
|
+
}
|
|
3971
|
+
if (typeof config.routing.basePath !== "string") {
|
|
3972
|
+
errors.push("config.routing.basePath must be a string");
|
|
3973
|
+
} else if (config.routing.basePath && !config.routing.basePath.startsWith("/")) {
|
|
3974
|
+
errors.push(
|
|
3975
|
+
`config.routing.basePath must start with '/' (if not empty)
|
|
3976
|
+
Received: ${JSON.stringify(config.routing.basePath)}
|
|
3977
|
+
\u{1F4A1} Suggestion: Use an empty string '' or a path starting with '/', e.g., '/api'`
|
|
3978
|
+
);
|
|
3979
|
+
}
|
|
3980
|
+
const validClientBundlers = ["rspack", "webpack", "vite"];
|
|
3981
|
+
if (!validClientBundlers.includes(config.build.clientBundler)) {
|
|
3982
|
+
errors.push(
|
|
3983
|
+
`config.build.clientBundler must be one of: ${validClientBundlers.join(", ")}
|
|
3984
|
+
Received: ${JSON.stringify(config.build.clientBundler)}`
|
|
3985
|
+
);
|
|
3986
|
+
}
|
|
3987
|
+
const validServerBundlers = ["esbuild", "tsup", "swc"];
|
|
3988
|
+
if (!validServerBundlers.includes(config.build.serverBundler)) {
|
|
3989
|
+
errors.push(
|
|
3990
|
+
`config.build.serverBundler must be one of: ${validServerBundlers.join(", ")}
|
|
3991
|
+
Received: ${JSON.stringify(config.build.serverBundler)}`
|
|
3992
|
+
);
|
|
3993
|
+
}
|
|
3994
|
+
if (!["cjs", "esm"].includes(config.build.outputFormat)) {
|
|
3995
|
+
errors.push(
|
|
3996
|
+
`config.build.outputFormat must be 'cjs' or 'esm'
|
|
3997
|
+
Received: ${JSON.stringify(config.build.outputFormat)}`
|
|
3998
|
+
);
|
|
3999
|
+
}
|
|
4000
|
+
const validAdapters = ["express", "fastify", "koa"];
|
|
4001
|
+
if (!validAdapters.includes(config.server.adapter)) {
|
|
4002
|
+
errors.push(
|
|
4003
|
+
`config.server.adapter must be one of: ${validAdapters.join(", ")}
|
|
4004
|
+
Received: ${JSON.stringify(config.server.adapter)}`
|
|
4005
|
+
);
|
|
4006
|
+
}
|
|
4007
|
+
if (typeof config.server.port !== "number" || config.server.port < 1 || config.server.port > 65535) {
|
|
4008
|
+
errors.push(
|
|
4009
|
+
`config.server.port must be a number between 1 and 65535
|
|
4010
|
+
Received: ${JSON.stringify(config.server.port)}`
|
|
4011
|
+
);
|
|
4012
|
+
}
|
|
4013
|
+
if (!config.server.host || typeof config.server.host !== "string") {
|
|
4014
|
+
errors.push("config.server.host must be a non-empty string");
|
|
4015
|
+
}
|
|
4016
|
+
const validFrameworks = ["react", "preact", "vue", "svelte"];
|
|
4017
|
+
if (!validFrameworks.includes(config.rendering.framework)) {
|
|
4018
|
+
errors.push(
|
|
4019
|
+
`config.rendering.framework must be one of: ${validFrameworks.join(", ")}
|
|
4020
|
+
Received: ${JSON.stringify(config.rendering.framework)}`
|
|
4021
|
+
);
|
|
4022
|
+
}
|
|
4023
|
+
if (typeof config.rendering.streaming !== "boolean") {
|
|
4024
|
+
errors.push("config.rendering.streaming must be a boolean");
|
|
4025
|
+
}
|
|
4026
|
+
if (typeof config.rendering.ssr !== "boolean") {
|
|
4027
|
+
errors.push("config.rendering.ssr must be a boolean");
|
|
4028
|
+
}
|
|
4029
|
+
if (typeof config.rendering.ssg !== "boolean") {
|
|
4030
|
+
errors.push("config.rendering.ssg must be a boolean");
|
|
4031
|
+
}
|
|
4032
|
+
if (errors.length > 0) {
|
|
4033
|
+
const errorMessage = [
|
|
4034
|
+
"\u274C Configuration validation failed:",
|
|
4035
|
+
"",
|
|
4036
|
+
...errors.map((err, i) => `${i + 1}. ${err}`),
|
|
4037
|
+
"",
|
|
4038
|
+
"\u{1F4A1} Please check your loly.config.ts file and fix the errors above."
|
|
4039
|
+
].join("\n");
|
|
4040
|
+
throw new ConfigValidationError(errorMessage, errors);
|
|
4041
|
+
}
|
|
4042
|
+
}
|
|
3397
4043
|
function loadConfig(projectRoot) {
|
|
3398
4044
|
const configFiles = [
|
|
3399
4045
|
import_path18.default.join(projectRoot, "loly.config.ts"),
|
|
@@ -3401,6 +4047,7 @@ function loadConfig(projectRoot) {
|
|
|
3401
4047
|
import_path18.default.join(projectRoot, "loly.config.json")
|
|
3402
4048
|
];
|
|
3403
4049
|
let userConfig = {};
|
|
4050
|
+
let loadedConfigFile = null;
|
|
3404
4051
|
for (const configFile of configFiles) {
|
|
3405
4052
|
if (import_fs16.default.existsSync(configFile)) {
|
|
3406
4053
|
try {
|
|
@@ -3414,16 +4061,31 @@ function loadConfig(projectRoot) {
|
|
|
3414
4061
|
const mod = require(configFile);
|
|
3415
4062
|
userConfig = typeof mod.default === "function" ? mod.default(process.env.NODE_ENV) : mod.default || mod.config || mod;
|
|
3416
4063
|
}
|
|
4064
|
+
loadedConfigFile = import_path18.default.relative(projectRoot, configFile);
|
|
3417
4065
|
break;
|
|
3418
4066
|
} catch (error) {
|
|
3419
|
-
|
|
4067
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4068
|
+
throw new ConfigValidationError(
|
|
4069
|
+
`Failed to load configuration from ${import_path18.default.relative(projectRoot, configFile)}:
|
|
4070
|
+
${errorMessage}
|
|
4071
|
+
\u{1F4A1} Suggestion: Check that your config file exports a valid configuration object`
|
|
4072
|
+
);
|
|
3420
4073
|
}
|
|
3421
4074
|
}
|
|
3422
4075
|
}
|
|
3423
4076
|
const config = deepMerge(DEFAULT_CONFIG, userConfig);
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
4077
|
+
try {
|
|
4078
|
+
validateConfig(config, projectRoot);
|
|
4079
|
+
} catch (error) {
|
|
4080
|
+
if (error instanceof ConfigValidationError) {
|
|
4081
|
+
if (loadedConfigFile) {
|
|
4082
|
+
error.message = `[Configuration Error in ${loadedConfigFile}]
|
|
4083
|
+
|
|
4084
|
+
${error.message}`;
|
|
4085
|
+
}
|
|
4086
|
+
throw error;
|
|
4087
|
+
}
|
|
4088
|
+
throw error;
|
|
3427
4089
|
}
|
|
3428
4090
|
return config;
|
|
3429
4091
|
}
|
|
@@ -3440,14 +4102,14 @@ function getStaticDir(projectRoot, config) {
|
|
|
3440
4102
|
// modules/server/setup.ts
|
|
3441
4103
|
function setupServer(app, options) {
|
|
3442
4104
|
const { projectRoot, appDir, isDev, config } = options;
|
|
3443
|
-
const routeLoader = isDev ? new FilesystemRouteLoader(appDir) : new ManifestRouteLoader(projectRoot);
|
|
4105
|
+
const routeLoader = isDev ? new FilesystemRouteLoader(appDir, projectRoot) : new ManifestRouteLoader(projectRoot);
|
|
3444
4106
|
if (isDev) {
|
|
3445
4107
|
let getRoutes2 = function() {
|
|
3446
4108
|
clearAppRequireCache(appDir);
|
|
3447
|
-
|
|
4109
|
+
sharedLoader.invalidateCache();
|
|
3448
4110
|
return {
|
|
3449
|
-
routes:
|
|
3450
|
-
apiRoutes:
|
|
4111
|
+
routes: sharedLoader.loadRoutes(),
|
|
4112
|
+
apiRoutes: sharedLoader.loadApiRoutes()
|
|
3451
4113
|
};
|
|
3452
4114
|
};
|
|
3453
4115
|
var getRoutes = getRoutes2;
|
|
@@ -3461,19 +4123,20 @@ function setupServer(app, options) {
|
|
|
3461
4123
|
console.log(`[hot-reload] Cleared require cache for: ${rel}`);
|
|
3462
4124
|
}
|
|
3463
4125
|
if (isPageFile) {
|
|
3464
|
-
const loader = new FilesystemRouteLoader(appDir);
|
|
4126
|
+
const loader = new FilesystemRouteLoader(appDir, projectRoot);
|
|
3465
4127
|
const newRoutes = loader.loadRoutes();
|
|
3466
4128
|
writeClientRoutesManifest(newRoutes, projectRoot);
|
|
3467
4129
|
console.log("[hot-reload] Client routes manifest reloaded");
|
|
3468
4130
|
}
|
|
3469
4131
|
};
|
|
3470
|
-
setupHotReload({ app, appDir, waitForBuild, onFileChange });
|
|
4132
|
+
setupHotReload({ app, appDir, projectRoot, waitForBuild, onFileChange });
|
|
3471
4133
|
app.use("/static", import_express.default.static(outDir));
|
|
3472
4134
|
const routes = routeLoader.loadRoutes();
|
|
3473
4135
|
const wssRoutes = routeLoader.loadWssRoutes();
|
|
3474
4136
|
const notFoundPage = routeLoader.loadNotFoundRoute();
|
|
3475
4137
|
const errorPage = routeLoader.loadErrorRoute();
|
|
3476
4138
|
writeClientRoutesManifest(routes, projectRoot);
|
|
4139
|
+
const sharedLoader = new FilesystemRouteLoader(appDir, projectRoot);
|
|
3477
4140
|
return {
|
|
3478
4141
|
routes,
|
|
3479
4142
|
wssRoutes,
|
|
@@ -3561,104 +4224,24 @@ function sanitizeQuery(query) {
|
|
|
3561
4224
|
|
|
3562
4225
|
// modules/server/middleware/rate-limit.ts
|
|
3563
4226
|
var import_express_rate_limit = __toESM(require("express-rate-limit"));
|
|
3564
|
-
|
|
4227
|
+
|
|
4228
|
+
// modules/logger/index.ts
|
|
4229
|
+
var import_pino = __toESM(require("pino"));
|
|
4230
|
+
function createLogger(options = {}) {
|
|
3565
4231
|
const {
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
message: {
|
|
3580
|
-
error: message
|
|
3581
|
-
},
|
|
3582
|
-
standardHeaders,
|
|
3583
|
-
legacyHeaders,
|
|
3584
|
-
skipSuccessfulRequests,
|
|
3585
|
-
skipFailedRequests
|
|
3586
|
-
});
|
|
3587
|
-
}
|
|
3588
|
-
var defaultRateLimiter = createRateLimiter({
|
|
3589
|
-
windowMs: 15 * 60 * 1e3,
|
|
3590
|
-
// 15 minutes
|
|
3591
|
-
max: 100,
|
|
3592
|
-
message: "Too many requests from this IP, please try again later."
|
|
3593
|
-
});
|
|
3594
|
-
var strictRateLimiter = createRateLimiter({
|
|
3595
|
-
windowMs: 15 * 60 * 1e3,
|
|
3596
|
-
// 15 minutes
|
|
3597
|
-
max: 5,
|
|
3598
|
-
message: "Too many authentication attempts, please try again later."
|
|
3599
|
-
});
|
|
3600
|
-
var lenientRateLimiter = createRateLimiter({
|
|
3601
|
-
windowMs: 15 * 60 * 1e3,
|
|
3602
|
-
// 15 minutes
|
|
3603
|
-
max: 200,
|
|
3604
|
-
message: "Too many requests from this IP, please try again later."
|
|
3605
|
-
});
|
|
3606
|
-
|
|
3607
|
-
// modules/server/middleware/auto-rate-limit.ts
|
|
3608
|
-
function matchesStrictPattern(path25, patterns) {
|
|
3609
|
-
for (const pattern of patterns) {
|
|
3610
|
-
const regexPattern = pattern.replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*").replace(/\//g, "\\/");
|
|
3611
|
-
const regex = new RegExp(`^${regexPattern}$`);
|
|
3612
|
-
if (regex.test(path25)) {
|
|
3613
|
-
return true;
|
|
3614
|
-
}
|
|
3615
|
-
}
|
|
3616
|
-
return false;
|
|
3617
|
-
}
|
|
3618
|
-
function isRateLimiter(mw) {
|
|
3619
|
-
if (!mw) return false;
|
|
3620
|
-
if (mw === strictRateLimiter || mw === defaultRateLimiter || mw === lenientRateLimiter) {
|
|
3621
|
-
return true;
|
|
3622
|
-
}
|
|
3623
|
-
if (typeof mw === "function" && mw.name && mw.name.includes("rateLimit")) {
|
|
3624
|
-
return true;
|
|
3625
|
-
}
|
|
3626
|
-
if (mw && typeof mw === "function" && mw.skip || mw.resetKey) {
|
|
3627
|
-
return true;
|
|
3628
|
-
}
|
|
3629
|
-
return false;
|
|
3630
|
-
}
|
|
3631
|
-
function getAutoRateLimiter(route, strictPatterns = []) {
|
|
3632
|
-
const hasRateLimiter = route.middlewares?.some(isRateLimiter) || Object.values(route.methodMiddlewares || {}).some(
|
|
3633
|
-
(mws) => mws?.some(isRateLimiter)
|
|
3634
|
-
);
|
|
3635
|
-
if (hasRateLimiter) {
|
|
3636
|
-
return null;
|
|
3637
|
-
}
|
|
3638
|
-
if (strictPatterns.length > 0 && matchesStrictPattern(route.pattern, strictPatterns)) {
|
|
3639
|
-
console.log(`[rate-limit] Applying strict rate limiter to route: ${route.pattern}`);
|
|
3640
|
-
return strictRateLimiter;
|
|
3641
|
-
}
|
|
3642
|
-
return null;
|
|
3643
|
-
}
|
|
3644
|
-
|
|
3645
|
-
// modules/logger/index.ts
|
|
3646
|
-
var import_pino = __toESM(require("pino"));
|
|
3647
|
-
function createLogger(options = {}) {
|
|
3648
|
-
const {
|
|
3649
|
-
level = process.env.LOG_LEVEL || (process.env.NODE_ENV === "development" ? "debug" : "info"),
|
|
3650
|
-
enabled = process.env.LOG_ENABLED !== "false",
|
|
3651
|
-
pretty = process.env.NODE_ENV === "development",
|
|
3652
|
-
destination
|
|
3653
|
-
} = options;
|
|
3654
|
-
if (!enabled) {
|
|
3655
|
-
return (0, import_pino.default)({ enabled: false });
|
|
3656
|
-
}
|
|
3657
|
-
const baseConfig = {
|
|
3658
|
-
level,
|
|
3659
|
-
base: {
|
|
3660
|
-
name: "@lolyjs/core",
|
|
3661
|
-
env: process.env.NODE_ENV || "development"
|
|
4232
|
+
level = process.env.LOG_LEVEL || (process.env.NODE_ENV === "development" ? "debug" : "info"),
|
|
4233
|
+
enabled = process.env.LOG_ENABLED !== "false",
|
|
4234
|
+
pretty = process.env.NODE_ENV === "development",
|
|
4235
|
+
destination
|
|
4236
|
+
} = options;
|
|
4237
|
+
if (!enabled) {
|
|
4238
|
+
return (0, import_pino.default)({ enabled: false });
|
|
4239
|
+
}
|
|
4240
|
+
const baseConfig = {
|
|
4241
|
+
level,
|
|
4242
|
+
base: {
|
|
4243
|
+
name: "@lolyjs/core",
|
|
4244
|
+
env: process.env.NODE_ENV || "development"
|
|
3662
4245
|
},
|
|
3663
4246
|
timestamp: import_pino.default.stdTimeFunctions.isoTime,
|
|
3664
4247
|
formatters: {
|
|
@@ -3700,8 +4283,8 @@ function resetLogger() {
|
|
|
3700
4283
|
loggerInstance = null;
|
|
3701
4284
|
}
|
|
3702
4285
|
var Logger = class _Logger {
|
|
3703
|
-
constructor(
|
|
3704
|
-
this.pino =
|
|
4286
|
+
constructor(logger5, context = {}) {
|
|
4287
|
+
this.pino = logger5 || getLogger();
|
|
3705
4288
|
this.context = context;
|
|
3706
4289
|
}
|
|
3707
4290
|
/**
|
|
@@ -3778,12 +4361,12 @@ var DEFAULT_IGNORED_PATHS = [
|
|
|
3778
4361
|
/^\/sockjs-node/
|
|
3779
4362
|
// Hot reload websocket
|
|
3780
4363
|
];
|
|
3781
|
-
function shouldIgnorePath(
|
|
4364
|
+
function shouldIgnorePath(path28, ignoredPaths) {
|
|
3782
4365
|
return ignoredPaths.some((pattern) => {
|
|
3783
4366
|
if (typeof pattern === "string") {
|
|
3784
|
-
return
|
|
4367
|
+
return path28 === pattern || path28.startsWith(pattern);
|
|
3785
4368
|
}
|
|
3786
|
-
return pattern.test(
|
|
4369
|
+
return pattern.test(path28);
|
|
3787
4370
|
});
|
|
3788
4371
|
}
|
|
3789
4372
|
function requestLoggerMiddleware(options = {}) {
|
|
@@ -3834,6 +4417,151 @@ function getRequestLogger(req) {
|
|
|
3834
4417
|
return req.logger || logger.child({ requestId: "unknown" });
|
|
3835
4418
|
}
|
|
3836
4419
|
|
|
4420
|
+
// modules/server/middleware/rate-limit.ts
|
|
4421
|
+
var logger2 = createModuleLogger("rate-limit");
|
|
4422
|
+
function validateRateLimitConfig(config) {
|
|
4423
|
+
if (config.windowMs !== void 0 && (config.windowMs < 1e3 || !Number.isInteger(config.windowMs))) {
|
|
4424
|
+
throw new Error(
|
|
4425
|
+
`Invalid rateLimit.windowMs: ${config.windowMs}. Must be an integer >= 1000 (milliseconds)`
|
|
4426
|
+
);
|
|
4427
|
+
}
|
|
4428
|
+
if (config.max !== void 0 && (config.max < 1 || !Number.isInteger(config.max))) {
|
|
4429
|
+
throw new Error(
|
|
4430
|
+
`Invalid rateLimit.max: ${config.max}. Must be an integer >= 1`
|
|
4431
|
+
);
|
|
4432
|
+
}
|
|
4433
|
+
}
|
|
4434
|
+
function createRateLimiter(config = {}) {
|
|
4435
|
+
validateRateLimitConfig(config);
|
|
4436
|
+
const {
|
|
4437
|
+
windowMs = 15 * 60 * 1e3,
|
|
4438
|
+
// 15 minutes
|
|
4439
|
+
max = 100,
|
|
4440
|
+
// limit each IP to 100 requests per windowMs
|
|
4441
|
+
message = "Too many requests from this IP, please try again later.",
|
|
4442
|
+
standardHeaders = true,
|
|
4443
|
+
legacyHeaders = false,
|
|
4444
|
+
skipSuccessfulRequests = false,
|
|
4445
|
+
skipFailedRequests = false,
|
|
4446
|
+
keyGenerator,
|
|
4447
|
+
skip
|
|
4448
|
+
} = config;
|
|
4449
|
+
const limiter = (0, import_express_rate_limit.default)({
|
|
4450
|
+
windowMs,
|
|
4451
|
+
max,
|
|
4452
|
+
message: {
|
|
4453
|
+
error: message,
|
|
4454
|
+
retryAfter: Math.ceil(windowMs / 1e3)
|
|
4455
|
+
// seconds until retry
|
|
4456
|
+
},
|
|
4457
|
+
standardHeaders,
|
|
4458
|
+
legacyHeaders,
|
|
4459
|
+
skipSuccessfulRequests,
|
|
4460
|
+
skipFailedRequests,
|
|
4461
|
+
keyGenerator,
|
|
4462
|
+
skip
|
|
4463
|
+
});
|
|
4464
|
+
const wrappedLimiter = (req, res, next) => {
|
|
4465
|
+
limiter(req, res, (err) => {
|
|
4466
|
+
if (err && res.statusCode === 429) {
|
|
4467
|
+
const ip = req.ip || req.connection?.remoteAddress || "unknown";
|
|
4468
|
+
logger2.warn("Rate limit exceeded", {
|
|
4469
|
+
ip,
|
|
4470
|
+
path: req.path,
|
|
4471
|
+
method: req.method,
|
|
4472
|
+
limit: max,
|
|
4473
|
+
windowMs,
|
|
4474
|
+
retryAfter: Math.ceil(windowMs / 1e3)
|
|
4475
|
+
});
|
|
4476
|
+
}
|
|
4477
|
+
if (err) {
|
|
4478
|
+
return next(err);
|
|
4479
|
+
}
|
|
4480
|
+
next();
|
|
4481
|
+
});
|
|
4482
|
+
};
|
|
4483
|
+
Object.setPrototypeOf(wrappedLimiter, limiter);
|
|
4484
|
+
Object.assign(wrappedLimiter, limiter);
|
|
4485
|
+
return wrappedLimiter;
|
|
4486
|
+
}
|
|
4487
|
+
var defaultRateLimiter = createRateLimiter({
|
|
4488
|
+
windowMs: 15 * 60 * 1e3,
|
|
4489
|
+
// 15 minutes
|
|
4490
|
+
max: 100,
|
|
4491
|
+
message: "Too many requests from this IP, please try again later."
|
|
4492
|
+
});
|
|
4493
|
+
var strictRateLimiter = createRateLimiter({
|
|
4494
|
+
windowMs: 15 * 60 * 1e3,
|
|
4495
|
+
// 15 minutes
|
|
4496
|
+
max: 5,
|
|
4497
|
+
message: "Too many authentication attempts, please try again later."
|
|
4498
|
+
});
|
|
4499
|
+
var lenientRateLimiter = createRateLimiter({
|
|
4500
|
+
windowMs: 15 * 60 * 1e3,
|
|
4501
|
+
// 15 minutes
|
|
4502
|
+
max: 200,
|
|
4503
|
+
message: "Too many requests from this IP, please try again later."
|
|
4504
|
+
});
|
|
4505
|
+
function createRateLimiterFromConfig(config, useApiMax = false) {
|
|
4506
|
+
if (!config) return null;
|
|
4507
|
+
const max = useApiMax ? config.apiMax ?? config.max ?? 100 : config.max ?? 100;
|
|
4508
|
+
const windowMs = config.windowMs ?? 15 * 60 * 1e3;
|
|
4509
|
+
return createRateLimiter({
|
|
4510
|
+
windowMs,
|
|
4511
|
+
max,
|
|
4512
|
+
message: `Too many requests from this IP, please try again after ${Math.ceil(windowMs / 1e3)} seconds.`
|
|
4513
|
+
});
|
|
4514
|
+
}
|
|
4515
|
+
function createStrictRateLimiterFromConfig(config) {
|
|
4516
|
+
if (!config || config.strictMax === void 0) {
|
|
4517
|
+
return strictRateLimiter;
|
|
4518
|
+
}
|
|
4519
|
+
const windowMs = config.windowMs ?? 15 * 60 * 1e3;
|
|
4520
|
+
return createRateLimiter({
|
|
4521
|
+
windowMs,
|
|
4522
|
+
max: config.strictMax,
|
|
4523
|
+
message: `Too many authentication attempts, please try again after ${Math.ceil(windowMs / 1e3)} seconds.`
|
|
4524
|
+
});
|
|
4525
|
+
}
|
|
4526
|
+
|
|
4527
|
+
// modules/server/middleware/auto-rate-limit.ts
|
|
4528
|
+
function matchesStrictPattern(path28, patterns) {
|
|
4529
|
+
for (const pattern of patterns) {
|
|
4530
|
+
const regexPattern = pattern.replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*").replace(/\//g, "\\/");
|
|
4531
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
4532
|
+
if (regex.test(path28)) {
|
|
4533
|
+
return true;
|
|
4534
|
+
}
|
|
4535
|
+
}
|
|
4536
|
+
return false;
|
|
4537
|
+
}
|
|
4538
|
+
function isRateLimiter(mw) {
|
|
4539
|
+
if (!mw) return false;
|
|
4540
|
+
if (mw === strictRateLimiter || mw === defaultRateLimiter || mw === lenientRateLimiter) {
|
|
4541
|
+
return true;
|
|
4542
|
+
}
|
|
4543
|
+
if (typeof mw === "function" && mw.name && mw.name.includes("rateLimit")) {
|
|
4544
|
+
return true;
|
|
4545
|
+
}
|
|
4546
|
+
if (mw && typeof mw === "function" && mw.skip || mw.resetKey) {
|
|
4547
|
+
return true;
|
|
4548
|
+
}
|
|
4549
|
+
return false;
|
|
4550
|
+
}
|
|
4551
|
+
function getAutoRateLimiter(route, strictPatterns = [], rateLimitConfig) {
|
|
4552
|
+
const hasRateLimiter = route.middlewares?.some(isRateLimiter) || Object.values(route.methodMiddlewares || {}).some(
|
|
4553
|
+
(mws) => mws?.some(isRateLimiter)
|
|
4554
|
+
);
|
|
4555
|
+
if (hasRateLimiter) {
|
|
4556
|
+
return null;
|
|
4557
|
+
}
|
|
4558
|
+
if (strictPatterns.length > 0 && matchesStrictPattern(route.pattern, strictPatterns)) {
|
|
4559
|
+
const limiter = rateLimitConfig ? createStrictRateLimiterFromConfig(rateLimitConfig) : strictRateLimiter;
|
|
4560
|
+
return limiter;
|
|
4561
|
+
}
|
|
4562
|
+
return null;
|
|
4563
|
+
}
|
|
4564
|
+
|
|
3837
4565
|
// modules/server/handlers/api.ts
|
|
3838
4566
|
async function handleApiRequest(options) {
|
|
3839
4567
|
const { apiRoutes, urlPath, req, res, env = "dev" } = options;
|
|
@@ -3865,7 +4593,8 @@ async function handleApiRequest(options) {
|
|
|
3865
4593
|
try {
|
|
3866
4594
|
const autoRateLimiter = getAutoRateLimiter(
|
|
3867
4595
|
route,
|
|
3868
|
-
options.strictRateLimitPatterns
|
|
4596
|
+
options.strictRateLimitPatterns,
|
|
4597
|
+
options.rateLimitConfig
|
|
3869
4598
|
);
|
|
3870
4599
|
const reqLogger = getRequestLogger(req);
|
|
3871
4600
|
if (autoRateLimiter) {
|
|
@@ -3877,26 +4606,46 @@ async function handleApiRequest(options) {
|
|
|
3877
4606
|
const globalMws = route.middlewares ?? [];
|
|
3878
4607
|
const perMethodMws = route.methodMiddlewares?.[method] ?? [];
|
|
3879
4608
|
const chain = autoRateLimiter ? [autoRateLimiter, ...globalMws, ...perMethodMws] : [...globalMws, ...perMethodMws];
|
|
3880
|
-
for (
|
|
3881
|
-
const
|
|
3882
|
-
if (
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
4609
|
+
for (let i = 0; i < chain.length; i++) {
|
|
4610
|
+
const mw = chain[i];
|
|
4611
|
+
if (typeof mw !== "function") {
|
|
4612
|
+
reqLogger.warn("Invalid middleware in chain", {
|
|
4613
|
+
route: route.pattern,
|
|
4614
|
+
method,
|
|
4615
|
+
middlewareIndex: i,
|
|
4616
|
+
middlewareType: typeof mw
|
|
4617
|
+
});
|
|
4618
|
+
continue;
|
|
4619
|
+
}
|
|
4620
|
+
try {
|
|
4621
|
+
const isExpressRateLimit = mw.skip || mw.resetKey || mw.name?.includes("rateLimit");
|
|
4622
|
+
if (isExpressRateLimit) {
|
|
4623
|
+
await new Promise((resolve3, reject) => {
|
|
4624
|
+
const next = (err) => {
|
|
4625
|
+
if (err) reject(err);
|
|
4626
|
+
else resolve3();
|
|
4627
|
+
};
|
|
4628
|
+
try {
|
|
4629
|
+
const result = mw(req, res, next);
|
|
4630
|
+
if (result && typeof result.then === "function") {
|
|
4631
|
+
result.then(() => resolve3()).catch(reject);
|
|
4632
|
+
}
|
|
4633
|
+
} catch (err) {
|
|
4634
|
+
reject(err);
|
|
3892
4635
|
}
|
|
3893
|
-
}
|
|
3894
|
-
|
|
3895
|
-
|
|
4636
|
+
});
|
|
4637
|
+
} else {
|
|
4638
|
+
await Promise.resolve(mw(ctx, async () => {
|
|
4639
|
+
}));
|
|
4640
|
+
}
|
|
4641
|
+
} catch (error) {
|
|
4642
|
+
reqLogger.error("API middleware failed", error instanceof Error ? error : new Error(String(error)), {
|
|
4643
|
+
route: route.pattern,
|
|
4644
|
+
method,
|
|
4645
|
+
middlewareIndex: i,
|
|
4646
|
+
middlewareName: mw.name || "anonymous"
|
|
3896
4647
|
});
|
|
3897
|
-
|
|
3898
|
-
await Promise.resolve(mw(ctx, async () => {
|
|
3899
|
-
}));
|
|
4648
|
+
throw error;
|
|
3900
4649
|
}
|
|
3901
4650
|
if (res.headersSent) {
|
|
3902
4651
|
return;
|
|
@@ -3947,42 +4696,289 @@ function createDocumentTree(options) {
|
|
|
3947
4696
|
titleFallback,
|
|
3948
4697
|
descriptionFallback,
|
|
3949
4698
|
chunkHref,
|
|
4699
|
+
entrypointFiles = [],
|
|
3950
4700
|
theme,
|
|
3951
4701
|
clientJsPath = "/static/client.js",
|
|
3952
4702
|
clientCssPath = "/static/client.css",
|
|
3953
|
-
nonce
|
|
4703
|
+
nonce,
|
|
4704
|
+
includeInlineScripts = true
|
|
4705
|
+
// Default true - scripts inline in body for both SSR and SSG
|
|
3954
4706
|
} = options;
|
|
3955
|
-
const metaObj = meta ??
|
|
3956
|
-
const title = metaObj
|
|
3957
|
-
const lang = metaObj
|
|
3958
|
-
const description = metaObj
|
|
4707
|
+
const metaObj = meta ?? null;
|
|
4708
|
+
const title = metaObj?.title ?? titleFallback ?? "My Framework Dev";
|
|
4709
|
+
const lang = metaObj?.lang ?? "en";
|
|
4710
|
+
const description = metaObj?.description ?? descriptionFallback ?? "Demo Loly framework";
|
|
3959
4711
|
const extraMetaTags = [];
|
|
4712
|
+
const linkTags = [];
|
|
3960
4713
|
if (description) {
|
|
3961
4714
|
extraMetaTags.push(
|
|
3962
4715
|
import_react.default.createElement("meta", {
|
|
4716
|
+
key: "meta-description",
|
|
3963
4717
|
name: "description",
|
|
3964
4718
|
content: description
|
|
3965
4719
|
})
|
|
3966
4720
|
);
|
|
3967
4721
|
}
|
|
3968
|
-
if (
|
|
3969
|
-
|
|
4722
|
+
if (metaObj?.robots) {
|
|
4723
|
+
extraMetaTags.push(
|
|
4724
|
+
import_react.default.createElement("meta", {
|
|
4725
|
+
key: "meta-robots",
|
|
4726
|
+
name: "robots",
|
|
4727
|
+
content: metaObj.robots
|
|
4728
|
+
})
|
|
4729
|
+
);
|
|
4730
|
+
}
|
|
4731
|
+
if (metaObj?.themeColor) {
|
|
4732
|
+
extraMetaTags.push(
|
|
4733
|
+
import_react.default.createElement("meta", {
|
|
4734
|
+
key: "meta-theme-color",
|
|
4735
|
+
name: "theme-color",
|
|
4736
|
+
content: metaObj.themeColor
|
|
4737
|
+
})
|
|
4738
|
+
);
|
|
4739
|
+
}
|
|
4740
|
+
if (metaObj?.viewport) {
|
|
4741
|
+
extraMetaTags.push(
|
|
4742
|
+
import_react.default.createElement("meta", {
|
|
4743
|
+
key: "meta-viewport",
|
|
4744
|
+
name: "viewport",
|
|
4745
|
+
content: metaObj.viewport
|
|
4746
|
+
})
|
|
4747
|
+
);
|
|
4748
|
+
}
|
|
4749
|
+
if (metaObj?.canonical) {
|
|
4750
|
+
linkTags.push(
|
|
4751
|
+
import_react.default.createElement("link", {
|
|
4752
|
+
key: "link-canonical",
|
|
4753
|
+
rel: "canonical",
|
|
4754
|
+
href: metaObj.canonical
|
|
4755
|
+
})
|
|
4756
|
+
);
|
|
4757
|
+
}
|
|
4758
|
+
if (metaObj?.openGraph) {
|
|
4759
|
+
const og = metaObj.openGraph;
|
|
4760
|
+
if (og.title) {
|
|
4761
|
+
extraMetaTags.push(
|
|
4762
|
+
import_react.default.createElement("meta", {
|
|
4763
|
+
key: "og-title",
|
|
4764
|
+
property: "og:title",
|
|
4765
|
+
content: og.title
|
|
4766
|
+
})
|
|
4767
|
+
);
|
|
4768
|
+
}
|
|
4769
|
+
if (og.description) {
|
|
4770
|
+
extraMetaTags.push(
|
|
4771
|
+
import_react.default.createElement("meta", {
|
|
4772
|
+
key: "og-description",
|
|
4773
|
+
property: "og:description",
|
|
4774
|
+
content: og.description
|
|
4775
|
+
})
|
|
4776
|
+
);
|
|
4777
|
+
}
|
|
4778
|
+
if (og.type) {
|
|
3970
4779
|
extraMetaTags.push(
|
|
3971
4780
|
import_react.default.createElement("meta", {
|
|
4781
|
+
key: "og-type",
|
|
4782
|
+
property: "og:type",
|
|
4783
|
+
content: og.type
|
|
4784
|
+
})
|
|
4785
|
+
);
|
|
4786
|
+
}
|
|
4787
|
+
if (og.url) {
|
|
4788
|
+
extraMetaTags.push(
|
|
4789
|
+
import_react.default.createElement("meta", {
|
|
4790
|
+
key: "og-url",
|
|
4791
|
+
property: "og:url",
|
|
4792
|
+
content: og.url
|
|
4793
|
+
})
|
|
4794
|
+
);
|
|
4795
|
+
}
|
|
4796
|
+
if (og.image) {
|
|
4797
|
+
if (typeof og.image === "string") {
|
|
4798
|
+
extraMetaTags.push(
|
|
4799
|
+
import_react.default.createElement("meta", {
|
|
4800
|
+
key: "og-image",
|
|
4801
|
+
property: "og:image",
|
|
4802
|
+
content: og.image
|
|
4803
|
+
})
|
|
4804
|
+
);
|
|
4805
|
+
} else {
|
|
4806
|
+
extraMetaTags.push(
|
|
4807
|
+
import_react.default.createElement("meta", {
|
|
4808
|
+
key: "og-image",
|
|
4809
|
+
property: "og:image",
|
|
4810
|
+
content: og.image.url
|
|
4811
|
+
})
|
|
4812
|
+
);
|
|
4813
|
+
if (og.image.width) {
|
|
4814
|
+
extraMetaTags.push(
|
|
4815
|
+
import_react.default.createElement("meta", {
|
|
4816
|
+
key: "og-image-width",
|
|
4817
|
+
property: "og:image:width",
|
|
4818
|
+
content: String(og.image.width)
|
|
4819
|
+
})
|
|
4820
|
+
);
|
|
4821
|
+
}
|
|
4822
|
+
if (og.image.height) {
|
|
4823
|
+
extraMetaTags.push(
|
|
4824
|
+
import_react.default.createElement("meta", {
|
|
4825
|
+
key: "og-image-height",
|
|
4826
|
+
property: "og:image:height",
|
|
4827
|
+
content: String(og.image.height)
|
|
4828
|
+
})
|
|
4829
|
+
);
|
|
4830
|
+
}
|
|
4831
|
+
if (og.image.alt) {
|
|
4832
|
+
extraMetaTags.push(
|
|
4833
|
+
import_react.default.createElement("meta", {
|
|
4834
|
+
key: "og-image-alt",
|
|
4835
|
+
property: "og:image:alt",
|
|
4836
|
+
content: og.image.alt
|
|
4837
|
+
})
|
|
4838
|
+
);
|
|
4839
|
+
}
|
|
4840
|
+
}
|
|
4841
|
+
}
|
|
4842
|
+
if (og.siteName) {
|
|
4843
|
+
extraMetaTags.push(
|
|
4844
|
+
import_react.default.createElement("meta", {
|
|
4845
|
+
key: "og-site-name",
|
|
4846
|
+
property: "og:site_name",
|
|
4847
|
+
content: og.siteName
|
|
4848
|
+
})
|
|
4849
|
+
);
|
|
4850
|
+
}
|
|
4851
|
+
if (og.locale) {
|
|
4852
|
+
extraMetaTags.push(
|
|
4853
|
+
import_react.default.createElement("meta", {
|
|
4854
|
+
key: "og-locale",
|
|
4855
|
+
property: "og:locale",
|
|
4856
|
+
content: og.locale
|
|
4857
|
+
})
|
|
4858
|
+
);
|
|
4859
|
+
}
|
|
4860
|
+
}
|
|
4861
|
+
if (metaObj?.twitter) {
|
|
4862
|
+
const twitter = metaObj.twitter;
|
|
4863
|
+
if (twitter.card) {
|
|
4864
|
+
extraMetaTags.push(
|
|
4865
|
+
import_react.default.createElement("meta", {
|
|
4866
|
+
key: "twitter-card",
|
|
4867
|
+
name: "twitter:card",
|
|
4868
|
+
content: twitter.card
|
|
4869
|
+
})
|
|
4870
|
+
);
|
|
4871
|
+
}
|
|
4872
|
+
if (twitter.title) {
|
|
4873
|
+
extraMetaTags.push(
|
|
4874
|
+
import_react.default.createElement("meta", {
|
|
4875
|
+
key: "twitter-title",
|
|
4876
|
+
name: "twitter:title",
|
|
4877
|
+
content: twitter.title
|
|
4878
|
+
})
|
|
4879
|
+
);
|
|
4880
|
+
}
|
|
4881
|
+
if (twitter.description) {
|
|
4882
|
+
extraMetaTags.push(
|
|
4883
|
+
import_react.default.createElement("meta", {
|
|
4884
|
+
key: "twitter-description",
|
|
4885
|
+
name: "twitter:description",
|
|
4886
|
+
content: twitter.description
|
|
4887
|
+
})
|
|
4888
|
+
);
|
|
4889
|
+
}
|
|
4890
|
+
if (twitter.image) {
|
|
4891
|
+
extraMetaTags.push(
|
|
4892
|
+
import_react.default.createElement("meta", {
|
|
4893
|
+
key: "twitter-image",
|
|
4894
|
+
name: "twitter:image",
|
|
4895
|
+
content: twitter.image
|
|
4896
|
+
})
|
|
4897
|
+
);
|
|
4898
|
+
}
|
|
4899
|
+
if (twitter.imageAlt) {
|
|
4900
|
+
extraMetaTags.push(
|
|
4901
|
+
import_react.default.createElement("meta", {
|
|
4902
|
+
key: "twitter-image-alt",
|
|
4903
|
+
name: "twitter:image:alt",
|
|
4904
|
+
content: twitter.imageAlt
|
|
4905
|
+
})
|
|
4906
|
+
);
|
|
4907
|
+
}
|
|
4908
|
+
if (twitter.site) {
|
|
4909
|
+
extraMetaTags.push(
|
|
4910
|
+
import_react.default.createElement("meta", {
|
|
4911
|
+
key: "twitter-site",
|
|
4912
|
+
name: "twitter:site",
|
|
4913
|
+
content: twitter.site
|
|
4914
|
+
})
|
|
4915
|
+
);
|
|
4916
|
+
}
|
|
4917
|
+
if (twitter.creator) {
|
|
4918
|
+
extraMetaTags.push(
|
|
4919
|
+
import_react.default.createElement("meta", {
|
|
4920
|
+
key: "twitter-creator",
|
|
4921
|
+
name: "twitter:creator",
|
|
4922
|
+
content: twitter.creator
|
|
4923
|
+
})
|
|
4924
|
+
);
|
|
4925
|
+
}
|
|
4926
|
+
}
|
|
4927
|
+
if (metaObj?.metaTags && Array.isArray(metaObj.metaTags)) {
|
|
4928
|
+
metaObj.metaTags.forEach((tag, index) => {
|
|
4929
|
+
extraMetaTags.push(
|
|
4930
|
+
import_react.default.createElement("meta", {
|
|
4931
|
+
key: `meta-custom-${index}`,
|
|
3972
4932
|
name: tag.name,
|
|
3973
4933
|
property: tag.property,
|
|
4934
|
+
httpEquiv: tag.httpEquiv,
|
|
3974
4935
|
content: tag.content
|
|
3975
4936
|
})
|
|
3976
4937
|
);
|
|
3977
|
-
}
|
|
4938
|
+
});
|
|
4939
|
+
}
|
|
4940
|
+
if (metaObj?.links && Array.isArray(metaObj.links)) {
|
|
4941
|
+
metaObj.links.forEach((link, index) => {
|
|
4942
|
+
linkTags.push(
|
|
4943
|
+
import_react.default.createElement("link", {
|
|
4944
|
+
key: `link-custom-${index}`,
|
|
4945
|
+
rel: link.rel,
|
|
4946
|
+
href: link.href,
|
|
4947
|
+
as: link.as,
|
|
4948
|
+
crossOrigin: link.crossorigin,
|
|
4949
|
+
type: link.type
|
|
4950
|
+
})
|
|
4951
|
+
);
|
|
4952
|
+
});
|
|
4953
|
+
}
|
|
4954
|
+
const serialized = JSON.stringify({
|
|
4955
|
+
...initialData,
|
|
4956
|
+
theme
|
|
4957
|
+
});
|
|
4958
|
+
const routerSerialized = JSON.stringify({
|
|
4959
|
+
...routerData
|
|
4960
|
+
});
|
|
4961
|
+
const bodyChildren = [
|
|
4962
|
+
import_react.default.createElement("div", { id: APP_CONTAINER_ID }, appTree)
|
|
4963
|
+
];
|
|
4964
|
+
if (includeInlineScripts) {
|
|
4965
|
+
bodyChildren.push(
|
|
4966
|
+
import_react.default.createElement("script", {
|
|
4967
|
+
key: "initial-data",
|
|
4968
|
+
nonce,
|
|
4969
|
+
dangerouslySetInnerHTML: {
|
|
4970
|
+
__html: `window.${WINDOW_DATA_KEY} = ${serialized};`
|
|
4971
|
+
}
|
|
4972
|
+
}),
|
|
4973
|
+
import_react.default.createElement("script", {
|
|
4974
|
+
key: "router-data",
|
|
4975
|
+
nonce,
|
|
4976
|
+
dangerouslySetInnerHTML: {
|
|
4977
|
+
__html: `window.${ROUTER_DATA_KEY} = ${routerSerialized};`
|
|
4978
|
+
}
|
|
4979
|
+
})
|
|
4980
|
+
);
|
|
3978
4981
|
}
|
|
3979
|
-
const serialized = JSON.stringify({
|
|
3980
|
-
...initialData,
|
|
3981
|
-
theme
|
|
3982
|
-
});
|
|
3983
|
-
const routerSerialized = JSON.stringify({
|
|
3984
|
-
...routerData
|
|
3985
|
-
});
|
|
3986
4982
|
const documentTree = import_react.default.createElement(
|
|
3987
4983
|
"html",
|
|
3988
4984
|
{ lang },
|
|
@@ -3991,13 +4987,25 @@ function createDocumentTree(options) {
|
|
|
3991
4987
|
null,
|
|
3992
4988
|
import_react.default.createElement("meta", { charSet: "utf-8" }),
|
|
3993
4989
|
import_react.default.createElement("title", null, title),
|
|
4990
|
+
// Viewport: use custom if provided, otherwise default
|
|
3994
4991
|
import_react.default.createElement("meta", {
|
|
3995
4992
|
name: "viewport",
|
|
3996
|
-
content: "width=device-width, initial-scale=1"
|
|
4993
|
+
content: metaObj?.viewport ?? "width=device-width, initial-scale=1"
|
|
3997
4994
|
}),
|
|
3998
4995
|
...extraMetaTags,
|
|
4996
|
+
...linkTags,
|
|
4997
|
+
...entrypointFiles.length > 0 ? entrypointFiles.slice(0, -1).map(
|
|
4998
|
+
(file) => import_react.default.createElement("link", {
|
|
4999
|
+
key: `preload-${file}`,
|
|
5000
|
+
rel: "preload",
|
|
5001
|
+
href: file,
|
|
5002
|
+
as: "script"
|
|
5003
|
+
})
|
|
5004
|
+
) : [],
|
|
5005
|
+
// Preload route-specific chunk if available
|
|
3999
5006
|
chunkHref && import_react.default.createElement("link", {
|
|
4000
|
-
|
|
5007
|
+
key: `preload-${chunkHref}`,
|
|
5008
|
+
rel: "preload",
|
|
4001
5009
|
href: chunkHref,
|
|
4002
5010
|
as: "script"
|
|
4003
5011
|
}),
|
|
@@ -4010,33 +5018,34 @@ function createDocumentTree(options) {
|
|
|
4010
5018
|
rel: "stylesheet",
|
|
4011
5019
|
href: clientCssPath
|
|
4012
5020
|
}),
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
5021
|
+
...entrypointFiles.length > 0 ? entrypointFiles.map(
|
|
5022
|
+
(file) => import_react.default.createElement("script", {
|
|
5023
|
+
key: file,
|
|
5024
|
+
src: file,
|
|
5025
|
+
defer: true,
|
|
5026
|
+
nonce
|
|
5027
|
+
// CSP nonce for external scripts
|
|
5028
|
+
})
|
|
5029
|
+
) : [
|
|
5030
|
+
import_react.default.createElement("script", {
|
|
5031
|
+
key: "client",
|
|
5032
|
+
src: clientJsPath,
|
|
5033
|
+
defer: true,
|
|
5034
|
+
nonce
|
|
5035
|
+
// CSP nonce for external scripts
|
|
5036
|
+
})
|
|
5037
|
+
]
|
|
4017
5038
|
),
|
|
4018
5039
|
import_react.default.createElement(
|
|
4019
5040
|
"body",
|
|
4020
5041
|
{
|
|
4021
5042
|
style: { margin: 0 },
|
|
4022
|
-
className: [initialData.className, theme].filter(Boolean).join(" "),
|
|
5043
|
+
className: [initialData.className || "", theme].filter(Boolean).join(" "),
|
|
4023
5044
|
suppressHydrationWarning: true
|
|
4024
5045
|
// Allow theme class to differ between server and client initially
|
|
4025
5046
|
},
|
|
4026
|
-
|
|
4027
|
-
)
|
|
4028
|
-
import_react.default.createElement("script", {
|
|
4029
|
-
nonce,
|
|
4030
|
-
dangerouslySetInnerHTML: {
|
|
4031
|
-
__html: `window.${WINDOW_DATA_KEY} = ${serialized};`
|
|
4032
|
-
}
|
|
4033
|
-
}),
|
|
4034
|
-
import_react.default.createElement("script", {
|
|
4035
|
-
nonce,
|
|
4036
|
-
dangerouslySetInnerHTML: {
|
|
4037
|
-
__html: `window.${ROUTER_DATA_KEY} = ${routerSerialized};`
|
|
4038
|
-
}
|
|
4039
|
-
})
|
|
5047
|
+
...bodyChildren
|
|
5048
|
+
)
|
|
4040
5049
|
);
|
|
4041
5050
|
return documentTree;
|
|
4042
5051
|
}
|
|
@@ -4052,7 +5061,7 @@ function buildInitialData(urlPath, params, loaderResult) {
|
|
|
4052
5061
|
params,
|
|
4053
5062
|
props,
|
|
4054
5063
|
metadata: loaderResult.metadata ?? null,
|
|
4055
|
-
className: loaderResult.className
|
|
5064
|
+
className: loaderResult.className,
|
|
4056
5065
|
error: false,
|
|
4057
5066
|
notFound: false
|
|
4058
5067
|
};
|
|
@@ -4068,12 +5077,25 @@ var buildRouterData = (req) => {
|
|
|
4068
5077
|
};
|
|
4069
5078
|
|
|
4070
5079
|
// modules/server/handlers/middleware.ts
|
|
5080
|
+
var import_path20 = __toESM(require("path"));
|
|
4071
5081
|
async function runRouteMiddlewares(route, ctx) {
|
|
4072
|
-
for (
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
5082
|
+
for (let i = 0; i < route.middlewares.length; i++) {
|
|
5083
|
+
const mw = route.middlewares[i];
|
|
5084
|
+
try {
|
|
5085
|
+
await Promise.resolve(
|
|
5086
|
+
mw(ctx, async () => {
|
|
5087
|
+
})
|
|
5088
|
+
);
|
|
5089
|
+
} catch (error) {
|
|
5090
|
+
const reqLogger = getRequestLogger(ctx.req);
|
|
5091
|
+
const relativePath = route.pageFile ? import_path20.default.relative(process.cwd(), route.pageFile) : route.pattern;
|
|
5092
|
+
reqLogger.error("Route middleware failed", error instanceof Error ? error : new Error(String(error)), {
|
|
5093
|
+
route: route.pattern,
|
|
5094
|
+
middlewareIndex: i,
|
|
5095
|
+
pageFile: relativePath
|
|
5096
|
+
});
|
|
5097
|
+
throw error;
|
|
5098
|
+
}
|
|
4077
5099
|
if (ctx.res.headersSent) {
|
|
4078
5100
|
return;
|
|
4079
5101
|
}
|
|
@@ -4081,11 +5103,57 @@ async function runRouteMiddlewares(route, ctx) {
|
|
|
4081
5103
|
}
|
|
4082
5104
|
|
|
4083
5105
|
// modules/server/handlers/server-hook.ts
|
|
5106
|
+
var import_path21 = __toESM(require("path"));
|
|
5107
|
+
function createServerHookErrorMessage(error, hookType, routePattern, filePath) {
|
|
5108
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
5109
|
+
const errorStack = error instanceof Error ? error.stack : void 0;
|
|
5110
|
+
let message = `[${hookType.toUpperCase()} SERVER HOOK ERROR]
|
|
5111
|
+
`;
|
|
5112
|
+
message += `Route: ${routePattern}
|
|
5113
|
+
`;
|
|
5114
|
+
if (filePath) {
|
|
5115
|
+
const relativePath = import_path21.default.relative(process.cwd(), filePath);
|
|
5116
|
+
message += `File: ${relativePath}
|
|
5117
|
+
`;
|
|
5118
|
+
}
|
|
5119
|
+
message += `Error: ${errorMessage}
|
|
5120
|
+
`;
|
|
5121
|
+
if (errorMessage.includes("Cannot find module")) {
|
|
5122
|
+
message += `
|
|
5123
|
+
\u{1F4A1} Suggestion: Check that all imports in your ${hookType}.server.hook.ts are correct.
|
|
5124
|
+
`;
|
|
5125
|
+
} else if (errorMessage.includes("is not defined") || errorMessage.includes("Cannot read property")) {
|
|
5126
|
+
message += `
|
|
5127
|
+
\u{1F4A1} Suggestion: Verify that all variables and properties exist in your ${hookType}.server.hook.ts.
|
|
5128
|
+
`;
|
|
5129
|
+
} else if (errorMessage.includes("async") || errorMessage.includes("await")) {
|
|
5130
|
+
message += `
|
|
5131
|
+
\u{1F4A1} Suggestion: Make sure getServerSideProps is an async function and all promises are awaited.
|
|
5132
|
+
`;
|
|
5133
|
+
}
|
|
5134
|
+
if (errorStack) {
|
|
5135
|
+
message += `
|
|
5136
|
+
Stack trace:
|
|
5137
|
+
${errorStack}`;
|
|
5138
|
+
}
|
|
5139
|
+
return message;
|
|
5140
|
+
}
|
|
4084
5141
|
async function runRouteServerHook(route, ctx) {
|
|
4085
5142
|
if (!route.loader) {
|
|
4086
5143
|
return { props: {} };
|
|
4087
5144
|
}
|
|
4088
|
-
|
|
5145
|
+
try {
|
|
5146
|
+
return await route.loader(ctx);
|
|
5147
|
+
} catch (error) {
|
|
5148
|
+
const detailedError = new Error(
|
|
5149
|
+
createServerHookErrorMessage(error, "page", route.pattern, route.pageFile)
|
|
5150
|
+
);
|
|
5151
|
+
if (error instanceof Error && error.stack) {
|
|
5152
|
+
detailedError.stack = error.stack;
|
|
5153
|
+
}
|
|
5154
|
+
detailedError.originalError = error;
|
|
5155
|
+
throw detailedError;
|
|
5156
|
+
}
|
|
4089
5157
|
}
|
|
4090
5158
|
|
|
4091
5159
|
// modules/server/handlers/response.ts
|
|
@@ -4126,26 +5194,26 @@ function handleNotFound(res, urlPath) {
|
|
|
4126
5194
|
|
|
4127
5195
|
// modules/server/handlers/ssg.ts
|
|
4128
5196
|
var import_fs17 = __toESM(require("fs"));
|
|
4129
|
-
var
|
|
4130
|
-
var
|
|
5197
|
+
var import_path22 = __toESM(require("path"));
|
|
5198
|
+
var logger3 = createModuleLogger("ssg");
|
|
4131
5199
|
function getSsgDirForPath(baseDir, urlPath) {
|
|
4132
5200
|
const clean = urlPath === "/" ? "" : urlPath.replace(/^\/+/, "");
|
|
4133
|
-
return
|
|
5201
|
+
return import_path22.default.join(baseDir, clean);
|
|
4134
5202
|
}
|
|
4135
5203
|
function getSsgHtmlPath(baseDir, urlPath) {
|
|
4136
5204
|
const dir = getSsgDirForPath(baseDir, urlPath);
|
|
4137
|
-
return
|
|
5205
|
+
return import_path22.default.join(dir, "index.html");
|
|
4138
5206
|
}
|
|
4139
5207
|
function getSsgDataPath(baseDir, urlPath) {
|
|
4140
5208
|
const dir = getSsgDirForPath(baseDir, urlPath);
|
|
4141
|
-
return
|
|
5209
|
+
return import_path22.default.join(dir, "data.json");
|
|
4142
5210
|
}
|
|
4143
5211
|
function tryServeSsgHtml(res, ssgOutDir, urlPath) {
|
|
4144
5212
|
const ssgHtmlPath = getSsgHtmlPath(ssgOutDir, urlPath);
|
|
4145
5213
|
if (!import_fs17.default.existsSync(ssgHtmlPath)) {
|
|
4146
5214
|
return false;
|
|
4147
5215
|
}
|
|
4148
|
-
|
|
5216
|
+
logger3.info("Serving SSG HTML", { urlPath, ssgHtmlPath });
|
|
4149
5217
|
res.setHeader(
|
|
4150
5218
|
"Content-Security-Policy",
|
|
4151
5219
|
"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';"
|
|
@@ -4167,13 +5235,43 @@ function tryServeSsgData(res, ssgOutDir, urlPath) {
|
|
|
4167
5235
|
res.status(200).end(raw);
|
|
4168
5236
|
return true;
|
|
4169
5237
|
} catch (err) {
|
|
4170
|
-
|
|
5238
|
+
logger3.error("Error reading SSG data", err, { urlPath, ssgDataPath });
|
|
4171
5239
|
return false;
|
|
4172
5240
|
}
|
|
4173
5241
|
}
|
|
4174
5242
|
|
|
4175
5243
|
// modules/server/handlers/pages.ts
|
|
4176
5244
|
init_globals();
|
|
5245
|
+
var import_path23 = __toESM(require("path"));
|
|
5246
|
+
function mergeMetadata(base, override) {
|
|
5247
|
+
if (!base && !override) return null;
|
|
5248
|
+
if (!base) return override;
|
|
5249
|
+
if (!override) return base;
|
|
5250
|
+
return {
|
|
5251
|
+
// Simple fields: override wins
|
|
5252
|
+
title: override.title ?? base.title,
|
|
5253
|
+
description: override.description ?? base.description,
|
|
5254
|
+
lang: override.lang ?? base.lang,
|
|
5255
|
+
canonical: override.canonical ?? base.canonical,
|
|
5256
|
+
robots: override.robots ?? base.robots,
|
|
5257
|
+
themeColor: override.themeColor ?? base.themeColor,
|
|
5258
|
+
viewport: override.viewport ?? base.viewport,
|
|
5259
|
+
// Nested objects: shallow merge (override wins for each field)
|
|
5260
|
+
openGraph: override.openGraph ? {
|
|
5261
|
+
...base.openGraph,
|
|
5262
|
+
...override.openGraph,
|
|
5263
|
+
// For image, if override has image, use it entirely (don't merge)
|
|
5264
|
+
image: override.openGraph.image ?? base.openGraph?.image
|
|
5265
|
+
} : base.openGraph,
|
|
5266
|
+
twitter: override.twitter ? {
|
|
5267
|
+
...base.twitter,
|
|
5268
|
+
...override.twitter
|
|
5269
|
+
} : base.twitter,
|
|
5270
|
+
// Arrays: override replaces base entirely (not merged)
|
|
5271
|
+
metaTags: override.metaTags ?? base.metaTags,
|
|
5272
|
+
links: override.links ?? base.links
|
|
5273
|
+
};
|
|
5274
|
+
}
|
|
4177
5275
|
function isDataRequest(req) {
|
|
4178
5276
|
return req.query && req.query.__fw_data === "1" || req.headers["x-fw-data"] === "1";
|
|
4179
5277
|
}
|
|
@@ -4250,9 +5348,13 @@ async function handlePageRequestInternal(options) {
|
|
|
4250
5348
|
}
|
|
4251
5349
|
} catch (error) {
|
|
4252
5350
|
const reqLogger2 = getRequestLogger(req);
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
5351
|
+
const layoutFile = notFoundPage.layoutFiles[i];
|
|
5352
|
+
const relativeLayoutPath = layoutFile ? import_path23.default.relative(projectRoot || process.cwd(), layoutFile) : "unknown";
|
|
5353
|
+
reqLogger2.warn("Layout server hook failed for not-found page", {
|
|
5354
|
+
error: error instanceof Error ? error.message : String(error),
|
|
5355
|
+
stack: error instanceof Error ? error.stack : void 0,
|
|
5356
|
+
layoutFile: relativeLayoutPath,
|
|
5357
|
+
layoutIndex: i
|
|
4256
5358
|
});
|
|
4257
5359
|
}
|
|
4258
5360
|
}
|
|
@@ -4274,6 +5376,10 @@ async function handlePageRequestInternal(options) {
|
|
|
4274
5376
|
const appTree2 = buildAppTree(notFoundPage, {}, initialData2.props);
|
|
4275
5377
|
initialData2.notFound = true;
|
|
4276
5378
|
const nonce2 = res.locals.nonce || void 0;
|
|
5379
|
+
const entrypointFiles2 = [];
|
|
5380
|
+
if (assetManifest?.entrypoints?.client) {
|
|
5381
|
+
entrypointFiles2.push(...assetManifest.entrypoints.client.map((file) => `${STATIC_PATH}/${file}`));
|
|
5382
|
+
}
|
|
4277
5383
|
const documentTree2 = createDocumentTree({
|
|
4278
5384
|
appTree: appTree2,
|
|
4279
5385
|
initialData: initialData2,
|
|
@@ -4282,6 +5388,7 @@ async function handlePageRequestInternal(options) {
|
|
|
4282
5388
|
titleFallback: "Not found",
|
|
4283
5389
|
descriptionFallback: "Loly demo",
|
|
4284
5390
|
chunkHref: null,
|
|
5391
|
+
entrypointFiles: entrypointFiles2,
|
|
4285
5392
|
theme,
|
|
4286
5393
|
clientJsPath,
|
|
4287
5394
|
clientCssPath,
|
|
@@ -4334,6 +5441,7 @@ async function handlePageRequestInternal(options) {
|
|
|
4334
5441
|
return;
|
|
4335
5442
|
}
|
|
4336
5443
|
const layoutProps = {};
|
|
5444
|
+
const layoutMetadata = [];
|
|
4337
5445
|
const reqLogger = getRequestLogger(req);
|
|
4338
5446
|
if (route.layoutServerHooks && route.layoutServerHooks.length > 0) {
|
|
4339
5447
|
for (let i = 0; i < route.layoutServerHooks.length; i++) {
|
|
@@ -4344,11 +5452,19 @@ async function handlePageRequestInternal(options) {
|
|
|
4344
5452
|
if (layoutResult.props) {
|
|
4345
5453
|
Object.assign(layoutProps, layoutResult.props);
|
|
4346
5454
|
}
|
|
5455
|
+
if (layoutResult.metadata) {
|
|
5456
|
+
layoutMetadata.push(layoutResult.metadata);
|
|
5457
|
+
}
|
|
4347
5458
|
} catch (error) {
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
5459
|
+
const layoutFile = route.layoutFiles[i];
|
|
5460
|
+
const relativeLayoutPath = layoutFile ? import_path23.default.relative(projectRoot || process.cwd(), layoutFile) : "unknown";
|
|
5461
|
+
reqLogger.warn("Layout server hook failed", {
|
|
5462
|
+
error: error instanceof Error ? error.message : String(error),
|
|
5463
|
+
stack: error instanceof Error ? error.stack : void 0,
|
|
5464
|
+
layoutFile: relativeLayoutPath,
|
|
5465
|
+
route: route.pattern,
|
|
5466
|
+
layoutIndex: i,
|
|
5467
|
+
suggestion: "Check your layout.server.hook.ts file for errors"
|
|
4352
5468
|
});
|
|
4353
5469
|
}
|
|
4354
5470
|
}
|
|
@@ -4361,10 +5477,25 @@ async function handlePageRequestInternal(options) {
|
|
|
4361
5477
|
loaderResult.theme = theme;
|
|
4362
5478
|
}
|
|
4363
5479
|
} catch (error) {
|
|
5480
|
+
const relativePagePath = route.pageFile ? import_path23.default.relative(projectRoot || process.cwd(), route.pageFile) : "unknown";
|
|
5481
|
+
reqLogger.error("Page server hook failed", {
|
|
5482
|
+
error: error instanceof Error ? error.message : String(error),
|
|
5483
|
+
stack: error instanceof Error ? error.stack : void 0,
|
|
5484
|
+
pageFile: relativePagePath,
|
|
5485
|
+
route: route.pattern,
|
|
5486
|
+
pathname: urlPath,
|
|
5487
|
+
suggestion: "Check your page.server.hook.ts (or server.hook.ts) file for errors"
|
|
5488
|
+
});
|
|
4364
5489
|
if (isDataReq) {
|
|
4365
5490
|
res.statusCode = 500;
|
|
4366
5491
|
res.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
4367
|
-
|
|
5492
|
+
const errorResponse = {
|
|
5493
|
+
error: true,
|
|
5494
|
+
message: error instanceof Error ? error.message : String(error),
|
|
5495
|
+
route: route.pattern,
|
|
5496
|
+
pageFile: relativePagePath
|
|
5497
|
+
};
|
|
5498
|
+
res.end(JSON.stringify(errorResponse, null, 2));
|
|
4368
5499
|
return;
|
|
4369
5500
|
} else {
|
|
4370
5501
|
if (errorPage) {
|
|
@@ -4381,9 +5512,19 @@ async function handlePageRequestInternal(options) {
|
|
|
4381
5512
|
...loaderResult.props || {}
|
|
4382
5513
|
// Props from page (overrides layout)
|
|
4383
5514
|
};
|
|
5515
|
+
let combinedMetadata = null;
|
|
5516
|
+
for (const layoutMeta of layoutMetadata) {
|
|
5517
|
+
if (layoutMeta) {
|
|
5518
|
+
combinedMetadata = mergeMetadata(combinedMetadata, layoutMeta);
|
|
5519
|
+
}
|
|
5520
|
+
}
|
|
5521
|
+
if (loaderResult.metadata) {
|
|
5522
|
+
combinedMetadata = mergeMetadata(combinedMetadata, loaderResult.metadata);
|
|
5523
|
+
}
|
|
4384
5524
|
const combinedLoaderResult = {
|
|
4385
5525
|
...loaderResult,
|
|
4386
|
-
props: combinedProps
|
|
5526
|
+
props: combinedProps,
|
|
5527
|
+
metadata: combinedMetadata
|
|
4387
5528
|
};
|
|
4388
5529
|
if (isDataReq) {
|
|
4389
5530
|
handleDataResponse(res, combinedLoaderResult, theme);
|
|
@@ -4413,6 +5554,10 @@ async function handlePageRequestInternal(options) {
|
|
|
4413
5554
|
}
|
|
4414
5555
|
}
|
|
4415
5556
|
const nonce = res.locals.nonce || void 0;
|
|
5557
|
+
const entrypointFiles = [];
|
|
5558
|
+
if (assetManifest?.entrypoints?.client) {
|
|
5559
|
+
entrypointFiles.push(...assetManifest.entrypoints.client.map((file) => `${STATIC_PATH}/${file}`));
|
|
5560
|
+
}
|
|
4416
5561
|
const documentTree = createDocumentTree({
|
|
4417
5562
|
appTree,
|
|
4418
5563
|
initialData,
|
|
@@ -4421,6 +5566,7 @@ async function handlePageRequestInternal(options) {
|
|
|
4421
5566
|
titleFallback: "Loly framework",
|
|
4422
5567
|
descriptionFallback: "Loly demo",
|
|
4423
5568
|
chunkHref,
|
|
5569
|
+
entrypointFiles,
|
|
4424
5570
|
theme,
|
|
4425
5571
|
clientJsPath,
|
|
4426
5572
|
clientCssPath,
|
|
@@ -4439,7 +5585,16 @@ async function handlePageRequestInternal(options) {
|
|
|
4439
5585
|
onShellError(err) {
|
|
4440
5586
|
didError = true;
|
|
4441
5587
|
const reqLogger2 = getRequestLogger(req);
|
|
4442
|
-
|
|
5588
|
+
const routePattern = matched?.route?.pattern || "unknown";
|
|
5589
|
+
reqLogger2.error("SSR shell error", err, { route: routePattern });
|
|
5590
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
5591
|
+
console.error(`
|
|
5592
|
+
\u274C [framework][ssr] Shell error for route "${routePattern}":`);
|
|
5593
|
+
console.error(` ${errorMessage}`);
|
|
5594
|
+
if (err instanceof Error && err.stack) {
|
|
5595
|
+
console.error(` Stack: ${err.stack.split("\n").slice(0, 3).join("\n ")}`);
|
|
5596
|
+
}
|
|
5597
|
+
console.error("\u{1F4A1} This usually indicates a React rendering error\n");
|
|
4443
5598
|
if (!res.headersSent && errorPage) {
|
|
4444
5599
|
renderErrorPageWithStream(errorPage, req, res, err, routeChunks, theme, projectRoot, env);
|
|
4445
5600
|
} else if (!res.headersSent) {
|
|
@@ -4452,7 +5607,11 @@ async function handlePageRequestInternal(options) {
|
|
|
4452
5607
|
onError(err) {
|
|
4453
5608
|
didError = true;
|
|
4454
5609
|
const reqLogger2 = getRequestLogger(req);
|
|
4455
|
-
|
|
5610
|
+
const routePattern = matched?.route?.pattern || "unknown";
|
|
5611
|
+
reqLogger2.error("SSR error", err, { route: routePattern });
|
|
5612
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
5613
|
+
console.error(`\u26A0\uFE0F [framework][ssr] Error during streaming for route "${routePattern}":`);
|
|
5614
|
+
console.error(` ${errorMessage}`);
|
|
4456
5615
|
}
|
|
4457
5616
|
});
|
|
4458
5617
|
req.on("close", () => {
|
|
@@ -4481,9 +5640,13 @@ async function renderErrorPageWithStream(errorPage, req, res, error, routeChunks
|
|
|
4481
5640
|
Object.assign(layoutProps, layoutResult.props);
|
|
4482
5641
|
}
|
|
4483
5642
|
} catch (err) {
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
5643
|
+
const layoutFile = errorPage.layoutFiles[i];
|
|
5644
|
+
const relativeLayoutPath = layoutFile ? import_path23.default.relative(projectRoot || process.cwd(), layoutFile) : "unknown";
|
|
5645
|
+
reqLogger.warn("Layout server hook failed for error page", {
|
|
5646
|
+
error: err instanceof Error ? err.message : String(err),
|
|
5647
|
+
stack: err instanceof Error ? err.stack : void 0,
|
|
5648
|
+
layoutFile: relativeLayoutPath,
|
|
5649
|
+
layoutIndex: i
|
|
4487
5650
|
});
|
|
4488
5651
|
}
|
|
4489
5652
|
}
|
|
@@ -4530,6 +5693,10 @@ async function renderErrorPageWithStream(errorPage, req, res, error, routeChunks
|
|
|
4530
5693
|
}
|
|
4531
5694
|
}
|
|
4532
5695
|
const nonce = res.locals.nonce || void 0;
|
|
5696
|
+
const entrypointFiles = [];
|
|
5697
|
+
if (assetManifest?.entrypoints?.client) {
|
|
5698
|
+
entrypointFiles.push(...assetManifest.entrypoints.client.map((file) => `${STATIC_PATH}/${file}`));
|
|
5699
|
+
}
|
|
4533
5700
|
const documentTree = createDocumentTree({
|
|
4534
5701
|
appTree,
|
|
4535
5702
|
initialData,
|
|
@@ -4655,7 +5822,7 @@ async function getServerConfig(projectRoot) {
|
|
|
4655
5822
|
}
|
|
4656
5823
|
|
|
4657
5824
|
// modules/server/routes.ts
|
|
4658
|
-
var
|
|
5825
|
+
var import_path24 = __toESM(require("path"));
|
|
4659
5826
|
function setupRoutes(options) {
|
|
4660
5827
|
const {
|
|
4661
5828
|
app,
|
|
@@ -4670,21 +5837,23 @@ function setupRoutes(options) {
|
|
|
4670
5837
|
config
|
|
4671
5838
|
} = options;
|
|
4672
5839
|
const routeChunks = routeLoader.loadRouteChunks();
|
|
4673
|
-
const ssgOutDir =
|
|
4674
|
-
config ? getBuildDir(projectRoot, config) :
|
|
5840
|
+
const ssgOutDir = import_path24.default.join(
|
|
5841
|
+
config ? getBuildDir(projectRoot, config) : import_path24.default.join(projectRoot, BUILD_FOLDER_NAME),
|
|
4675
5842
|
"ssg"
|
|
4676
5843
|
);
|
|
4677
5844
|
app.all("/api/*", async (req, res) => {
|
|
4678
5845
|
const apiRoutes = isDev && getRoutes ? getRoutes().apiRoutes : initialApiRoutes;
|
|
4679
5846
|
const serverConfig = await getServerConfig(projectRoot);
|
|
4680
5847
|
const strictPatterns = serverConfig.rateLimit?.strictPatterns || [];
|
|
5848
|
+
const rateLimitConfig = serverConfig.rateLimit;
|
|
4681
5849
|
await handleApiRequest({
|
|
4682
5850
|
apiRoutes,
|
|
4683
5851
|
urlPath: req.path,
|
|
4684
5852
|
req,
|
|
4685
5853
|
res,
|
|
4686
5854
|
env: isDev ? "dev" : "prod",
|
|
4687
|
-
strictRateLimitPatterns: strictPatterns
|
|
5855
|
+
strictRateLimitPatterns: strictPatterns,
|
|
5856
|
+
rateLimitConfig
|
|
4688
5857
|
});
|
|
4689
5858
|
});
|
|
4690
5859
|
app.get("*", async (req, res) => {
|
|
@@ -4937,12 +6106,29 @@ var setupApplication = async ({
|
|
|
4937
6106
|
corsOptions.origin = process.env.NODE_ENV === "development";
|
|
4938
6107
|
}
|
|
4939
6108
|
app.use((0, import_cors.default)(corsOptions));
|
|
4940
|
-
if (rateLimit2
|
|
4941
|
-
const
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
|
|
6109
|
+
if (rateLimit2) {
|
|
6110
|
+
const shouldApply = process.env.NODE_ENV !== "development" || process.env.ENABLE_RATE_LIMIT === "true";
|
|
6111
|
+
if (shouldApply) {
|
|
6112
|
+
try {
|
|
6113
|
+
const generalLimiter = createRateLimiterFromConfig(rateLimit2, false);
|
|
6114
|
+
if (generalLimiter) {
|
|
6115
|
+
app.use(generalLimiter);
|
|
6116
|
+
const logger5 = createModuleLogger("server");
|
|
6117
|
+
logger5.info("Rate limiting enabled", {
|
|
6118
|
+
windowMs: rateLimit2.windowMs ?? 15 * 60 * 1e3,
|
|
6119
|
+
max: rateLimit2.max ?? 100,
|
|
6120
|
+
apiMax: rateLimit2.apiMax,
|
|
6121
|
+
strictMax: rateLimit2.strictMax,
|
|
6122
|
+
strictPatterns: rateLimit2.strictPatterns?.length ?? 0
|
|
6123
|
+
});
|
|
6124
|
+
}
|
|
6125
|
+
} catch (error) {
|
|
6126
|
+
const logger5 = createModuleLogger("server");
|
|
6127
|
+
logger5.error("Failed to setup rate limiting", {
|
|
6128
|
+
error: error instanceof Error ? error.message : String(error)
|
|
6129
|
+
});
|
|
6130
|
+
}
|
|
6131
|
+
}
|
|
4946
6132
|
}
|
|
4947
6133
|
app.use((0, import_cookie_parser.default)());
|
|
4948
6134
|
app.use(import_express2.default.json({ limit: bodyLimit }));
|
|
@@ -4957,22 +6143,31 @@ var setupApplication = async ({
|
|
|
4957
6143
|
|
|
4958
6144
|
// src/server.ts
|
|
4959
6145
|
var import_dotenv2 = __toESM(require("dotenv"));
|
|
4960
|
-
var envPath =
|
|
6146
|
+
var envPath = import_path25.default.join(process.cwd(), ".env");
|
|
4961
6147
|
if (import_fs18.default.existsSync(envPath)) {
|
|
4962
6148
|
import_dotenv2.default.config({ path: envPath });
|
|
4963
6149
|
} else {
|
|
4964
6150
|
import_dotenv2.default.config();
|
|
4965
6151
|
}
|
|
4966
|
-
var
|
|
6152
|
+
var logger4 = createModuleLogger("server");
|
|
4967
6153
|
async function startServer(options = {}) {
|
|
4968
6154
|
const isDev = options.isDev ?? process.env.NODE_ENV === "development";
|
|
4969
6155
|
const projectRoot = options.rootDir ?? process.cwd();
|
|
4970
|
-
|
|
6156
|
+
let config;
|
|
6157
|
+
try {
|
|
6158
|
+
config = options.config ?? loadConfig(projectRoot);
|
|
6159
|
+
} catch (error) {
|
|
6160
|
+
if (error instanceof ConfigValidationError) {
|
|
6161
|
+
console.error("\n" + error.message + "\n");
|
|
6162
|
+
process.exit(1);
|
|
6163
|
+
}
|
|
6164
|
+
throw error;
|
|
6165
|
+
}
|
|
4971
6166
|
const port = options.port ?? (process.env.PORT ? parseInt(process.env.PORT, 10) : void 0) ?? config.server.port;
|
|
4972
6167
|
const host = process.env.HOST ?? (!isDev ? "0.0.0.0" : void 0) ?? config.server.host;
|
|
4973
|
-
const appDir = options.appDir ?? (isDev ? getAppDir(projectRoot, config) :
|
|
6168
|
+
const appDir = options.appDir ?? (isDev ? getAppDir(projectRoot, config) : import_path25.default.join(getBuildDir(projectRoot, config), "server"));
|
|
4974
6169
|
if (!isDev && !import_fs18.default.existsSync(appDir)) {
|
|
4975
|
-
|
|
6170
|
+
logger4.error("Compiled directory not found", void 0, {
|
|
4976
6171
|
buildDir: config.directories.build,
|
|
4977
6172
|
appDir,
|
|
4978
6173
|
environment: "production"
|
|
@@ -4989,7 +6184,7 @@ async function startServer(options = {}) {
|
|
|
4989
6184
|
isDev,
|
|
4990
6185
|
config
|
|
4991
6186
|
});
|
|
4992
|
-
const routeLoader = isDev ? new FilesystemRouteLoader(appDir) : new ManifestRouteLoader(projectRoot);
|
|
6187
|
+
const routeLoader = isDev ? new FilesystemRouteLoader(appDir, projectRoot) : new ManifestRouteLoader(projectRoot);
|
|
4993
6188
|
setupWssEvents({
|
|
4994
6189
|
httpServer,
|
|
4995
6190
|
wssRoutes
|
|
@@ -5041,10 +6236,10 @@ async function startProdServer(options = {}) {
|
|
|
5041
6236
|
}
|
|
5042
6237
|
|
|
5043
6238
|
// modules/build/ssg/builder.ts
|
|
5044
|
-
var
|
|
6239
|
+
var import_path29 = __toESM(require("path"));
|
|
5045
6240
|
|
|
5046
6241
|
// modules/build/ssg/path.ts
|
|
5047
|
-
var
|
|
6242
|
+
var import_path26 = __toESM(require("path"));
|
|
5048
6243
|
function buildPathFromPattern(pattern, params) {
|
|
5049
6244
|
const segments = pattern.split("/").filter(Boolean);
|
|
5050
6245
|
const parts = [];
|
|
@@ -5073,12 +6268,12 @@ function buildPathFromPattern(pattern, params) {
|
|
|
5073
6268
|
}
|
|
5074
6269
|
function pathToOutDir(baseDir, urlPath) {
|
|
5075
6270
|
const clean = urlPath === "/" ? "" : urlPath.replace(/^\/+/, "");
|
|
5076
|
-
return
|
|
6271
|
+
return import_path26.default.join(baseDir, clean);
|
|
5077
6272
|
}
|
|
5078
6273
|
|
|
5079
6274
|
// modules/build/ssg/renderer.ts
|
|
5080
6275
|
var import_fs19 = __toESM(require("fs"));
|
|
5081
|
-
var
|
|
6276
|
+
var import_path27 = __toESM(require("path"));
|
|
5082
6277
|
var import_server3 = require("react-dom/server");
|
|
5083
6278
|
init_globals();
|
|
5084
6279
|
async function renderStaticRoute(projectRoot, ssgOutDir, route, urlPath, params) {
|
|
@@ -5095,6 +6290,10 @@ async function renderStaticRoute(projectRoot, ssgOutDir, route, urlPath, params)
|
|
|
5095
6290
|
chunkHref = `${STATIC_PATH}/${chunkName}.js`;
|
|
5096
6291
|
}
|
|
5097
6292
|
}
|
|
6293
|
+
const entrypointFiles = [];
|
|
6294
|
+
if (assetManifest?.entrypoints?.client) {
|
|
6295
|
+
entrypointFiles.push(...assetManifest.entrypoints.client.map((file) => `${STATIC_PATH}/${file}`));
|
|
6296
|
+
}
|
|
5098
6297
|
const req = {
|
|
5099
6298
|
method: "GET",
|
|
5100
6299
|
headers: {},
|
|
@@ -5124,32 +6323,79 @@ async function renderStaticRoute(projectRoot, ssgOutDir, route, urlPath, params)
|
|
|
5124
6323
|
})
|
|
5125
6324
|
);
|
|
5126
6325
|
}
|
|
6326
|
+
const layoutProps = {};
|
|
6327
|
+
const layoutMetadata = [];
|
|
6328
|
+
if (route.layoutServerHooks && route.layoutServerHooks.length > 0) {
|
|
6329
|
+
for (let i = 0; i < route.layoutServerHooks.length; i++) {
|
|
6330
|
+
const layoutServerHook = route.layoutServerHooks[i];
|
|
6331
|
+
if (layoutServerHook) {
|
|
6332
|
+
try {
|
|
6333
|
+
const layoutResult = await layoutServerHook(ctx);
|
|
6334
|
+
if (layoutResult.props) {
|
|
6335
|
+
Object.assign(layoutProps, layoutResult.props);
|
|
6336
|
+
}
|
|
6337
|
+
if (layoutResult.metadata) {
|
|
6338
|
+
layoutMetadata.push(layoutResult.metadata);
|
|
6339
|
+
}
|
|
6340
|
+
} catch (error) {
|
|
6341
|
+
console.warn(
|
|
6342
|
+
`\u26A0\uFE0F [framework][ssg] Layout server hook ${i} failed for route ${route.pattern}:`,
|
|
6343
|
+
error instanceof Error ? error.message : String(error)
|
|
6344
|
+
);
|
|
6345
|
+
if (error instanceof Error && error.stack) {
|
|
6346
|
+
console.warn(` Stack: ${error.stack.split("\n").slice(0, 3).join("\n ")}`);
|
|
6347
|
+
}
|
|
6348
|
+
}
|
|
6349
|
+
}
|
|
6350
|
+
}
|
|
6351
|
+
}
|
|
5127
6352
|
let loaderResult = { props: {} };
|
|
5128
6353
|
if (route.loader) {
|
|
5129
6354
|
loaderResult = await route.loader(ctx);
|
|
5130
6355
|
}
|
|
5131
|
-
|
|
6356
|
+
const combinedProps = {
|
|
6357
|
+
...layoutProps,
|
|
6358
|
+
...loaderResult.props || {}
|
|
6359
|
+
};
|
|
6360
|
+
let combinedMetadata = null;
|
|
6361
|
+
for (const layoutMeta of layoutMetadata) {
|
|
6362
|
+
if (layoutMeta) {
|
|
6363
|
+
combinedMetadata = mergeMetadata(combinedMetadata, layoutMeta);
|
|
6364
|
+
}
|
|
6365
|
+
}
|
|
6366
|
+
if (loaderResult.metadata) {
|
|
6367
|
+
combinedMetadata = mergeMetadata(combinedMetadata, loaderResult.metadata);
|
|
6368
|
+
}
|
|
6369
|
+
const combinedLoaderResult = {
|
|
6370
|
+
...loaderResult,
|
|
6371
|
+
props: combinedProps,
|
|
6372
|
+
metadata: combinedMetadata
|
|
6373
|
+
};
|
|
6374
|
+
if (combinedLoaderResult.redirect || combinedLoaderResult.notFound) {
|
|
5132
6375
|
return;
|
|
5133
6376
|
}
|
|
5134
|
-
const initialData = buildInitialData(urlPath, params,
|
|
6377
|
+
const initialData = buildInitialData(urlPath, params, combinedLoaderResult);
|
|
5135
6378
|
const routerData = buildRouterData(req);
|
|
5136
6379
|
const appTree = buildAppTree(route, params, initialData.props);
|
|
5137
6380
|
const documentTree = createDocumentTree({
|
|
5138
6381
|
appTree,
|
|
5139
6382
|
initialData,
|
|
5140
6383
|
routerData,
|
|
5141
|
-
meta:
|
|
6384
|
+
meta: combinedLoaderResult.metadata,
|
|
5142
6385
|
titleFallback: "My Framework Dev",
|
|
5143
6386
|
descriptionFallback: "Static page generated by @lolyjs/core.",
|
|
5144
6387
|
chunkHref,
|
|
6388
|
+
entrypointFiles,
|
|
5145
6389
|
clientJsPath,
|
|
5146
|
-
clientCssPath
|
|
6390
|
+
clientCssPath,
|
|
6391
|
+
includeInlineScripts: true
|
|
6392
|
+
// SSG needs inline scripts (renderToString doesn't support bootstrapScripts)
|
|
5147
6393
|
});
|
|
5148
6394
|
const html = "<!DOCTYPE html>" + (0, import_server3.renderToString)(documentTree);
|
|
5149
6395
|
const dir = pathToOutDir(ssgOutDir, urlPath);
|
|
5150
6396
|
ensureDir(dir);
|
|
5151
|
-
const htmlFile =
|
|
5152
|
-
const dataFile =
|
|
6397
|
+
const htmlFile = import_path27.default.join(dir, "index.html");
|
|
6398
|
+
const dataFile = import_path27.default.join(dir, "data.json");
|
|
5153
6399
|
import_fs19.default.writeFileSync(htmlFile, html, "utf-8");
|
|
5154
6400
|
import_fs19.default.writeFileSync(dataFile, JSON.stringify(initialData, null, 2), "utf-8");
|
|
5155
6401
|
}
|
|
@@ -5157,7 +6403,7 @@ async function renderStaticRoute(projectRoot, ssgOutDir, route, urlPath, params)
|
|
|
5157
6403
|
// modules/build/ssg/builder.ts
|
|
5158
6404
|
init_globals();
|
|
5159
6405
|
async function buildStaticPages(projectRoot, routes) {
|
|
5160
|
-
const ssgOutDir =
|
|
6406
|
+
const ssgOutDir = import_path29.default.join(projectRoot, BUILD_FOLDER_NAME, "ssg");
|
|
5161
6407
|
ensureDir(ssgOutDir);
|
|
5162
6408
|
for (const route of routes) {
|
|
5163
6409
|
if (route.dynamic !== "force-static") continue;
|
|
@@ -5167,12 +6413,15 @@ async function buildStaticPages(projectRoot, routes) {
|
|
|
5167
6413
|
} else {
|
|
5168
6414
|
if (!route.generateStaticParams) {
|
|
5169
6415
|
console.warn(
|
|
5170
|
-
|
|
6416
|
+
`\u26A0\uFE0F [framework][ssg] Route "${route.pattern}" is marked as force-static but has no generateStaticParams function`
|
|
5171
6417
|
);
|
|
6418
|
+
console.warn(` \u{1F4A1} Add a generateStaticParams export to enable static generation for this route`);
|
|
6419
|
+
console.warn(` Skipping this route...
|
|
6420
|
+
`);
|
|
5172
6421
|
continue;
|
|
5173
6422
|
}
|
|
5174
6423
|
try {
|
|
5175
|
-
console.log(
|
|
6424
|
+
console.log(`\u{1F4E6} [framework][ssg] Generating static params for route: ${route.pattern}`);
|
|
5176
6425
|
let timeoutId = null;
|
|
5177
6426
|
const timeoutPromise = new Promise((_, reject) => {
|
|
5178
6427
|
timeoutId = setTimeout(() => {
|
|
@@ -5187,12 +6436,16 @@ async function buildStaticPages(projectRoot, routes) {
|
|
|
5187
6436
|
clearTimeout(timeoutId);
|
|
5188
6437
|
}
|
|
5189
6438
|
allParams = sp;
|
|
5190
|
-
console.log(`
|
|
6439
|
+
console.log(` \u2705 Generated ${sp.length} static params for route: ${route.pattern}`);
|
|
5191
6440
|
} catch (error) {
|
|
5192
|
-
console.error(
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
)
|
|
6441
|
+
console.error(`
|
|
6442
|
+
\u274C [framework][ssg] Error generating static params for route "${route.pattern}":`);
|
|
6443
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
6444
|
+
if (error instanceof Error && error.stack) {
|
|
6445
|
+
console.error(` Stack: ${error.stack.split("\n").slice(0, 3).join("\n ")}`);
|
|
6446
|
+
}
|
|
6447
|
+
console.error(`\u{1F4A1} Check your generateStaticParams function for this route
|
|
6448
|
+
`);
|
|
5196
6449
|
throw error;
|
|
5197
6450
|
}
|
|
5198
6451
|
}
|
|
@@ -5201,11 +6454,11 @@ async function buildStaticPages(projectRoot, routes) {
|
|
|
5201
6454
|
await renderStaticRoute(projectRoot, ssgOutDir, route, urlPath, params);
|
|
5202
6455
|
}
|
|
5203
6456
|
}
|
|
5204
|
-
console.log(
|
|
6457
|
+
console.log(`\u2705 [framework][ssg] Finished building all static pages`);
|
|
5205
6458
|
}
|
|
5206
6459
|
|
|
5207
6460
|
// modules/build/bundler/server.ts
|
|
5208
|
-
var
|
|
6461
|
+
var import_path31 = __toESM(require("path"));
|
|
5209
6462
|
var import_fs20 = __toESM(require("fs"));
|
|
5210
6463
|
var import_esbuild = __toESM(require("esbuild"));
|
|
5211
6464
|
init_globals();
|
|
@@ -5215,7 +6468,7 @@ function collectAppSources(appDir) {
|
|
|
5215
6468
|
function walk(dir) {
|
|
5216
6469
|
const items = import_fs20.default.readdirSync(dir, { withFileTypes: true });
|
|
5217
6470
|
for (const item of items) {
|
|
5218
|
-
const full =
|
|
6471
|
+
const full = import_path31.default.join(dir, item.name);
|
|
5219
6472
|
if (item.isDirectory()) {
|
|
5220
6473
|
walk(full);
|
|
5221
6474
|
continue;
|
|
@@ -5232,7 +6485,7 @@ function collectAppSources(appDir) {
|
|
|
5232
6485
|
return entries;
|
|
5233
6486
|
}
|
|
5234
6487
|
async function buildServerApp(projectRoot, appDir) {
|
|
5235
|
-
const outDir =
|
|
6488
|
+
const outDir = import_path31.default.join(projectRoot, BUILD_FOLDER_NAME, "server");
|
|
5236
6489
|
const entryPoints = collectAppSources(appDir);
|
|
5237
6490
|
ensureDir(outDir);
|
|
5238
6491
|
if (entryPoints.length === 0) {
|
|
@@ -5250,12 +6503,12 @@ async function buildServerApp(projectRoot, appDir) {
|
|
|
5250
6503
|
bundle: true,
|
|
5251
6504
|
splitting: false,
|
|
5252
6505
|
logLevel: "info",
|
|
5253
|
-
tsconfig:
|
|
6506
|
+
tsconfig: import_path31.default.join(projectRoot, "tsconfig.json"),
|
|
5254
6507
|
packages: "external"
|
|
5255
6508
|
});
|
|
5256
6509
|
for (const fileName of SERVER_FILES) {
|
|
5257
|
-
const initTS =
|
|
5258
|
-
const initJS =
|
|
6510
|
+
const initTS = import_path31.default.join(projectRoot, `${fileName}.ts`);
|
|
6511
|
+
const initJS = import_path31.default.join(outDir, `${fileName}.js`);
|
|
5259
6512
|
if (import_fs20.default.existsSync(initTS)) {
|
|
5260
6513
|
await import_esbuild.default.build({
|
|
5261
6514
|
entryPoints: [initTS],
|
|
@@ -5267,7 +6520,7 @@ async function buildServerApp(projectRoot, appDir) {
|
|
|
5267
6520
|
sourcemap: true,
|
|
5268
6521
|
bundle: false,
|
|
5269
6522
|
logLevel: "info",
|
|
5270
|
-
tsconfig:
|
|
6523
|
+
tsconfig: import_path31.default.join(projectRoot, "tsconfig.json")
|
|
5271
6524
|
});
|
|
5272
6525
|
}
|
|
5273
6526
|
}
|
|
@@ -5410,22 +6663,158 @@ function matchRouteClient(pathWithSearch, routes) {
|
|
|
5410
6663
|
}
|
|
5411
6664
|
|
|
5412
6665
|
// modules/runtime/client/metadata.ts
|
|
6666
|
+
function getOrCreateMeta(selector, attributes) {
|
|
6667
|
+
let meta = document.querySelector(selector);
|
|
6668
|
+
if (!meta) {
|
|
6669
|
+
meta = document.createElement("meta");
|
|
6670
|
+
if (attributes.name) meta.name = attributes.name;
|
|
6671
|
+
if (attributes.property) meta.setAttribute("property", attributes.property);
|
|
6672
|
+
if (attributes.httpEquiv) meta.httpEquiv = attributes.httpEquiv;
|
|
6673
|
+
document.head.appendChild(meta);
|
|
6674
|
+
}
|
|
6675
|
+
return meta;
|
|
6676
|
+
}
|
|
6677
|
+
function getOrCreateLink(rel, href) {
|
|
6678
|
+
const selector = `link[rel="${rel}"]`;
|
|
6679
|
+
let link = document.querySelector(selector);
|
|
6680
|
+
if (!link) {
|
|
6681
|
+
link = document.createElement("link");
|
|
6682
|
+
link.rel = rel;
|
|
6683
|
+
link.href = href;
|
|
6684
|
+
document.head.appendChild(link);
|
|
6685
|
+
} else {
|
|
6686
|
+
link.href = href;
|
|
6687
|
+
}
|
|
6688
|
+
return link;
|
|
6689
|
+
}
|
|
5413
6690
|
function applyMetadata(md) {
|
|
5414
6691
|
if (!md) return;
|
|
5415
6692
|
if (md.title) {
|
|
5416
6693
|
document.title = md.title;
|
|
5417
6694
|
}
|
|
5418
6695
|
if (md.description) {
|
|
5419
|
-
|
|
5420
|
-
'meta[name="description"]'
|
|
5421
|
-
);
|
|
5422
|
-
if (!meta) {
|
|
5423
|
-
meta = document.createElement("meta");
|
|
5424
|
-
meta.name = "description";
|
|
5425
|
-
document.head.appendChild(meta);
|
|
5426
|
-
}
|
|
6696
|
+
const meta = getOrCreateMeta('meta[name="description"]', { name: "description" });
|
|
5427
6697
|
meta.content = md.description;
|
|
5428
6698
|
}
|
|
6699
|
+
if (md.robots) {
|
|
6700
|
+
const meta = getOrCreateMeta('meta[name="robots"]', { name: "robots" });
|
|
6701
|
+
meta.content = md.robots;
|
|
6702
|
+
}
|
|
6703
|
+
if (md.themeColor) {
|
|
6704
|
+
const meta = getOrCreateMeta('meta[name="theme-color"]', { name: "theme-color" });
|
|
6705
|
+
meta.content = md.themeColor;
|
|
6706
|
+
}
|
|
6707
|
+
if (md.viewport) {
|
|
6708
|
+
const meta = getOrCreateMeta('meta[name="viewport"]', { name: "viewport" });
|
|
6709
|
+
meta.content = md.viewport;
|
|
6710
|
+
}
|
|
6711
|
+
if (md.canonical) {
|
|
6712
|
+
getOrCreateLink("canonical", md.canonical);
|
|
6713
|
+
}
|
|
6714
|
+
if (md.openGraph) {
|
|
6715
|
+
const og = md.openGraph;
|
|
6716
|
+
if (og.title) {
|
|
6717
|
+
const meta = getOrCreateMeta('meta[property="og:title"]', { property: "og:title" });
|
|
6718
|
+
meta.content = og.title;
|
|
6719
|
+
}
|
|
6720
|
+
if (og.description) {
|
|
6721
|
+
const meta = getOrCreateMeta('meta[property="og:description"]', { property: "og:description" });
|
|
6722
|
+
meta.content = og.description;
|
|
6723
|
+
}
|
|
6724
|
+
if (og.type) {
|
|
6725
|
+
const meta = getOrCreateMeta('meta[property="og:type"]', { property: "og:type" });
|
|
6726
|
+
meta.content = og.type;
|
|
6727
|
+
}
|
|
6728
|
+
if (og.url) {
|
|
6729
|
+
const meta = getOrCreateMeta('meta[property="og:url"]', { property: "og:url" });
|
|
6730
|
+
meta.content = og.url;
|
|
6731
|
+
}
|
|
6732
|
+
if (og.image) {
|
|
6733
|
+
if (typeof og.image === "string") {
|
|
6734
|
+
const meta = getOrCreateMeta('meta[property="og:image"]', { property: "og:image" });
|
|
6735
|
+
meta.content = og.image;
|
|
6736
|
+
} else {
|
|
6737
|
+
const meta = getOrCreateMeta('meta[property="og:image"]', { property: "og:image" });
|
|
6738
|
+
meta.content = og.image.url;
|
|
6739
|
+
if (og.image.width) {
|
|
6740
|
+
const metaWidth = getOrCreateMeta('meta[property="og:image:width"]', { property: "og:image:width" });
|
|
6741
|
+
metaWidth.content = String(og.image.width);
|
|
6742
|
+
}
|
|
6743
|
+
if (og.image.height) {
|
|
6744
|
+
const metaHeight = getOrCreateMeta('meta[property="og:image:height"]', { property: "og:image:height" });
|
|
6745
|
+
metaHeight.content = String(og.image.height);
|
|
6746
|
+
}
|
|
6747
|
+
if (og.image.alt) {
|
|
6748
|
+
const metaAlt = getOrCreateMeta('meta[property="og:image:alt"]', { property: "og:image:alt" });
|
|
6749
|
+
metaAlt.content = og.image.alt;
|
|
6750
|
+
}
|
|
6751
|
+
}
|
|
6752
|
+
}
|
|
6753
|
+
if (og.siteName) {
|
|
6754
|
+
const meta = getOrCreateMeta('meta[property="og:site_name"]', { property: "og:site_name" });
|
|
6755
|
+
meta.content = og.siteName;
|
|
6756
|
+
}
|
|
6757
|
+
if (og.locale) {
|
|
6758
|
+
const meta = getOrCreateMeta('meta[property="og:locale"]', { property: "og:locale" });
|
|
6759
|
+
meta.content = og.locale;
|
|
6760
|
+
}
|
|
6761
|
+
}
|
|
6762
|
+
if (md.twitter) {
|
|
6763
|
+
const twitter = md.twitter;
|
|
6764
|
+
if (twitter.card) {
|
|
6765
|
+
const meta = getOrCreateMeta('meta[name="twitter:card"]', { name: "twitter:card" });
|
|
6766
|
+
meta.content = twitter.card;
|
|
6767
|
+
}
|
|
6768
|
+
if (twitter.title) {
|
|
6769
|
+
const meta = getOrCreateMeta('meta[name="twitter:title"]', { name: "twitter:title" });
|
|
6770
|
+
meta.content = twitter.title;
|
|
6771
|
+
}
|
|
6772
|
+
if (twitter.description) {
|
|
6773
|
+
const meta = getOrCreateMeta('meta[name="twitter:description"]', { name: "twitter:description" });
|
|
6774
|
+
meta.content = twitter.description;
|
|
6775
|
+
}
|
|
6776
|
+
if (twitter.image) {
|
|
6777
|
+
const meta = getOrCreateMeta('meta[name="twitter:image"]', { name: "twitter:image" });
|
|
6778
|
+
meta.content = twitter.image;
|
|
6779
|
+
}
|
|
6780
|
+
if (twitter.imageAlt) {
|
|
6781
|
+
const meta = getOrCreateMeta('meta[name="twitter:image:alt"]', { name: "twitter:image:alt" });
|
|
6782
|
+
meta.content = twitter.imageAlt;
|
|
6783
|
+
}
|
|
6784
|
+
if (twitter.site) {
|
|
6785
|
+
const meta = getOrCreateMeta('meta[name="twitter:site"]', { name: "twitter:site" });
|
|
6786
|
+
meta.content = twitter.site;
|
|
6787
|
+
}
|
|
6788
|
+
if (twitter.creator) {
|
|
6789
|
+
const meta = getOrCreateMeta('meta[name="twitter:creator"]', { name: "twitter:creator" });
|
|
6790
|
+
meta.content = twitter.creator;
|
|
6791
|
+
}
|
|
6792
|
+
}
|
|
6793
|
+
if (md.metaTags && Array.isArray(md.metaTags)) {
|
|
6794
|
+
md.metaTags.forEach((tag) => {
|
|
6795
|
+
let selector = "";
|
|
6796
|
+
if (tag.name) {
|
|
6797
|
+
selector = `meta[name="${tag.name}"]`;
|
|
6798
|
+
} else if (tag.property) {
|
|
6799
|
+
selector = `meta[property="${tag.property}"]`;
|
|
6800
|
+
} else if (tag.httpEquiv) {
|
|
6801
|
+
selector = `meta[http-equiv="${tag.httpEquiv}"]`;
|
|
6802
|
+
}
|
|
6803
|
+
if (selector) {
|
|
6804
|
+
const meta = getOrCreateMeta(selector, {
|
|
6805
|
+
name: tag.name,
|
|
6806
|
+
property: tag.property,
|
|
6807
|
+
httpEquiv: tag.httpEquiv
|
|
6808
|
+
});
|
|
6809
|
+
meta.content = tag.content;
|
|
6810
|
+
}
|
|
6811
|
+
});
|
|
6812
|
+
}
|
|
6813
|
+
if (md.links && Array.isArray(md.links)) {
|
|
6814
|
+
md.links.forEach((link) => {
|
|
6815
|
+
getOrCreateLink(link.rel, link.href);
|
|
6816
|
+
});
|
|
6817
|
+
}
|
|
5429
6818
|
}
|
|
5430
6819
|
|
|
5431
6820
|
// modules/runtime/client/AppShell.tsx
|
|
@@ -5642,10 +7031,15 @@ async function handleErrorRoute(nextUrl, json, errorRoute, setState) {
|
|
|
5642
7031
|
});
|
|
5643
7032
|
return true;
|
|
5644
7033
|
} catch (loadError) {
|
|
5645
|
-
console.error(
|
|
5646
|
-
|
|
5647
|
-
|
|
5648
|
-
|
|
7034
|
+
console.error("\n\u274C [client] Error loading error route components:");
|
|
7035
|
+
console.error(loadError);
|
|
7036
|
+
if (loadError instanceof Error) {
|
|
7037
|
+
console.error(` Message: ${loadError.message}`);
|
|
7038
|
+
if (loadError.stack) {
|
|
7039
|
+
console.error(` Stack: ${loadError.stack.split("\n").slice(0, 3).join("\n ")}`);
|
|
7040
|
+
}
|
|
7041
|
+
}
|
|
7042
|
+
console.error("\u{1F4A1} Falling back to full page reload\n");
|
|
5649
7043
|
window.location.href = nextUrl;
|
|
5650
7044
|
return false;
|
|
5651
7045
|
}
|
|
@@ -5750,7 +7144,11 @@ async function handleNormalRoute(nextUrl, json, routes, setState) {
|
|
|
5750
7144
|
searchParams: Object.fromEntries(url.searchParams.entries())
|
|
5751
7145
|
};
|
|
5752
7146
|
setRouterData(routerData);
|
|
5753
|
-
const
|
|
7147
|
+
const prefetched = prefetchedRoutes.get(matched.route);
|
|
7148
|
+
const components = prefetched ? await prefetched : await matched.route.load();
|
|
7149
|
+
if (!prefetched) {
|
|
7150
|
+
prefetchedRoutes.set(matched.route, Promise.resolve(components));
|
|
7151
|
+
}
|
|
5754
7152
|
window.scrollTo({
|
|
5755
7153
|
top: 0,
|
|
5756
7154
|
behavior: "smooth"
|
|
@@ -5789,7 +7187,7 @@ async function navigate(nextUrl, handlers, options) {
|
|
|
5789
7187
|
}
|
|
5790
7188
|
}
|
|
5791
7189
|
if (!ok) {
|
|
5792
|
-
if (json
|
|
7190
|
+
if (json?.redirect) {
|
|
5793
7191
|
window.location.href = json.redirect.destination;
|
|
5794
7192
|
return;
|
|
5795
7193
|
}
|
|
@@ -5810,6 +7208,47 @@ async function navigate(nextUrl, handlers, options) {
|
|
|
5810
7208
|
window.location.href = nextUrl;
|
|
5811
7209
|
}
|
|
5812
7210
|
}
|
|
7211
|
+
var prefetchedRoutes = /* @__PURE__ */ new WeakMap();
|
|
7212
|
+
function prefetchRoute(url, routes, notFoundRoute) {
|
|
7213
|
+
const [pathname] = url.split("?");
|
|
7214
|
+
const matched = matchRouteClient(pathname, routes);
|
|
7215
|
+
if (!matched) {
|
|
7216
|
+
if (notFoundRoute) {
|
|
7217
|
+
const existing2 = prefetchedRoutes.get(notFoundRoute);
|
|
7218
|
+
if (!existing2) {
|
|
7219
|
+
const promise = notFoundRoute.load();
|
|
7220
|
+
prefetchedRoutes.set(notFoundRoute, promise);
|
|
7221
|
+
}
|
|
7222
|
+
}
|
|
7223
|
+
return;
|
|
7224
|
+
}
|
|
7225
|
+
const existing = prefetchedRoutes.get(matched.route);
|
|
7226
|
+
if (!existing) {
|
|
7227
|
+
const promise = matched.route.load();
|
|
7228
|
+
prefetchedRoutes.set(matched.route, promise);
|
|
7229
|
+
}
|
|
7230
|
+
}
|
|
7231
|
+
function createHoverHandler(routes, notFoundRoute) {
|
|
7232
|
+
return function handleHover(ev) {
|
|
7233
|
+
try {
|
|
7234
|
+
const target = ev.target;
|
|
7235
|
+
if (!target) return;
|
|
7236
|
+
const anchor = target.closest("a[href]");
|
|
7237
|
+
if (!anchor) return;
|
|
7238
|
+
const href = anchor.getAttribute("href");
|
|
7239
|
+
if (!href) return;
|
|
7240
|
+
if (href.startsWith("#")) return;
|
|
7241
|
+
const url = new URL(href, window.location.href);
|
|
7242
|
+
if (url.origin !== window.location.origin) return;
|
|
7243
|
+
if (anchor.target && anchor.target !== "_self") return;
|
|
7244
|
+
const nextUrl = url.pathname + url.search;
|
|
7245
|
+
const currentUrl = window.location.pathname + window.location.search;
|
|
7246
|
+
if (nextUrl === currentUrl) return;
|
|
7247
|
+
prefetchRoute(nextUrl, routes, notFoundRoute);
|
|
7248
|
+
} catch (error) {
|
|
7249
|
+
}
|
|
7250
|
+
};
|
|
7251
|
+
}
|
|
5813
7252
|
function createClickHandler(navigate2) {
|
|
5814
7253
|
return function handleClick(ev) {
|
|
5815
7254
|
try {
|
|
@@ -5920,17 +7359,20 @@ function AppShell({
|
|
|
5920
7359
|
}
|
|
5921
7360
|
const handleClick = createClickHandler(handleNavigateInternal);
|
|
5922
7361
|
const handlePopState = createPopStateHandler(handleNavigateInternal);
|
|
7362
|
+
const handleHover = createHoverHandler(routes, notFoundRoute);
|
|
5923
7363
|
window.addEventListener("click", handleClick, false);
|
|
5924
7364
|
window.addEventListener("popstate", handlePopState, false);
|
|
7365
|
+
window.addEventListener("mouseover", handleHover, false);
|
|
5925
7366
|
return () => {
|
|
5926
7367
|
isMounted = false;
|
|
5927
7368
|
window.removeEventListener("click", handleClick, false);
|
|
5928
7369
|
window.removeEventListener("popstate", handlePopState, false);
|
|
7370
|
+
window.removeEventListener("mouseover", handleHover, false);
|
|
5929
7371
|
};
|
|
5930
|
-
}, []);
|
|
7372
|
+
}, [routes, notFoundRoute]);
|
|
5931
7373
|
(0, import_react3.useEffect)(() => {
|
|
5932
7374
|
const handleDataRefresh = () => {
|
|
5933
|
-
const freshData = window
|
|
7375
|
+
const freshData = window[WINDOW_DATA_KEY2];
|
|
5934
7376
|
if (!freshData) return;
|
|
5935
7377
|
const currentPathname = window.location.pathname;
|
|
5936
7378
|
const freshPathname = freshData.pathname;
|
|
@@ -5957,6 +7399,91 @@ function AppShell({
|
|
|
5957
7399
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(RouterContext.Provider, { value: { navigate: handleNavigate }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(RouterView, { state }, routeKey) });
|
|
5958
7400
|
}
|
|
5959
7401
|
|
|
7402
|
+
// modules/runtime/client/hot-reload.ts
|
|
7403
|
+
function setupHotReload2() {
|
|
7404
|
+
const nodeEnv = process.env.NODE_ENV || "production";
|
|
7405
|
+
const isDev = nodeEnv === "development";
|
|
7406
|
+
console.log(`[hot-reload] NODE_ENV: ${nodeEnv}, isDev: ${isDev}`);
|
|
7407
|
+
if (!isDev) {
|
|
7408
|
+
console.log("[hot-reload] Skipping hot reload setup (not in development mode)");
|
|
7409
|
+
return;
|
|
7410
|
+
}
|
|
7411
|
+
console.log("[hot-reload] Setting up hot reload client...");
|
|
7412
|
+
let eventSource = null;
|
|
7413
|
+
let reloadTimeout = null;
|
|
7414
|
+
let reconnectTimeout = null;
|
|
7415
|
+
let reconnectAttempts = 0;
|
|
7416
|
+
const MAX_RECONNECT_ATTEMPTS = 10;
|
|
7417
|
+
const RECONNECT_DELAY = 1e3;
|
|
7418
|
+
const RELOAD_DELAY = 100;
|
|
7419
|
+
function connect() {
|
|
7420
|
+
try {
|
|
7421
|
+
if (eventSource) {
|
|
7422
|
+
console.log("[hot-reload] Closing existing EventSource connection");
|
|
7423
|
+
eventSource.close();
|
|
7424
|
+
}
|
|
7425
|
+
const endpoint = "/__fw/hot";
|
|
7426
|
+
eventSource = new EventSource(endpoint);
|
|
7427
|
+
eventSource.addEventListener("ping", (event) => {
|
|
7428
|
+
if ("data" in event) {
|
|
7429
|
+
console.log("[hot-reload] \u2705 Connected to hot reload server");
|
|
7430
|
+
}
|
|
7431
|
+
reconnectAttempts = 0;
|
|
7432
|
+
});
|
|
7433
|
+
eventSource.addEventListener("message", (event) => {
|
|
7434
|
+
const data = event.data;
|
|
7435
|
+
if (data && typeof data === "string" && data.startsWith("reload:")) {
|
|
7436
|
+
const filePath = data.slice(7);
|
|
7437
|
+
console.log(`[hot-reload] \u{1F4DD} File changed: ${filePath}, reloading...`);
|
|
7438
|
+
if (reloadTimeout) {
|
|
7439
|
+
clearTimeout(reloadTimeout);
|
|
7440
|
+
}
|
|
7441
|
+
reloadTimeout = setTimeout(() => {
|
|
7442
|
+
try {
|
|
7443
|
+
window.location.reload();
|
|
7444
|
+
} catch (error) {
|
|
7445
|
+
console.error("[hot-reload] \u274C Error reloading page:", error);
|
|
7446
|
+
setTimeout(() => window.location.reload(), 100);
|
|
7447
|
+
}
|
|
7448
|
+
}, RELOAD_DELAY);
|
|
7449
|
+
}
|
|
7450
|
+
});
|
|
7451
|
+
eventSource.onopen = () => {
|
|
7452
|
+
reconnectAttempts = 0;
|
|
7453
|
+
};
|
|
7454
|
+
eventSource.onerror = (error) => {
|
|
7455
|
+
const states = ["CONNECTING", "OPEN", "CLOSED"];
|
|
7456
|
+
const state = states[eventSource?.readyState ?? 0] || "UNKNOWN";
|
|
7457
|
+
if (eventSource?.readyState === EventSource.CONNECTING) {
|
|
7458
|
+
console.log("[hot-reload] \u23F3 Still connecting...");
|
|
7459
|
+
return;
|
|
7460
|
+
} else if (eventSource?.readyState === EventSource.OPEN) {
|
|
7461
|
+
console.warn("[hot-reload] \u26A0\uFE0F Connection error (but connection is open):", error);
|
|
7462
|
+
} else {
|
|
7463
|
+
console.warn(`[hot-reload] \u274C Connection closed (readyState: ${state})`);
|
|
7464
|
+
if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
|
|
7465
|
+
reconnectAttempts++;
|
|
7466
|
+
const delay = RECONNECT_DELAY * reconnectAttempts;
|
|
7467
|
+
if (reconnectTimeout) {
|
|
7468
|
+
clearTimeout(reconnectTimeout);
|
|
7469
|
+
}
|
|
7470
|
+
reconnectTimeout = setTimeout(() => {
|
|
7471
|
+
console.log(`[hot-reload] \u{1F504} Reconnecting... (attempt ${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})`);
|
|
7472
|
+
connect();
|
|
7473
|
+
}, delay);
|
|
7474
|
+
} else {
|
|
7475
|
+
console.error("[hot-reload] \u274C Max reconnect attempts reached. Please refresh the page manually.");
|
|
7476
|
+
}
|
|
7477
|
+
}
|
|
7478
|
+
};
|
|
7479
|
+
} catch (error) {
|
|
7480
|
+
console.error("[hot-reload] \u274C Failed to create EventSource:", error);
|
|
7481
|
+
console.error("[hot-reload] EventSource may not be supported in this browser.");
|
|
7482
|
+
}
|
|
7483
|
+
}
|
|
7484
|
+
connect();
|
|
7485
|
+
}
|
|
7486
|
+
|
|
5960
7487
|
// modules/runtime/client/bootstrap.tsx
|
|
5961
7488
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
5962
7489
|
async function loadInitialRoute(initialUrl, initialData, routes, notFoundRoute, errorRoute) {
|
|
@@ -5998,101 +7525,91 @@ async function loadInitialRoute(initialUrl, initialData, routes, notFoundRoute,
|
|
|
5998
7525
|
props: initialData?.props ?? {}
|
|
5999
7526
|
};
|
|
6000
7527
|
}
|
|
6001
|
-
function
|
|
6002
|
-
|
|
6003
|
-
|
|
6004
|
-
|
|
6005
|
-
|
|
7528
|
+
function initializeRouterData(initialUrl, initialData) {
|
|
7529
|
+
let routerData = getRouterData();
|
|
7530
|
+
if (!routerData) {
|
|
7531
|
+
const url = new URL(initialUrl, window.location.origin);
|
|
7532
|
+
routerData = {
|
|
7533
|
+
pathname: url.pathname,
|
|
7534
|
+
params: initialData?.params || {},
|
|
7535
|
+
searchParams: Object.fromEntries(url.searchParams.entries())
|
|
7536
|
+
};
|
|
7537
|
+
setRouterData(routerData);
|
|
6006
7538
|
}
|
|
7539
|
+
}
|
|
7540
|
+
async function hydrateInitialRoute(container, initialUrl, initialData, routes, notFoundRoute, errorRoute) {
|
|
6007
7541
|
try {
|
|
6008
|
-
|
|
6009
|
-
|
|
6010
|
-
|
|
6011
|
-
|
|
6012
|
-
|
|
6013
|
-
|
|
6014
|
-
|
|
6015
|
-
|
|
6016
|
-
|
|
6017
|
-
|
|
6018
|
-
|
|
6019
|
-
|
|
6020
|
-
console.log("[hot-reload] Reloading page...");
|
|
6021
|
-
window.location.reload();
|
|
6022
|
-
}, 500);
|
|
6023
|
-
}
|
|
6024
|
-
});
|
|
6025
|
-
eventSource.addEventListener("ping", () => {
|
|
6026
|
-
console.log("[hot-reload] \u2713 Connected to hot reload server");
|
|
6027
|
-
});
|
|
6028
|
-
eventSource.onopen = () => {
|
|
6029
|
-
console.log("[hot-reload] \u2713 SSE connection opened");
|
|
6030
|
-
};
|
|
6031
|
-
eventSource.onerror = (error) => {
|
|
6032
|
-
const states = ["CONNECTING", "OPEN", "CLOSED"];
|
|
6033
|
-
const state = states[eventSource.readyState] || "UNKNOWN";
|
|
6034
|
-
if (eventSource.readyState === EventSource.CONNECTING) {
|
|
6035
|
-
console.log("[hot-reload] Connecting...");
|
|
6036
|
-
} else if (eventSource.readyState === EventSource.OPEN) {
|
|
6037
|
-
console.warn("[hot-reload] Connection error (but connection is open):", error);
|
|
6038
|
-
} else {
|
|
6039
|
-
console.log("[hot-reload] Connection closed (readyState:", state, ")");
|
|
7542
|
+
const initialState = await loadInitialRoute(
|
|
7543
|
+
initialUrl,
|
|
7544
|
+
initialData,
|
|
7545
|
+
routes,
|
|
7546
|
+
notFoundRoute,
|
|
7547
|
+
errorRoute
|
|
7548
|
+
);
|
|
7549
|
+
if (initialData?.metadata) {
|
|
7550
|
+
try {
|
|
7551
|
+
applyMetadata(initialData.metadata);
|
|
7552
|
+
} catch (metadataError) {
|
|
7553
|
+
console.warn("[client] Error applying metadata:", metadataError);
|
|
6040
7554
|
}
|
|
6041
|
-
}
|
|
7555
|
+
}
|
|
7556
|
+
(0, import_client5.hydrateRoot)(
|
|
7557
|
+
container,
|
|
7558
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
7559
|
+
AppShell,
|
|
7560
|
+
{
|
|
7561
|
+
initialState,
|
|
7562
|
+
routes,
|
|
7563
|
+
notFoundRoute,
|
|
7564
|
+
errorRoute
|
|
7565
|
+
}
|
|
7566
|
+
)
|
|
7567
|
+
);
|
|
6042
7568
|
} catch (error) {
|
|
6043
|
-
console.
|
|
7569
|
+
console.error(
|
|
7570
|
+
"[client] Error loading initial route components for",
|
|
7571
|
+
initialUrl,
|
|
7572
|
+
error
|
|
7573
|
+
);
|
|
7574
|
+
throw error;
|
|
6044
7575
|
}
|
|
6045
7576
|
}
|
|
6046
7577
|
function bootstrapClient(routes, notFoundRoute, errorRoute = null) {
|
|
6047
|
-
console.log("[client] Bootstrap starting, setting up hot reload...");
|
|
6048
7578
|
setupHotReload2();
|
|
6049
|
-
(async
|
|
6050
|
-
const container = document.getElementById(APP_CONTAINER_ID2);
|
|
6051
|
-
const initialData = getWindowData();
|
|
6052
|
-
if (!container) {
|
|
6053
|
-
console.error(`Container #${APP_CONTAINER_ID2} not found for hydration`);
|
|
6054
|
-
return;
|
|
6055
|
-
}
|
|
6056
|
-
const initialUrl = window.location.pathname + window.location.search;
|
|
6057
|
-
let routerData = getRouterData();
|
|
6058
|
-
if (!routerData) {
|
|
6059
|
-
const url = new URL(initialUrl, window.location.origin);
|
|
6060
|
-
routerData = {
|
|
6061
|
-
pathname: url.pathname,
|
|
6062
|
-
params: initialData?.params || {},
|
|
6063
|
-
searchParams: Object.fromEntries(url.searchParams.entries())
|
|
6064
|
-
};
|
|
6065
|
-
setRouterData(routerData);
|
|
6066
|
-
}
|
|
7579
|
+
(async () => {
|
|
6067
7580
|
try {
|
|
6068
|
-
const
|
|
7581
|
+
const container = document.getElementById(APP_CONTAINER_ID2);
|
|
7582
|
+
if (!container) {
|
|
7583
|
+
console.error(`
|
|
7584
|
+
\u274C [client] Hydration failed: Container #${APP_CONTAINER_ID2} not found`);
|
|
7585
|
+
console.error("\u{1F4A1} This usually means:");
|
|
7586
|
+
console.error(" \u2022 The HTML structure doesn't match what React expects");
|
|
7587
|
+
console.error(" \u2022 The container was removed before hydration");
|
|
7588
|
+
console.error(" \u2022 There's a mismatch between SSR and client HTML\n");
|
|
7589
|
+
return;
|
|
7590
|
+
}
|
|
7591
|
+
const initialData = getWindowData();
|
|
7592
|
+
const initialUrl = window.location.pathname + window.location.search;
|
|
7593
|
+
initializeRouterData(initialUrl, initialData);
|
|
7594
|
+
await hydrateInitialRoute(
|
|
7595
|
+
container,
|
|
6069
7596
|
initialUrl,
|
|
6070
7597
|
initialData,
|
|
6071
7598
|
routes,
|
|
6072
7599
|
notFoundRoute,
|
|
6073
7600
|
errorRoute
|
|
6074
7601
|
);
|
|
6075
|
-
if (initialData?.metadata) {
|
|
6076
|
-
applyMetadata(initialData.metadata);
|
|
6077
|
-
}
|
|
6078
|
-
(0, import_client5.hydrateRoot)(
|
|
6079
|
-
container,
|
|
6080
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
6081
|
-
AppShell,
|
|
6082
|
-
{
|
|
6083
|
-
initialState,
|
|
6084
|
-
routes,
|
|
6085
|
-
notFoundRoute,
|
|
6086
|
-
errorRoute
|
|
6087
|
-
}
|
|
6088
|
-
)
|
|
6089
|
-
);
|
|
6090
7602
|
} catch (error) {
|
|
6091
|
-
console.error(
|
|
6092
|
-
|
|
6093
|
-
|
|
6094
|
-
error
|
|
6095
|
-
|
|
7603
|
+
console.error("\n\u274C [client] Fatal error during bootstrap:");
|
|
7604
|
+
console.error(error);
|
|
7605
|
+
if (error instanceof Error) {
|
|
7606
|
+
console.error("\nError details:");
|
|
7607
|
+
console.error(` Message: ${error.message}`);
|
|
7608
|
+
if (error.stack) {
|
|
7609
|
+
console.error(` Stack: ${error.stack}`);
|
|
7610
|
+
}
|
|
7611
|
+
}
|
|
7612
|
+
console.error("\n\u{1F4A1} Attempting page reload to recover...\n");
|
|
6096
7613
|
window.location.reload();
|
|
6097
7614
|
}
|
|
6098
7615
|
})();
|
|
@@ -6120,11 +7637,11 @@ var ValidationError = class extends Error {
|
|
|
6120
7637
|
format() {
|
|
6121
7638
|
const formatted = {};
|
|
6122
7639
|
for (const error of this.errors) {
|
|
6123
|
-
const
|
|
6124
|
-
if (!formatted[
|
|
6125
|
-
formatted[
|
|
7640
|
+
const path28 = error.path.join(".");
|
|
7641
|
+
if (!formatted[path28]) {
|
|
7642
|
+
formatted[path28] = [];
|
|
6126
7643
|
}
|
|
6127
|
-
formatted[
|
|
7644
|
+
formatted[path28].push(error.message);
|
|
6128
7645
|
}
|
|
6129
7646
|
return formatted;
|
|
6130
7647
|
}
|