@lolyjs/core 0.2.0-alpha.14 → 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 -755
- 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 +4389 -2972
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +4385 -2968
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +2225 -560
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +9 -75
- package/dist/index.d.ts +9 -75
- package/dist/index.js +2226 -561
- 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"));
|
|
@@ -246,7 +246,7 @@ function loadLayoutsForDir(pageDir, appDir) {
|
|
|
246
246
|
};
|
|
247
247
|
}
|
|
248
248
|
|
|
249
|
-
// modules/router/
|
|
249
|
+
// modules/router/server-hook.ts
|
|
250
250
|
var import_fs3 = __toESM(require("fs"));
|
|
251
251
|
var import_path3 = __toESM(require("path"));
|
|
252
252
|
var NAMING = {
|
|
@@ -258,14 +258,16 @@ var NAMING = {
|
|
|
258
258
|
// Files
|
|
259
259
|
SERVER_HOOK: "server.hook"
|
|
260
260
|
};
|
|
261
|
-
function
|
|
262
|
-
const
|
|
263
|
-
const
|
|
264
|
-
const
|
|
261
|
+
function loadServerHookForDir(currentDir) {
|
|
262
|
+
const pageServerHookTs = import_path3.default.join(currentDir, `page.server.hook.ts`);
|
|
263
|
+
const pageServerHookJs = import_path3.default.join(currentDir, `page.server.hook.js`);
|
|
264
|
+
const serverHookTs = import_path3.default.join(currentDir, `${NAMING.SERVER_HOOK}.ts`);
|
|
265
|
+
const serverHookJs = import_path3.default.join(currentDir, `${NAMING.SERVER_HOOK}.js`);
|
|
266
|
+
const file = import_fs3.default.existsSync(pageServerHookTs) ? pageServerHookTs : import_fs3.default.existsSync(pageServerHookJs) ? pageServerHookJs : import_fs3.default.existsSync(serverHookTs) ? serverHookTs : import_fs3.default.existsSync(serverHookJs) ? serverHookJs : null;
|
|
265
267
|
if (!file) {
|
|
266
268
|
return {
|
|
267
269
|
middlewares: [],
|
|
268
|
-
|
|
270
|
+
serverHook: null,
|
|
269
271
|
dynamic: "auto",
|
|
270
272
|
generateStaticParams: null
|
|
271
273
|
};
|
|
@@ -281,31 +283,119 @@ function loadLoaderForDir(currentDir) {
|
|
|
281
283
|
mod = require(file);
|
|
282
284
|
} catch (error) {
|
|
283
285
|
console.error(
|
|
284
|
-
`[framework][
|
|
286
|
+
`[framework][server-hook] Error loading server hook from ${file}:`,
|
|
285
287
|
error
|
|
286
288
|
);
|
|
287
289
|
return {
|
|
288
290
|
middlewares: [],
|
|
289
|
-
|
|
291
|
+
serverHook: null,
|
|
290
292
|
dynamic: "auto",
|
|
291
293
|
generateStaticParams: null
|
|
292
294
|
};
|
|
293
295
|
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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
|
+
}
|
|
316
|
+
const serverHook = typeof mod?.[NAMING.GET_SERVER_DATA_FN] === "function" ? mod[NAMING.GET_SERVER_DATA_FN] : null;
|
|
298
317
|
const dynamic = mod?.[NAMING.RENDER_TYPE_CONST] === "force-static" || mod?.[NAMING.RENDER_TYPE_CONST] === "force-dynamic" ? mod.dynamic : "auto";
|
|
299
318
|
const generateStaticParams = typeof mod?.[NAMING.GENERATE_SSG_PARAMS] === "function" ? mod[NAMING.GENERATE_SSG_PARAMS] : null;
|
|
300
319
|
return {
|
|
301
320
|
middlewares,
|
|
302
|
-
|
|
321
|
+
serverHook,
|
|
303
322
|
dynamic,
|
|
304
323
|
generateStaticParams
|
|
305
324
|
};
|
|
306
325
|
}
|
|
326
|
+
function loadLayoutServerHook(layoutFile) {
|
|
327
|
+
const layoutDir = import_path3.default.dirname(layoutFile);
|
|
328
|
+
const layoutBasename = import_path3.default.basename(layoutFile, import_path3.default.extname(layoutFile));
|
|
329
|
+
const serverHookTs = import_path3.default.join(layoutDir, `${layoutBasename}.server.hook.ts`);
|
|
330
|
+
const serverHookJs = import_path3.default.join(layoutDir, `${layoutBasename}.server.hook.js`);
|
|
331
|
+
const file = import_fs3.default.existsSync(serverHookTs) ? serverHookTs : import_fs3.default.existsSync(serverHookJs) ? serverHookJs : null;
|
|
332
|
+
if (!file) {
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
335
|
+
if (file.endsWith(".ts") || file.endsWith(".tsx")) {
|
|
336
|
+
try {
|
|
337
|
+
require("tsx/cjs");
|
|
338
|
+
} catch (e) {
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
try {
|
|
342
|
+
const mod = require(file);
|
|
343
|
+
const serverHook = typeof mod?.getServerSideProps === "function" ? mod.getServerSideProps : null;
|
|
344
|
+
return serverHook;
|
|
345
|
+
} catch (error) {
|
|
346
|
+
console.error(
|
|
347
|
+
`[framework][server-hook] Error loading layout server hook from ${file}:`,
|
|
348
|
+
error
|
|
349
|
+
);
|
|
350
|
+
return null;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
307
353
|
|
|
308
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
|
+
}
|
|
309
399
|
function loadRoutes(appDir) {
|
|
310
400
|
if (!import_fs4.default.existsSync(appDir)) {
|
|
311
401
|
return [];
|
|
@@ -335,7 +425,12 @@ function loadRoutes(appDir) {
|
|
|
335
425
|
currentDir,
|
|
336
426
|
appDir
|
|
337
427
|
);
|
|
338
|
-
const
|
|
428
|
+
const layoutServerHooks = [];
|
|
429
|
+
for (const layoutFile of layoutFiles) {
|
|
430
|
+
const layoutServerHook = loadLayoutServerHook(layoutFile);
|
|
431
|
+
layoutServerHooks.push(layoutServerHook);
|
|
432
|
+
}
|
|
433
|
+
const { middlewares, serverHook, dynamic, generateStaticParams } = loadServerHookForDir(currentDir);
|
|
339
434
|
routes.push({
|
|
340
435
|
pattern: routePath,
|
|
341
436
|
regex,
|
|
@@ -345,13 +440,17 @@ function loadRoutes(appDir) {
|
|
|
345
440
|
pageFile: fullPath,
|
|
346
441
|
layoutFiles,
|
|
347
442
|
middlewares,
|
|
348
|
-
loader,
|
|
443
|
+
loader: serverHook,
|
|
444
|
+
// Keep 'loader' field name for backward compatibility
|
|
445
|
+
layoutServerHooks,
|
|
446
|
+
// Server hooks for each layout (same order as layouts)
|
|
349
447
|
dynamic,
|
|
350
448
|
generateStaticParams
|
|
351
449
|
});
|
|
352
450
|
}
|
|
353
451
|
}
|
|
354
452
|
walk(appDir);
|
|
453
|
+
validateRoutes(routes, appDir);
|
|
355
454
|
return routes;
|
|
356
455
|
}
|
|
357
456
|
|
|
@@ -715,9 +814,12 @@ function writeClientBoostrapManifest(projectRoot) {
|
|
|
715
814
|
lines.push("");
|
|
716
815
|
lines.push(`import { bootstrapClient } from "@lolyjs/core/runtime"`);
|
|
717
816
|
lines.push("");
|
|
718
|
-
lines.push(
|
|
719
|
-
|
|
720
|
-
);
|
|
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(`}`);
|
|
721
823
|
import_fs7.default.writeFileSync(manifestPath, lines.join("\n"), "utf-8");
|
|
722
824
|
}
|
|
723
825
|
function writeRoutesManifest({
|
|
@@ -864,7 +966,12 @@ function loadRoutesFromManifest(projectRoot) {
|
|
|
864
966
|
(f) => import_path10.default.join(projectRoot, f)
|
|
865
967
|
);
|
|
866
968
|
const pageDir = import_path10.default.dirname(pageFile);
|
|
867
|
-
const
|
|
969
|
+
const layoutServerHooks = [];
|
|
970
|
+
for (const layoutFile of layoutFiles) {
|
|
971
|
+
const layoutServerHook = loadLayoutServerHook(layoutFile);
|
|
972
|
+
layoutServerHooks.push(layoutServerHook);
|
|
973
|
+
}
|
|
974
|
+
const { middlewares, serverHook, dynamic, generateStaticParams } = loadServerHookForDir(pageDir);
|
|
868
975
|
pageRoutes.push({
|
|
869
976
|
pattern: entry.pattern,
|
|
870
977
|
regex,
|
|
@@ -874,7 +981,10 @@ function loadRoutesFromManifest(projectRoot) {
|
|
|
874
981
|
pageFile,
|
|
875
982
|
layoutFiles,
|
|
876
983
|
middlewares,
|
|
877
|
-
loader,
|
|
984
|
+
loader: serverHook,
|
|
985
|
+
// Keep 'loader' field name for backward compatibility
|
|
986
|
+
layoutServerHooks,
|
|
987
|
+
// Server hooks for each layout (same order as layouts)
|
|
878
988
|
dynamic: entry.dynamic ?? dynamic,
|
|
879
989
|
generateStaticParams
|
|
880
990
|
});
|
|
@@ -1047,24 +1157,210 @@ function loadWssRoutes(appDir) {
|
|
|
1047
1157
|
}
|
|
1048
1158
|
|
|
1049
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
|
+
}
|
|
1050
1239
|
var FilesystemRouteLoader = class {
|
|
1051
|
-
|
|
1240
|
+
// Maximum cache age in ms (1 second fallback)
|
|
1241
|
+
constructor(appDir, projectRoot = appDir) {
|
|
1052
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
|
+
}
|
|
1053
1278
|
}
|
|
1054
1279
|
loadRoutes() {
|
|
1055
|
-
|
|
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;
|
|
1056
1296
|
}
|
|
1057
1297
|
loadApiRoutes() {
|
|
1058
|
-
|
|
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;
|
|
1059
1313
|
}
|
|
1060
1314
|
loadWssRoutes() {
|
|
1061
|
-
|
|
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;
|
|
1062
1330
|
}
|
|
1063
1331
|
loadNotFoundRoute() {
|
|
1064
|
-
|
|
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;
|
|
1065
1347
|
}
|
|
1066
1348
|
loadErrorRoute() {
|
|
1067
|
-
|
|
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;
|
|
1068
1364
|
}
|
|
1069
1365
|
loadRouteChunks() {
|
|
1070
1366
|
return {};
|
|
@@ -1073,27 +1369,67 @@ var FilesystemRouteLoader = class {
|
|
|
1073
1369
|
var ManifestRouteLoader = class {
|
|
1074
1370
|
constructor(projectRoot) {
|
|
1075
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;
|
|
1076
1385
|
}
|
|
1077
1386
|
loadRoutes() {
|
|
1387
|
+
if (this.cache.routes) {
|
|
1388
|
+
return this.cache.routes;
|
|
1389
|
+
}
|
|
1078
1390
|
const { routes } = loadRoutesFromManifest(this.projectRoot);
|
|
1391
|
+
this.cache.routes = routes;
|
|
1079
1392
|
return routes;
|
|
1080
1393
|
}
|
|
1081
1394
|
loadApiRoutes() {
|
|
1395
|
+
if (this.cache.apiRoutes) {
|
|
1396
|
+
return this.cache.apiRoutes;
|
|
1397
|
+
}
|
|
1082
1398
|
const { apiRoutes } = loadRoutesFromManifest(this.projectRoot);
|
|
1399
|
+
this.cache.apiRoutes = apiRoutes;
|
|
1083
1400
|
return apiRoutes;
|
|
1084
1401
|
}
|
|
1085
1402
|
loadWssRoutes() {
|
|
1403
|
+
if (this.cache.wssRoutes) {
|
|
1404
|
+
return this.cache.wssRoutes;
|
|
1405
|
+
}
|
|
1086
1406
|
const { wssRoutes } = loadRoutesFromManifest(this.projectRoot);
|
|
1407
|
+
this.cache.wssRoutes = wssRoutes;
|
|
1087
1408
|
return wssRoutes;
|
|
1088
1409
|
}
|
|
1089
1410
|
loadNotFoundRoute() {
|
|
1090
|
-
|
|
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;
|
|
1091
1417
|
}
|
|
1092
1418
|
loadErrorRoute() {
|
|
1093
|
-
|
|
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;
|
|
1094
1425
|
}
|
|
1095
1426
|
loadRouteChunks() {
|
|
1096
|
-
|
|
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;
|
|
1097
1433
|
}
|
|
1098
1434
|
};
|
|
1099
1435
|
function loadNotFoundRouteFromFilesystem(appDir) {
|
|
@@ -1128,7 +1464,12 @@ function loadNotFoundRouteFromFilesystem(appDir) {
|
|
|
1128
1464
|
notFoundDir,
|
|
1129
1465
|
appDir
|
|
1130
1466
|
);
|
|
1131
|
-
const
|
|
1467
|
+
const layoutServerHooks = [];
|
|
1468
|
+
for (const layoutFile of layoutFiles) {
|
|
1469
|
+
const layoutServerHook = loadLayoutServerHook(layoutFile);
|
|
1470
|
+
layoutServerHooks.push(layoutServerHook);
|
|
1471
|
+
}
|
|
1472
|
+
const { middlewares, serverHook, dynamic, generateStaticParams } = loadServerHookForDir(notFoundDir);
|
|
1132
1473
|
return {
|
|
1133
1474
|
pattern: NOT_FOUND_PATTERN,
|
|
1134
1475
|
regex: new RegExp(`^${NOT_FOUND_PATTERN}/?$`),
|
|
@@ -1138,7 +1479,10 @@ function loadNotFoundRouteFromFilesystem(appDir) {
|
|
|
1138
1479
|
pageFile: notFoundFile,
|
|
1139
1480
|
layoutFiles,
|
|
1140
1481
|
middlewares,
|
|
1141
|
-
loader,
|
|
1482
|
+
loader: serverHook,
|
|
1483
|
+
// Keep 'loader' field name for backward compatibility
|
|
1484
|
+
layoutServerHooks,
|
|
1485
|
+
// Server hooks for each layout (same order as layouts)
|
|
1142
1486
|
dynamic,
|
|
1143
1487
|
generateStaticParams
|
|
1144
1488
|
};
|
|
@@ -1169,7 +1513,12 @@ function loadErrorRouteFromFilesystem(appDir) {
|
|
|
1169
1513
|
appDir,
|
|
1170
1514
|
appDir
|
|
1171
1515
|
);
|
|
1172
|
-
const
|
|
1516
|
+
const layoutServerHooks = [];
|
|
1517
|
+
for (const layoutFile of layoutFiles) {
|
|
1518
|
+
const layoutServerHook = loadLayoutServerHook(layoutFile);
|
|
1519
|
+
layoutServerHooks.push(layoutServerHook);
|
|
1520
|
+
}
|
|
1521
|
+
const { middlewares, serverHook, dynamic, generateStaticParams } = loadServerHookForDir(appDir);
|
|
1173
1522
|
return {
|
|
1174
1523
|
pattern: ERROR_PATTERN,
|
|
1175
1524
|
regex: new RegExp(`^${ERROR_PATTERN}/?$`),
|
|
@@ -1179,7 +1528,10 @@ function loadErrorRouteFromFilesystem(appDir) {
|
|
|
1179
1528
|
pageFile: errorFile,
|
|
1180
1529
|
layoutFiles,
|
|
1181
1530
|
middlewares,
|
|
1182
|
-
loader,
|
|
1531
|
+
loader: serverHook,
|
|
1532
|
+
// Keep 'loader' field name for backward compatibility
|
|
1533
|
+
layoutServerHooks,
|
|
1534
|
+
// Server hooks for each layout (same order as layouts)
|
|
1183
1535
|
dynamic,
|
|
1184
1536
|
generateStaticParams
|
|
1185
1537
|
};
|
|
@@ -1210,7 +1562,8 @@ function loadAliasesFromTsconfig(projectRoot) {
|
|
|
1210
1562
|
try {
|
|
1211
1563
|
tsconfig = JSON.parse(import_fs11.default.readFileSync(tsconfigPath, "utf-8"));
|
|
1212
1564
|
} catch (err) {
|
|
1213
|
-
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.");
|
|
1214
1567
|
aliases["@app"] = import_path13.default.resolve(projectRoot, "app");
|
|
1215
1568
|
return aliases;
|
|
1216
1569
|
}
|
|
@@ -1264,7 +1617,7 @@ function copyStaticAssets(projectRoot, outDir) {
|
|
|
1264
1617
|
}
|
|
1265
1618
|
}
|
|
1266
1619
|
}
|
|
1267
|
-
function generateAssetManifest(outDir) {
|
|
1620
|
+
function generateAssetManifest(outDir, stats) {
|
|
1268
1621
|
const manifest = {
|
|
1269
1622
|
client: {
|
|
1270
1623
|
js: "client.js",
|
|
@@ -1276,27 +1629,128 @@ function generateAssetManifest(outDir) {
|
|
|
1276
1629
|
return manifest;
|
|
1277
1630
|
}
|
|
1278
1631
|
const files = import_fs11.default.readdirSync(outDir);
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
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
|
+
}
|
|
1697
|
+
}
|
|
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
|
+
}
|
|
1282
1703
|
}
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
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
|
+
}
|
|
1286
1709
|
}
|
|
1710
|
+
const sharedChunksToAdd = [];
|
|
1287
1711
|
for (const file of files) {
|
|
1288
1712
|
if (!file.endsWith(".js")) continue;
|
|
1289
1713
|
if (file === manifest.client.js) continue;
|
|
1714
|
+
if (manifest.entrypoints?.client?.includes(file)) continue;
|
|
1290
1715
|
const routeMatch = file.match(/^(route-[^.]+)(\.[\w-]+)?\.js$/);
|
|
1291
1716
|
if (routeMatch) {
|
|
1292
1717
|
const chunkName = routeMatch[1];
|
|
1293
1718
|
manifest.chunks[chunkName] = file;
|
|
1294
1719
|
continue;
|
|
1295
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
|
+
}
|
|
1296
1735
|
const numericMatch = file.match(/^(\d+)(\.[\w-]+)?\.js$/);
|
|
1297
1736
|
if (numericMatch) {
|
|
1298
1737
|
const chunkName = numericMatch[1];
|
|
1299
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);
|
|
1300
1754
|
}
|
|
1301
1755
|
}
|
|
1302
1756
|
return manifest;
|
|
@@ -1336,9 +1790,7 @@ function createClientConfig(projectRoot, mode) {
|
|
|
1336
1790
|
const outDir = import_path14.default.join(buildDir, "client");
|
|
1337
1791
|
const envPath2 = import_path14.default.join(projectRoot, ".env");
|
|
1338
1792
|
if (import_fs12.default.existsSync(envPath2)) {
|
|
1339
|
-
import_dotenv.default.config({
|
|
1340
|
-
path: envPath2
|
|
1341
|
-
});
|
|
1793
|
+
import_dotenv.default.config({ path: envPath2 });
|
|
1342
1794
|
}
|
|
1343
1795
|
const publicEnv = {};
|
|
1344
1796
|
for (const [key, value] of Object.entries(process.env)) {
|
|
@@ -1397,14 +1849,57 @@ function createClientConfig(projectRoot, mode) {
|
|
|
1397
1849
|
},
|
|
1398
1850
|
plugins: [
|
|
1399
1851
|
new import_core.rspack.DefinePlugin({
|
|
1400
|
-
//
|
|
1401
|
-
|
|
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),
|
|
1402
1855
|
...publicEnv
|
|
1403
1856
|
}),
|
|
1404
1857
|
new import_core.rspack.CssExtractRspackPlugin({
|
|
1405
1858
|
filename: mode === "production" ? "client.[contenthash].css" : "client.css"
|
|
1406
1859
|
})
|
|
1407
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,
|
|
1408
1903
|
infrastructureLogging: {
|
|
1409
1904
|
level: "error"
|
|
1410
1905
|
},
|
|
@@ -1433,7 +1928,13 @@ function startClientBundler(projectRoot, mode = "development") {
|
|
|
1433
1928
|
});
|
|
1434
1929
|
compiler.watch({}, (err, stats) => {
|
|
1435
1930
|
if (err) {
|
|
1436
|
-
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");
|
|
1437
1938
|
isBuilding = false;
|
|
1438
1939
|
lastBuildTime = Date.now();
|
|
1439
1940
|
if (buildResolve) {
|
|
@@ -1444,17 +1945,20 @@ function startClientBundler(projectRoot, mode = "development") {
|
|
|
1444
1945
|
return;
|
|
1445
1946
|
}
|
|
1446
1947
|
if (!stats) {
|
|
1948
|
+
console.warn("\u26A0\uFE0F [framework][client] Build completed but no stats available");
|
|
1447
1949
|
isBuilding = false;
|
|
1448
1950
|
lastBuildTime = Date.now();
|
|
1449
1951
|
return;
|
|
1450
1952
|
}
|
|
1451
1953
|
if (stats.hasErrors()) {
|
|
1452
|
-
console.error(
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
);
|
|
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");
|
|
1456
1960
|
} else {
|
|
1457
|
-
console.log("[framework][client]
|
|
1961
|
+
console.log("\u2705 [framework][client] Client bundle rebuilt successfully");
|
|
1458
1962
|
}
|
|
1459
1963
|
isBuilding = false;
|
|
1460
1964
|
lastBuildTime = Date.now();
|
|
@@ -1489,23 +1993,33 @@ function buildClientBundle(projectRoot) {
|
|
|
1489
1993
|
compiler.close(() => {
|
|
1490
1994
|
});
|
|
1491
1995
|
if (err) {
|
|
1492
|
-
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");
|
|
1493
2003
|
return reject(err);
|
|
1494
2004
|
}
|
|
1495
2005
|
if (!stats) {
|
|
1496
|
-
const error = new Error("No stats from Rspack");
|
|
1497
|
-
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");
|
|
1498
2009
|
return reject(error);
|
|
1499
2010
|
}
|
|
1500
2011
|
if (stats.hasErrors()) {
|
|
1501
|
-
console.error(
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
);
|
|
1505
|
-
|
|
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"));
|
|
1506
2020
|
}
|
|
1507
2021
|
copyStaticAssets(projectRoot, outDir);
|
|
1508
|
-
const assetManifest = generateAssetManifest(outDir);
|
|
2022
|
+
const assetManifest = generateAssetManifest(outDir, stats);
|
|
1509
2023
|
const manifestPath = import_path15.default.join(projectRoot, BUILD_FOLDER_NAME, "asset-manifest.json");
|
|
1510
2024
|
import_fs13.default.writeFileSync(manifestPath, JSON.stringify(assetManifest, null, 2), "utf-8");
|
|
1511
2025
|
resolve3({ outDir });
|
|
@@ -1589,7 +2103,7 @@ var ReaddirpStream = class extends import_node_stream.Readable {
|
|
|
1589
2103
|
this._directoryFilter = normalizeFilter(opts.directoryFilter);
|
|
1590
2104
|
const statMethod = opts.lstat ? import_promises.lstat : import_promises.stat;
|
|
1591
2105
|
if (wantBigintFsStats) {
|
|
1592
|
-
this._stat = (
|
|
2106
|
+
this._stat = (path28) => statMethod(path28, { bigint: true });
|
|
1593
2107
|
} else {
|
|
1594
2108
|
this._stat = statMethod;
|
|
1595
2109
|
}
|
|
@@ -1614,8 +2128,8 @@ var ReaddirpStream = class extends import_node_stream.Readable {
|
|
|
1614
2128
|
const par = this.parent;
|
|
1615
2129
|
const fil = par && par.files;
|
|
1616
2130
|
if (fil && fil.length > 0) {
|
|
1617
|
-
const { path:
|
|
1618
|
-
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));
|
|
1619
2133
|
const awaited = await Promise.all(slice);
|
|
1620
2134
|
for (const entry of awaited) {
|
|
1621
2135
|
if (!entry)
|
|
@@ -1655,20 +2169,20 @@ var ReaddirpStream = class extends import_node_stream.Readable {
|
|
|
1655
2169
|
this.reading = false;
|
|
1656
2170
|
}
|
|
1657
2171
|
}
|
|
1658
|
-
async _exploreDir(
|
|
2172
|
+
async _exploreDir(path28, depth) {
|
|
1659
2173
|
let files;
|
|
1660
2174
|
try {
|
|
1661
|
-
files = await (0, import_promises.readdir)(
|
|
2175
|
+
files = await (0, import_promises.readdir)(path28, this._rdOptions);
|
|
1662
2176
|
} catch (error) {
|
|
1663
2177
|
this._onError(error);
|
|
1664
2178
|
}
|
|
1665
|
-
return { files, depth, path:
|
|
2179
|
+
return { files, depth, path: path28 };
|
|
1666
2180
|
}
|
|
1667
|
-
async _formatEntry(dirent,
|
|
2181
|
+
async _formatEntry(dirent, path28) {
|
|
1668
2182
|
let entry;
|
|
1669
2183
|
const basename3 = this._isDirent ? dirent.name : dirent;
|
|
1670
2184
|
try {
|
|
1671
|
-
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));
|
|
1672
2186
|
entry = { path: (0, import_node_path.relative)(this._root, fullPath), fullPath, basename: basename3 };
|
|
1673
2187
|
entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
|
|
1674
2188
|
} catch (err) {
|
|
@@ -2068,16 +2582,16 @@ var delFromSet = (main, prop, item) => {
|
|
|
2068
2582
|
};
|
|
2069
2583
|
var isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
|
|
2070
2584
|
var FsWatchInstances = /* @__PURE__ */ new Map();
|
|
2071
|
-
function createFsWatchInstance(
|
|
2585
|
+
function createFsWatchInstance(path28, options, listener, errHandler, emitRaw) {
|
|
2072
2586
|
const handleEvent = (rawEvent, evPath) => {
|
|
2073
|
-
listener(
|
|
2074
|
-
emitRaw(rawEvent, evPath, { watchedPath:
|
|
2075
|
-
if (evPath &&
|
|
2076
|
-
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));
|
|
2077
2591
|
}
|
|
2078
2592
|
};
|
|
2079
2593
|
try {
|
|
2080
|
-
return (0, import_fs14.watch)(
|
|
2594
|
+
return (0, import_fs14.watch)(path28, {
|
|
2081
2595
|
persistent: options.persistent
|
|
2082
2596
|
}, handleEvent);
|
|
2083
2597
|
} catch (error) {
|
|
@@ -2093,12 +2607,12 @@ var fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {
|
|
|
2093
2607
|
listener(val1, val2, val3);
|
|
2094
2608
|
});
|
|
2095
2609
|
};
|
|
2096
|
-
var setFsWatchListener = (
|
|
2610
|
+
var setFsWatchListener = (path28, fullPath, options, handlers) => {
|
|
2097
2611
|
const { listener, errHandler, rawEmitter } = handlers;
|
|
2098
2612
|
let cont = FsWatchInstances.get(fullPath);
|
|
2099
2613
|
let watcher;
|
|
2100
2614
|
if (!options.persistent) {
|
|
2101
|
-
watcher = createFsWatchInstance(
|
|
2615
|
+
watcher = createFsWatchInstance(path28, options, listener, errHandler, rawEmitter);
|
|
2102
2616
|
if (!watcher)
|
|
2103
2617
|
return;
|
|
2104
2618
|
return watcher.close.bind(watcher);
|
|
@@ -2109,7 +2623,7 @@ var setFsWatchListener = (path25, fullPath, options, handlers) => {
|
|
|
2109
2623
|
addAndConvert(cont, KEY_RAW, rawEmitter);
|
|
2110
2624
|
} else {
|
|
2111
2625
|
watcher = createFsWatchInstance(
|
|
2112
|
-
|
|
2626
|
+
path28,
|
|
2113
2627
|
options,
|
|
2114
2628
|
fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS),
|
|
2115
2629
|
errHandler,
|
|
@@ -2124,7 +2638,7 @@ var setFsWatchListener = (path25, fullPath, options, handlers) => {
|
|
|
2124
2638
|
cont.watcherUnusable = true;
|
|
2125
2639
|
if (isWindows && error.code === "EPERM") {
|
|
2126
2640
|
try {
|
|
2127
|
-
const fd = await (0, import_promises2.open)(
|
|
2641
|
+
const fd = await (0, import_promises2.open)(path28, "r");
|
|
2128
2642
|
await fd.close();
|
|
2129
2643
|
broadcastErr(error);
|
|
2130
2644
|
} catch (err) {
|
|
@@ -2155,7 +2669,7 @@ var setFsWatchListener = (path25, fullPath, options, handlers) => {
|
|
|
2155
2669
|
};
|
|
2156
2670
|
};
|
|
2157
2671
|
var FsWatchFileInstances = /* @__PURE__ */ new Map();
|
|
2158
|
-
var setFsWatchFileListener = (
|
|
2672
|
+
var setFsWatchFileListener = (path28, fullPath, options, handlers) => {
|
|
2159
2673
|
const { listener, rawEmitter } = handlers;
|
|
2160
2674
|
let cont = FsWatchFileInstances.get(fullPath);
|
|
2161
2675
|
const copts = cont && cont.options;
|
|
@@ -2177,7 +2691,7 @@ var setFsWatchFileListener = (path25, fullPath, options, handlers) => {
|
|
|
2177
2691
|
});
|
|
2178
2692
|
const currmtime = curr.mtimeMs;
|
|
2179
2693
|
if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
|
|
2180
|
-
foreach(cont.listeners, (listener2) => listener2(
|
|
2694
|
+
foreach(cont.listeners, (listener2) => listener2(path28, curr));
|
|
2181
2695
|
}
|
|
2182
2696
|
})
|
|
2183
2697
|
};
|
|
@@ -2205,13 +2719,13 @@ var NodeFsHandler = class {
|
|
|
2205
2719
|
* @param listener on fs change
|
|
2206
2720
|
* @returns closer for the watcher instance
|
|
2207
2721
|
*/
|
|
2208
|
-
_watchWithNodeFs(
|
|
2722
|
+
_watchWithNodeFs(path28, listener) {
|
|
2209
2723
|
const opts = this.fsw.options;
|
|
2210
|
-
const directory = sysPath.dirname(
|
|
2211
|
-
const basename3 = sysPath.basename(
|
|
2724
|
+
const directory = sysPath.dirname(path28);
|
|
2725
|
+
const basename3 = sysPath.basename(path28);
|
|
2212
2726
|
const parent = this.fsw._getWatchedDir(directory);
|
|
2213
2727
|
parent.add(basename3);
|
|
2214
|
-
const absolutePath = sysPath.resolve(
|
|
2728
|
+
const absolutePath = sysPath.resolve(path28);
|
|
2215
2729
|
const options = {
|
|
2216
2730
|
persistent: opts.persistent
|
|
2217
2731
|
};
|
|
@@ -2221,12 +2735,12 @@ var NodeFsHandler = class {
|
|
|
2221
2735
|
if (opts.usePolling) {
|
|
2222
2736
|
const enableBin = opts.interval !== opts.binaryInterval;
|
|
2223
2737
|
options.interval = enableBin && isBinaryPath(basename3) ? opts.binaryInterval : opts.interval;
|
|
2224
|
-
closer = setFsWatchFileListener(
|
|
2738
|
+
closer = setFsWatchFileListener(path28, absolutePath, options, {
|
|
2225
2739
|
listener,
|
|
2226
2740
|
rawEmitter: this.fsw._emitRaw
|
|
2227
2741
|
});
|
|
2228
2742
|
} else {
|
|
2229
|
-
closer = setFsWatchListener(
|
|
2743
|
+
closer = setFsWatchListener(path28, absolutePath, options, {
|
|
2230
2744
|
listener,
|
|
2231
2745
|
errHandler: this._boundHandleError,
|
|
2232
2746
|
rawEmitter: this.fsw._emitRaw
|
|
@@ -2248,7 +2762,7 @@ var NodeFsHandler = class {
|
|
|
2248
2762
|
let prevStats = stats;
|
|
2249
2763
|
if (parent.has(basename3))
|
|
2250
2764
|
return;
|
|
2251
|
-
const listener = async (
|
|
2765
|
+
const listener = async (path28, newStats) => {
|
|
2252
2766
|
if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
|
|
2253
2767
|
return;
|
|
2254
2768
|
if (!newStats || newStats.mtimeMs === 0) {
|
|
@@ -2262,11 +2776,11 @@ var NodeFsHandler = class {
|
|
|
2262
2776
|
this.fsw._emit(EV.CHANGE, file, newStats2);
|
|
2263
2777
|
}
|
|
2264
2778
|
if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats2.ino) {
|
|
2265
|
-
this.fsw._closeFile(
|
|
2779
|
+
this.fsw._closeFile(path28);
|
|
2266
2780
|
prevStats = newStats2;
|
|
2267
2781
|
const closer2 = this._watchWithNodeFs(file, listener);
|
|
2268
2782
|
if (closer2)
|
|
2269
|
-
this.fsw._addPathCloser(
|
|
2783
|
+
this.fsw._addPathCloser(path28, closer2);
|
|
2270
2784
|
} else {
|
|
2271
2785
|
prevStats = newStats2;
|
|
2272
2786
|
}
|
|
@@ -2298,7 +2812,7 @@ var NodeFsHandler = class {
|
|
|
2298
2812
|
* @param item basename of this item
|
|
2299
2813
|
* @returns true if no more processing is needed for this entry.
|
|
2300
2814
|
*/
|
|
2301
|
-
async _handleSymlink(entry, directory,
|
|
2815
|
+
async _handleSymlink(entry, directory, path28, item) {
|
|
2302
2816
|
if (this.fsw.closed) {
|
|
2303
2817
|
return;
|
|
2304
2818
|
}
|
|
@@ -2308,7 +2822,7 @@ var NodeFsHandler = class {
|
|
|
2308
2822
|
this.fsw._incrReadyCount();
|
|
2309
2823
|
let linkPath;
|
|
2310
2824
|
try {
|
|
2311
|
-
linkPath = await (0, import_promises2.realpath)(
|
|
2825
|
+
linkPath = await (0, import_promises2.realpath)(path28);
|
|
2312
2826
|
} catch (e) {
|
|
2313
2827
|
this.fsw._emitReady();
|
|
2314
2828
|
return true;
|
|
@@ -2318,12 +2832,12 @@ var NodeFsHandler = class {
|
|
|
2318
2832
|
if (dir.has(item)) {
|
|
2319
2833
|
if (this.fsw._symlinkPaths.get(full) !== linkPath) {
|
|
2320
2834
|
this.fsw._symlinkPaths.set(full, linkPath);
|
|
2321
|
-
this.fsw._emit(EV.CHANGE,
|
|
2835
|
+
this.fsw._emit(EV.CHANGE, path28, entry.stats);
|
|
2322
2836
|
}
|
|
2323
2837
|
} else {
|
|
2324
2838
|
dir.add(item);
|
|
2325
2839
|
this.fsw._symlinkPaths.set(full, linkPath);
|
|
2326
|
-
this.fsw._emit(EV.ADD,
|
|
2840
|
+
this.fsw._emit(EV.ADD, path28, entry.stats);
|
|
2327
2841
|
}
|
|
2328
2842
|
this.fsw._emitReady();
|
|
2329
2843
|
return true;
|
|
@@ -2352,9 +2866,9 @@ var NodeFsHandler = class {
|
|
|
2352
2866
|
return;
|
|
2353
2867
|
}
|
|
2354
2868
|
const item = entry.path;
|
|
2355
|
-
let
|
|
2869
|
+
let path28 = sysPath.join(directory, item);
|
|
2356
2870
|
current.add(item);
|
|
2357
|
-
if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory,
|
|
2871
|
+
if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path28, item)) {
|
|
2358
2872
|
return;
|
|
2359
2873
|
}
|
|
2360
2874
|
if (this.fsw.closed) {
|
|
@@ -2363,8 +2877,8 @@ var NodeFsHandler = class {
|
|
|
2363
2877
|
}
|
|
2364
2878
|
if (item === target || !target && !previous.has(item)) {
|
|
2365
2879
|
this.fsw._incrReadyCount();
|
|
2366
|
-
|
|
2367
|
-
this._addToNodeFs(
|
|
2880
|
+
path28 = sysPath.join(dir, sysPath.relative(dir, path28));
|
|
2881
|
+
this._addToNodeFs(path28, initialAdd, wh, depth + 1);
|
|
2368
2882
|
}
|
|
2369
2883
|
}).on(EV.ERROR, this._boundHandleError);
|
|
2370
2884
|
return new Promise((resolve3, reject) => {
|
|
@@ -2433,13 +2947,13 @@ var NodeFsHandler = class {
|
|
|
2433
2947
|
* @param depth Child path actually targeted for watch
|
|
2434
2948
|
* @param target Child path actually targeted for watch
|
|
2435
2949
|
*/
|
|
2436
|
-
async _addToNodeFs(
|
|
2950
|
+
async _addToNodeFs(path28, initialAdd, priorWh, depth, target) {
|
|
2437
2951
|
const ready = this.fsw._emitReady;
|
|
2438
|
-
if (this.fsw._isIgnored(
|
|
2952
|
+
if (this.fsw._isIgnored(path28) || this.fsw.closed) {
|
|
2439
2953
|
ready();
|
|
2440
2954
|
return false;
|
|
2441
2955
|
}
|
|
2442
|
-
const wh = this.fsw._getWatchHelpers(
|
|
2956
|
+
const wh = this.fsw._getWatchHelpers(path28);
|
|
2443
2957
|
if (priorWh) {
|
|
2444
2958
|
wh.filterPath = (entry) => priorWh.filterPath(entry);
|
|
2445
2959
|
wh.filterDir = (entry) => priorWh.filterDir(entry);
|
|
@@ -2455,8 +2969,8 @@ var NodeFsHandler = class {
|
|
|
2455
2969
|
const follow = this.fsw.options.followSymlinks;
|
|
2456
2970
|
let closer;
|
|
2457
2971
|
if (stats.isDirectory()) {
|
|
2458
|
-
const absPath = sysPath.resolve(
|
|
2459
|
-
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;
|
|
2460
2974
|
if (this.fsw.closed)
|
|
2461
2975
|
return;
|
|
2462
2976
|
closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
|
|
@@ -2466,29 +2980,29 @@ var NodeFsHandler = class {
|
|
|
2466
2980
|
this.fsw._symlinkPaths.set(absPath, targetPath);
|
|
2467
2981
|
}
|
|
2468
2982
|
} else if (stats.isSymbolicLink()) {
|
|
2469
|
-
const targetPath = follow ? await (0, import_promises2.realpath)(
|
|
2983
|
+
const targetPath = follow ? await (0, import_promises2.realpath)(path28) : path28;
|
|
2470
2984
|
if (this.fsw.closed)
|
|
2471
2985
|
return;
|
|
2472
2986
|
const parent = sysPath.dirname(wh.watchPath);
|
|
2473
2987
|
this.fsw._getWatchedDir(parent).add(wh.watchPath);
|
|
2474
2988
|
this.fsw._emit(EV.ADD, wh.watchPath, stats);
|
|
2475
|
-
closer = await this._handleDir(parent, stats, initialAdd, depth,
|
|
2989
|
+
closer = await this._handleDir(parent, stats, initialAdd, depth, path28, wh, targetPath);
|
|
2476
2990
|
if (this.fsw.closed)
|
|
2477
2991
|
return;
|
|
2478
2992
|
if (targetPath !== void 0) {
|
|
2479
|
-
this.fsw._symlinkPaths.set(sysPath.resolve(
|
|
2993
|
+
this.fsw._symlinkPaths.set(sysPath.resolve(path28), targetPath);
|
|
2480
2994
|
}
|
|
2481
2995
|
} else {
|
|
2482
2996
|
closer = this._handleFile(wh.watchPath, stats, initialAdd);
|
|
2483
2997
|
}
|
|
2484
2998
|
ready();
|
|
2485
2999
|
if (closer)
|
|
2486
|
-
this.fsw._addPathCloser(
|
|
3000
|
+
this.fsw._addPathCloser(path28, closer);
|
|
2487
3001
|
return false;
|
|
2488
3002
|
} catch (error) {
|
|
2489
3003
|
if (this.fsw._handleError(error)) {
|
|
2490
3004
|
ready();
|
|
2491
|
-
return
|
|
3005
|
+
return path28;
|
|
2492
3006
|
}
|
|
2493
3007
|
}
|
|
2494
3008
|
}
|
|
@@ -2531,26 +3045,26 @@ function createPattern(matcher) {
|
|
|
2531
3045
|
}
|
|
2532
3046
|
return () => false;
|
|
2533
3047
|
}
|
|
2534
|
-
function normalizePath(
|
|
2535
|
-
if (typeof
|
|
3048
|
+
function normalizePath(path28) {
|
|
3049
|
+
if (typeof path28 !== "string")
|
|
2536
3050
|
throw new Error("string expected");
|
|
2537
|
-
|
|
2538
|
-
|
|
3051
|
+
path28 = sysPath2.normalize(path28);
|
|
3052
|
+
path28 = path28.replace(/\\/g, "/");
|
|
2539
3053
|
let prepend = false;
|
|
2540
|
-
if (
|
|
3054
|
+
if (path28.startsWith("//"))
|
|
2541
3055
|
prepend = true;
|
|
2542
3056
|
const DOUBLE_SLASH_RE2 = /\/\//;
|
|
2543
|
-
while (
|
|
2544
|
-
|
|
3057
|
+
while (path28.match(DOUBLE_SLASH_RE2))
|
|
3058
|
+
path28 = path28.replace(DOUBLE_SLASH_RE2, "/");
|
|
2545
3059
|
if (prepend)
|
|
2546
|
-
|
|
2547
|
-
return
|
|
3060
|
+
path28 = "/" + path28;
|
|
3061
|
+
return path28;
|
|
2548
3062
|
}
|
|
2549
3063
|
function matchPatterns(patterns, testString, stats) {
|
|
2550
|
-
const
|
|
3064
|
+
const path28 = normalizePath(testString);
|
|
2551
3065
|
for (let index = 0; index < patterns.length; index++) {
|
|
2552
3066
|
const pattern = patterns[index];
|
|
2553
|
-
if (pattern(
|
|
3067
|
+
if (pattern(path28, stats)) {
|
|
2554
3068
|
return true;
|
|
2555
3069
|
}
|
|
2556
3070
|
}
|
|
@@ -2590,19 +3104,19 @@ var toUnix = (string) => {
|
|
|
2590
3104
|
}
|
|
2591
3105
|
return str;
|
|
2592
3106
|
};
|
|
2593
|
-
var normalizePathToUnix = (
|
|
2594
|
-
var normalizeIgnored = (cwd = "") => (
|
|
2595
|
-
if (typeof
|
|
2596
|
-
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));
|
|
2597
3111
|
} else {
|
|
2598
|
-
return
|
|
3112
|
+
return path28;
|
|
2599
3113
|
}
|
|
2600
3114
|
};
|
|
2601
|
-
var getAbsolutePath = (
|
|
2602
|
-
if (sysPath2.isAbsolute(
|
|
2603
|
-
return
|
|
3115
|
+
var getAbsolutePath = (path28, cwd) => {
|
|
3116
|
+
if (sysPath2.isAbsolute(path28)) {
|
|
3117
|
+
return path28;
|
|
2604
3118
|
}
|
|
2605
|
-
return sysPath2.join(cwd,
|
|
3119
|
+
return sysPath2.join(cwd, path28);
|
|
2606
3120
|
};
|
|
2607
3121
|
var EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set());
|
|
2608
3122
|
var DirEntry = class {
|
|
@@ -2657,10 +3171,10 @@ var DirEntry = class {
|
|
|
2657
3171
|
var STAT_METHOD_F = "stat";
|
|
2658
3172
|
var STAT_METHOD_L = "lstat";
|
|
2659
3173
|
var WatchHelper = class {
|
|
2660
|
-
constructor(
|
|
3174
|
+
constructor(path28, follow, fsw) {
|
|
2661
3175
|
this.fsw = fsw;
|
|
2662
|
-
const watchPath =
|
|
2663
|
-
this.path =
|
|
3176
|
+
const watchPath = path28;
|
|
3177
|
+
this.path = path28 = path28.replace(REPLACER_RE, "");
|
|
2664
3178
|
this.watchPath = watchPath;
|
|
2665
3179
|
this.fullWatchPath = sysPath2.resolve(watchPath);
|
|
2666
3180
|
this.dirParts = [];
|
|
@@ -2782,20 +3296,20 @@ var FSWatcher = class extends import_events.EventEmitter {
|
|
|
2782
3296
|
this._closePromise = void 0;
|
|
2783
3297
|
let paths = unifyPaths(paths_);
|
|
2784
3298
|
if (cwd) {
|
|
2785
|
-
paths = paths.map((
|
|
2786
|
-
const absPath = getAbsolutePath(
|
|
3299
|
+
paths = paths.map((path28) => {
|
|
3300
|
+
const absPath = getAbsolutePath(path28, cwd);
|
|
2787
3301
|
return absPath;
|
|
2788
3302
|
});
|
|
2789
3303
|
}
|
|
2790
|
-
paths.forEach((
|
|
2791
|
-
this._removeIgnoredPath(
|
|
3304
|
+
paths.forEach((path28) => {
|
|
3305
|
+
this._removeIgnoredPath(path28);
|
|
2792
3306
|
});
|
|
2793
3307
|
this._userIgnored = void 0;
|
|
2794
3308
|
if (!this._readyCount)
|
|
2795
3309
|
this._readyCount = 0;
|
|
2796
3310
|
this._readyCount += paths.length;
|
|
2797
|
-
Promise.all(paths.map(async (
|
|
2798
|
-
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);
|
|
2799
3313
|
if (res)
|
|
2800
3314
|
this._emitReady();
|
|
2801
3315
|
return res;
|
|
@@ -2817,17 +3331,17 @@ var FSWatcher = class extends import_events.EventEmitter {
|
|
|
2817
3331
|
return this;
|
|
2818
3332
|
const paths = unifyPaths(paths_);
|
|
2819
3333
|
const { cwd } = this.options;
|
|
2820
|
-
paths.forEach((
|
|
2821
|
-
if (!sysPath2.isAbsolute(
|
|
3334
|
+
paths.forEach((path28) => {
|
|
3335
|
+
if (!sysPath2.isAbsolute(path28) && !this._closers.has(path28)) {
|
|
2822
3336
|
if (cwd)
|
|
2823
|
-
|
|
2824
|
-
|
|
3337
|
+
path28 = sysPath2.join(cwd, path28);
|
|
3338
|
+
path28 = sysPath2.resolve(path28);
|
|
2825
3339
|
}
|
|
2826
|
-
this._closePath(
|
|
2827
|
-
this._addIgnoredPath(
|
|
2828
|
-
if (this._watched.has(
|
|
3340
|
+
this._closePath(path28);
|
|
3341
|
+
this._addIgnoredPath(path28);
|
|
3342
|
+
if (this._watched.has(path28)) {
|
|
2829
3343
|
this._addIgnoredPath({
|
|
2830
|
-
path:
|
|
3344
|
+
path: path28,
|
|
2831
3345
|
recursive: true
|
|
2832
3346
|
});
|
|
2833
3347
|
}
|
|
@@ -2891,38 +3405,38 @@ var FSWatcher = class extends import_events.EventEmitter {
|
|
|
2891
3405
|
* @param stats arguments to be passed with event
|
|
2892
3406
|
* @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
|
|
2893
3407
|
*/
|
|
2894
|
-
async _emit(event,
|
|
3408
|
+
async _emit(event, path28, stats) {
|
|
2895
3409
|
if (this.closed)
|
|
2896
3410
|
return;
|
|
2897
3411
|
const opts = this.options;
|
|
2898
3412
|
if (isWindows)
|
|
2899
|
-
|
|
3413
|
+
path28 = sysPath2.normalize(path28);
|
|
2900
3414
|
if (opts.cwd)
|
|
2901
|
-
|
|
2902
|
-
const args = [
|
|
3415
|
+
path28 = sysPath2.relative(opts.cwd, path28);
|
|
3416
|
+
const args = [path28];
|
|
2903
3417
|
if (stats != null)
|
|
2904
3418
|
args.push(stats);
|
|
2905
3419
|
const awf = opts.awaitWriteFinish;
|
|
2906
3420
|
let pw;
|
|
2907
|
-
if (awf && (pw = this._pendingWrites.get(
|
|
3421
|
+
if (awf && (pw = this._pendingWrites.get(path28))) {
|
|
2908
3422
|
pw.lastChange = /* @__PURE__ */ new Date();
|
|
2909
3423
|
return this;
|
|
2910
3424
|
}
|
|
2911
3425
|
if (opts.atomic) {
|
|
2912
3426
|
if (event === EVENTS.UNLINK) {
|
|
2913
|
-
this._pendingUnlinks.set(
|
|
3427
|
+
this._pendingUnlinks.set(path28, [event, ...args]);
|
|
2914
3428
|
setTimeout(() => {
|
|
2915
|
-
this._pendingUnlinks.forEach((entry,
|
|
3429
|
+
this._pendingUnlinks.forEach((entry, path29) => {
|
|
2916
3430
|
this.emit(...entry);
|
|
2917
3431
|
this.emit(EVENTS.ALL, ...entry);
|
|
2918
|
-
this._pendingUnlinks.delete(
|
|
3432
|
+
this._pendingUnlinks.delete(path29);
|
|
2919
3433
|
});
|
|
2920
3434
|
}, typeof opts.atomic === "number" ? opts.atomic : 100);
|
|
2921
3435
|
return this;
|
|
2922
3436
|
}
|
|
2923
|
-
if (event === EVENTS.ADD && this._pendingUnlinks.has(
|
|
3437
|
+
if (event === EVENTS.ADD && this._pendingUnlinks.has(path28)) {
|
|
2924
3438
|
event = EVENTS.CHANGE;
|
|
2925
|
-
this._pendingUnlinks.delete(
|
|
3439
|
+
this._pendingUnlinks.delete(path28);
|
|
2926
3440
|
}
|
|
2927
3441
|
}
|
|
2928
3442
|
if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
|
|
@@ -2940,16 +3454,16 @@ var FSWatcher = class extends import_events.EventEmitter {
|
|
|
2940
3454
|
this.emitWithAll(event, args);
|
|
2941
3455
|
}
|
|
2942
3456
|
};
|
|
2943
|
-
this._awaitWriteFinish(
|
|
3457
|
+
this._awaitWriteFinish(path28, awf.stabilityThreshold, event, awfEmit);
|
|
2944
3458
|
return this;
|
|
2945
3459
|
}
|
|
2946
3460
|
if (event === EVENTS.CHANGE) {
|
|
2947
|
-
const isThrottled = !this._throttle(EVENTS.CHANGE,
|
|
3461
|
+
const isThrottled = !this._throttle(EVENTS.CHANGE, path28, 50);
|
|
2948
3462
|
if (isThrottled)
|
|
2949
3463
|
return this;
|
|
2950
3464
|
}
|
|
2951
3465
|
if (opts.alwaysStat && stats === void 0 && (event === EVENTS.ADD || event === EVENTS.ADD_DIR || event === EVENTS.CHANGE)) {
|
|
2952
|
-
const fullPath = opts.cwd ? sysPath2.join(opts.cwd,
|
|
3466
|
+
const fullPath = opts.cwd ? sysPath2.join(opts.cwd, path28) : path28;
|
|
2953
3467
|
let stats2;
|
|
2954
3468
|
try {
|
|
2955
3469
|
stats2 = await (0, import_promises3.stat)(fullPath);
|
|
@@ -2980,23 +3494,23 @@ var FSWatcher = class extends import_events.EventEmitter {
|
|
|
2980
3494
|
* @param timeout duration of time to suppress duplicate actions
|
|
2981
3495
|
* @returns tracking object or false if action should be suppressed
|
|
2982
3496
|
*/
|
|
2983
|
-
_throttle(actionType,
|
|
3497
|
+
_throttle(actionType, path28, timeout) {
|
|
2984
3498
|
if (!this._throttled.has(actionType)) {
|
|
2985
3499
|
this._throttled.set(actionType, /* @__PURE__ */ new Map());
|
|
2986
3500
|
}
|
|
2987
3501
|
const action = this._throttled.get(actionType);
|
|
2988
3502
|
if (!action)
|
|
2989
3503
|
throw new Error("invalid throttle");
|
|
2990
|
-
const actionPath = action.get(
|
|
3504
|
+
const actionPath = action.get(path28);
|
|
2991
3505
|
if (actionPath) {
|
|
2992
3506
|
actionPath.count++;
|
|
2993
3507
|
return false;
|
|
2994
3508
|
}
|
|
2995
3509
|
let timeoutObject;
|
|
2996
3510
|
const clear = () => {
|
|
2997
|
-
const item = action.get(
|
|
3511
|
+
const item = action.get(path28);
|
|
2998
3512
|
const count = item ? item.count : 0;
|
|
2999
|
-
action.delete(
|
|
3513
|
+
action.delete(path28);
|
|
3000
3514
|
clearTimeout(timeoutObject);
|
|
3001
3515
|
if (item)
|
|
3002
3516
|
clearTimeout(item.timeoutObject);
|
|
@@ -3004,7 +3518,7 @@ var FSWatcher = class extends import_events.EventEmitter {
|
|
|
3004
3518
|
};
|
|
3005
3519
|
timeoutObject = setTimeout(clear, timeout);
|
|
3006
3520
|
const thr = { timeoutObject, clear, count: 0 };
|
|
3007
|
-
action.set(
|
|
3521
|
+
action.set(path28, thr);
|
|
3008
3522
|
return thr;
|
|
3009
3523
|
}
|
|
3010
3524
|
_incrReadyCount() {
|
|
@@ -3018,44 +3532,44 @@ var FSWatcher = class extends import_events.EventEmitter {
|
|
|
3018
3532
|
* @param event
|
|
3019
3533
|
* @param awfEmit Callback to be called when ready for event to be emitted.
|
|
3020
3534
|
*/
|
|
3021
|
-
_awaitWriteFinish(
|
|
3535
|
+
_awaitWriteFinish(path28, threshold, event, awfEmit) {
|
|
3022
3536
|
const awf = this.options.awaitWriteFinish;
|
|
3023
3537
|
if (typeof awf !== "object")
|
|
3024
3538
|
return;
|
|
3025
3539
|
const pollInterval = awf.pollInterval;
|
|
3026
3540
|
let timeoutHandler;
|
|
3027
|
-
let fullPath =
|
|
3028
|
-
if (this.options.cwd && !sysPath2.isAbsolute(
|
|
3029
|
-
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);
|
|
3030
3544
|
}
|
|
3031
3545
|
const now = /* @__PURE__ */ new Date();
|
|
3032
3546
|
const writes = this._pendingWrites;
|
|
3033
3547
|
function awaitWriteFinishFn(prevStat) {
|
|
3034
3548
|
(0, import_fs15.stat)(fullPath, (err, curStat) => {
|
|
3035
|
-
if (err || !writes.has(
|
|
3549
|
+
if (err || !writes.has(path28)) {
|
|
3036
3550
|
if (err && err.code !== "ENOENT")
|
|
3037
3551
|
awfEmit(err);
|
|
3038
3552
|
return;
|
|
3039
3553
|
}
|
|
3040
3554
|
const now2 = Number(/* @__PURE__ */ new Date());
|
|
3041
3555
|
if (prevStat && curStat.size !== prevStat.size) {
|
|
3042
|
-
writes.get(
|
|
3556
|
+
writes.get(path28).lastChange = now2;
|
|
3043
3557
|
}
|
|
3044
|
-
const pw = writes.get(
|
|
3558
|
+
const pw = writes.get(path28);
|
|
3045
3559
|
const df = now2 - pw.lastChange;
|
|
3046
3560
|
if (df >= threshold) {
|
|
3047
|
-
writes.delete(
|
|
3561
|
+
writes.delete(path28);
|
|
3048
3562
|
awfEmit(void 0, curStat);
|
|
3049
3563
|
} else {
|
|
3050
3564
|
timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
|
|
3051
3565
|
}
|
|
3052
3566
|
});
|
|
3053
3567
|
}
|
|
3054
|
-
if (!writes.has(
|
|
3055
|
-
writes.set(
|
|
3568
|
+
if (!writes.has(path28)) {
|
|
3569
|
+
writes.set(path28, {
|
|
3056
3570
|
lastChange: now,
|
|
3057
3571
|
cancelWait: () => {
|
|
3058
|
-
writes.delete(
|
|
3572
|
+
writes.delete(path28);
|
|
3059
3573
|
clearTimeout(timeoutHandler);
|
|
3060
3574
|
return event;
|
|
3061
3575
|
}
|
|
@@ -3066,8 +3580,8 @@ var FSWatcher = class extends import_events.EventEmitter {
|
|
|
3066
3580
|
/**
|
|
3067
3581
|
* Determines whether user has asked to ignore this path.
|
|
3068
3582
|
*/
|
|
3069
|
-
_isIgnored(
|
|
3070
|
-
if (this.options.atomic && DOT_RE.test(
|
|
3583
|
+
_isIgnored(path28, stats) {
|
|
3584
|
+
if (this.options.atomic && DOT_RE.test(path28))
|
|
3071
3585
|
return true;
|
|
3072
3586
|
if (!this._userIgnored) {
|
|
3073
3587
|
const { cwd } = this.options;
|
|
@@ -3077,17 +3591,17 @@ var FSWatcher = class extends import_events.EventEmitter {
|
|
|
3077
3591
|
const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
|
|
3078
3592
|
this._userIgnored = anymatch(list, void 0);
|
|
3079
3593
|
}
|
|
3080
|
-
return this._userIgnored(
|
|
3594
|
+
return this._userIgnored(path28, stats);
|
|
3081
3595
|
}
|
|
3082
|
-
_isntIgnored(
|
|
3083
|
-
return !this._isIgnored(
|
|
3596
|
+
_isntIgnored(path28, stat4) {
|
|
3597
|
+
return !this._isIgnored(path28, stat4);
|
|
3084
3598
|
}
|
|
3085
3599
|
/**
|
|
3086
3600
|
* Provides a set of common helpers and properties relating to symlink handling.
|
|
3087
3601
|
* @param path file or directory pattern being watched
|
|
3088
3602
|
*/
|
|
3089
|
-
_getWatchHelpers(
|
|
3090
|
-
return new WatchHelper(
|
|
3603
|
+
_getWatchHelpers(path28) {
|
|
3604
|
+
return new WatchHelper(path28, this.options.followSymlinks, this);
|
|
3091
3605
|
}
|
|
3092
3606
|
// Directory helpers
|
|
3093
3607
|
// -----------------
|
|
@@ -3119,63 +3633,63 @@ var FSWatcher = class extends import_events.EventEmitter {
|
|
|
3119
3633
|
* @param item base path of item/directory
|
|
3120
3634
|
*/
|
|
3121
3635
|
_remove(directory, item, isDirectory) {
|
|
3122
|
-
const
|
|
3123
|
-
const fullPath = sysPath2.resolve(
|
|
3124
|
-
isDirectory = isDirectory != null ? isDirectory : this._watched.has(
|
|
3125
|
-
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))
|
|
3126
3640
|
return;
|
|
3127
3641
|
if (!isDirectory && this._watched.size === 1) {
|
|
3128
3642
|
this.add(directory, item, true);
|
|
3129
3643
|
}
|
|
3130
|
-
const wp = this._getWatchedDir(
|
|
3644
|
+
const wp = this._getWatchedDir(path28);
|
|
3131
3645
|
const nestedDirectoryChildren = wp.getChildren();
|
|
3132
|
-
nestedDirectoryChildren.forEach((nested) => this._remove(
|
|
3646
|
+
nestedDirectoryChildren.forEach((nested) => this._remove(path28, nested));
|
|
3133
3647
|
const parent = this._getWatchedDir(directory);
|
|
3134
3648
|
const wasTracked = parent.has(item);
|
|
3135
3649
|
parent.remove(item);
|
|
3136
3650
|
if (this._symlinkPaths.has(fullPath)) {
|
|
3137
3651
|
this._symlinkPaths.delete(fullPath);
|
|
3138
3652
|
}
|
|
3139
|
-
let relPath =
|
|
3653
|
+
let relPath = path28;
|
|
3140
3654
|
if (this.options.cwd)
|
|
3141
|
-
relPath = sysPath2.relative(this.options.cwd,
|
|
3655
|
+
relPath = sysPath2.relative(this.options.cwd, path28);
|
|
3142
3656
|
if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
|
|
3143
3657
|
const event = this._pendingWrites.get(relPath).cancelWait();
|
|
3144
3658
|
if (event === EVENTS.ADD)
|
|
3145
3659
|
return;
|
|
3146
3660
|
}
|
|
3147
|
-
this._watched.delete(
|
|
3661
|
+
this._watched.delete(path28);
|
|
3148
3662
|
this._watched.delete(fullPath);
|
|
3149
3663
|
const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
|
|
3150
|
-
if (wasTracked && !this._isIgnored(
|
|
3151
|
-
this._emit(eventName,
|
|
3152
|
-
this._closePath(
|
|
3664
|
+
if (wasTracked && !this._isIgnored(path28))
|
|
3665
|
+
this._emit(eventName, path28);
|
|
3666
|
+
this._closePath(path28);
|
|
3153
3667
|
}
|
|
3154
3668
|
/**
|
|
3155
3669
|
* Closes all watchers for a path
|
|
3156
3670
|
*/
|
|
3157
|
-
_closePath(
|
|
3158
|
-
this._closeFile(
|
|
3159
|
-
const dir = sysPath2.dirname(
|
|
3160
|
-
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));
|
|
3161
3675
|
}
|
|
3162
3676
|
/**
|
|
3163
3677
|
* Closes only file-specific watchers
|
|
3164
3678
|
*/
|
|
3165
|
-
_closeFile(
|
|
3166
|
-
const closers = this._closers.get(
|
|
3679
|
+
_closeFile(path28) {
|
|
3680
|
+
const closers = this._closers.get(path28);
|
|
3167
3681
|
if (!closers)
|
|
3168
3682
|
return;
|
|
3169
3683
|
closers.forEach((closer) => closer());
|
|
3170
|
-
this._closers.delete(
|
|
3684
|
+
this._closers.delete(path28);
|
|
3171
3685
|
}
|
|
3172
|
-
_addPathCloser(
|
|
3686
|
+
_addPathCloser(path28, closer) {
|
|
3173
3687
|
if (!closer)
|
|
3174
3688
|
return;
|
|
3175
|
-
let list = this._closers.get(
|
|
3689
|
+
let list = this._closers.get(path28);
|
|
3176
3690
|
if (!list) {
|
|
3177
3691
|
list = [];
|
|
3178
|
-
this._closers.set(
|
|
3692
|
+
this._closers.set(path28, list);
|
|
3179
3693
|
}
|
|
3180
3694
|
list.push(closer);
|
|
3181
3695
|
}
|
|
@@ -3210,54 +3724,132 @@ init_globals();
|
|
|
3210
3724
|
function setupHotReload({
|
|
3211
3725
|
app,
|
|
3212
3726
|
appDir,
|
|
3727
|
+
projectRoot,
|
|
3213
3728
|
route = "/__fw/hot",
|
|
3214
3729
|
waitForBuild,
|
|
3215
3730
|
onFileChange
|
|
3216
3731
|
}) {
|
|
3217
3732
|
const clients = /* @__PURE__ */ new Set();
|
|
3218
3733
|
app.get(route, (req, res) => {
|
|
3219
|
-
res.setHeader("Content-Type", "text/event-stream");
|
|
3220
|
-
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");
|
|
3221
3736
|
res.setHeader("Connection", "keep-alive");
|
|
3222
|
-
res.
|
|
3223
|
-
res.
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
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
|
+
}
|
|
3227
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
|
+
}
|
|
3228
3756
|
req.on("close", () => {
|
|
3229
3757
|
clients.delete(res);
|
|
3230
3758
|
});
|
|
3759
|
+
req.on("aborted", () => {
|
|
3760
|
+
clients.delete(res);
|
|
3761
|
+
});
|
|
3231
3762
|
});
|
|
3232
|
-
|
|
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, {
|
|
3233
3766
|
ignoreInitial: true,
|
|
3234
|
-
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
|
+
}
|
|
3235
3800
|
});
|
|
3801
|
+
let broadcastTimeout = null;
|
|
3802
|
+
const BROADCAST_DEBOUNCE_MS = 300;
|
|
3236
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
|
+
}
|
|
3237
3808
|
const rel = import_path16.default.relative(appDir, filePath);
|
|
3238
3809
|
console.log(`[hot-reload] ${reason}: ${rel}`);
|
|
3239
|
-
if (
|
|
3240
|
-
|
|
3241
|
-
await onFileChange(filePath);
|
|
3242
|
-
} catch (error) {
|
|
3243
|
-
console.warn("[hot-reload] Error in onFileChange callback:", error);
|
|
3244
|
-
}
|
|
3810
|
+
if (broadcastTimeout) {
|
|
3811
|
+
clearTimeout(broadcastTimeout);
|
|
3245
3812
|
}
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
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
|
+
}
|
|
3253
3820
|
}
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
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
|
|
3257
3831
|
data: reload:${rel}
|
|
3258
3832
|
|
|
3259
|
-
|
|
3260
|
-
|
|
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);
|
|
3261
3853
|
}
|
|
3262
3854
|
watcher.on("add", (filePath) => broadcastReload("add", filePath)).on("change", (filePath) => broadcastReload("change", filePath)).on("unlink", (filePath) => broadcastReload("unlink", filePath));
|
|
3263
3855
|
}
|
|
@@ -3333,6 +3925,121 @@ function deepMerge(target, source) {
|
|
|
3333
3925
|
}
|
|
3334
3926
|
return result;
|
|
3335
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
|
+
}
|
|
3336
4043
|
function loadConfig(projectRoot) {
|
|
3337
4044
|
const configFiles = [
|
|
3338
4045
|
import_path18.default.join(projectRoot, "loly.config.ts"),
|
|
@@ -3340,6 +4047,7 @@ function loadConfig(projectRoot) {
|
|
|
3340
4047
|
import_path18.default.join(projectRoot, "loly.config.json")
|
|
3341
4048
|
];
|
|
3342
4049
|
let userConfig = {};
|
|
4050
|
+
let loadedConfigFile = null;
|
|
3343
4051
|
for (const configFile of configFiles) {
|
|
3344
4052
|
if (import_fs16.default.existsSync(configFile)) {
|
|
3345
4053
|
try {
|
|
@@ -3353,16 +4061,31 @@ function loadConfig(projectRoot) {
|
|
|
3353
4061
|
const mod = require(configFile);
|
|
3354
4062
|
userConfig = typeof mod.default === "function" ? mod.default(process.env.NODE_ENV) : mod.default || mod.config || mod;
|
|
3355
4063
|
}
|
|
4064
|
+
loadedConfigFile = import_path18.default.relative(projectRoot, configFile);
|
|
3356
4065
|
break;
|
|
3357
4066
|
} catch (error) {
|
|
3358
|
-
|
|
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
|
+
);
|
|
3359
4073
|
}
|
|
3360
4074
|
}
|
|
3361
4075
|
}
|
|
3362
4076
|
const config = deepMerge(DEFAULT_CONFIG, userConfig);
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
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;
|
|
3366
4089
|
}
|
|
3367
4090
|
return config;
|
|
3368
4091
|
}
|
|
@@ -3379,14 +4102,14 @@ function getStaticDir(projectRoot, config) {
|
|
|
3379
4102
|
// modules/server/setup.ts
|
|
3380
4103
|
function setupServer(app, options) {
|
|
3381
4104
|
const { projectRoot, appDir, isDev, config } = options;
|
|
3382
|
-
const routeLoader = isDev ? new FilesystemRouteLoader(appDir) : new ManifestRouteLoader(projectRoot);
|
|
4105
|
+
const routeLoader = isDev ? new FilesystemRouteLoader(appDir, projectRoot) : new ManifestRouteLoader(projectRoot);
|
|
3383
4106
|
if (isDev) {
|
|
3384
4107
|
let getRoutes2 = function() {
|
|
3385
4108
|
clearAppRequireCache(appDir);
|
|
3386
|
-
|
|
4109
|
+
sharedLoader.invalidateCache();
|
|
3387
4110
|
return {
|
|
3388
|
-
routes:
|
|
3389
|
-
apiRoutes:
|
|
4111
|
+
routes: sharedLoader.loadRoutes(),
|
|
4112
|
+
apiRoutes: sharedLoader.loadApiRoutes()
|
|
3390
4113
|
};
|
|
3391
4114
|
};
|
|
3392
4115
|
var getRoutes = getRoutes2;
|
|
@@ -3400,19 +4123,20 @@ function setupServer(app, options) {
|
|
|
3400
4123
|
console.log(`[hot-reload] Cleared require cache for: ${rel}`);
|
|
3401
4124
|
}
|
|
3402
4125
|
if (isPageFile) {
|
|
3403
|
-
const loader = new FilesystemRouteLoader(appDir);
|
|
4126
|
+
const loader = new FilesystemRouteLoader(appDir, projectRoot);
|
|
3404
4127
|
const newRoutes = loader.loadRoutes();
|
|
3405
4128
|
writeClientRoutesManifest(newRoutes, projectRoot);
|
|
3406
4129
|
console.log("[hot-reload] Client routes manifest reloaded");
|
|
3407
4130
|
}
|
|
3408
4131
|
};
|
|
3409
|
-
setupHotReload({ app, appDir, waitForBuild, onFileChange });
|
|
4132
|
+
setupHotReload({ app, appDir, projectRoot, waitForBuild, onFileChange });
|
|
3410
4133
|
app.use("/static", import_express.default.static(outDir));
|
|
3411
4134
|
const routes = routeLoader.loadRoutes();
|
|
3412
4135
|
const wssRoutes = routeLoader.loadWssRoutes();
|
|
3413
4136
|
const notFoundPage = routeLoader.loadNotFoundRoute();
|
|
3414
4137
|
const errorPage = routeLoader.loadErrorRoute();
|
|
3415
4138
|
writeClientRoutesManifest(routes, projectRoot);
|
|
4139
|
+
const sharedLoader = new FilesystemRouteLoader(appDir, projectRoot);
|
|
3416
4140
|
return {
|
|
3417
4141
|
routes,
|
|
3418
4142
|
wssRoutes,
|
|
@@ -3500,90 +4224,10 @@ function sanitizeQuery(query) {
|
|
|
3500
4224
|
|
|
3501
4225
|
// modules/server/middleware/rate-limit.ts
|
|
3502
4226
|
var import_express_rate_limit = __toESM(require("express-rate-limit"));
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
max = 100,
|
|
3508
|
-
// limit each IP to 100 requests per windowMs
|
|
3509
|
-
message = "Too many requests from this IP, please try again later.",
|
|
3510
|
-
standardHeaders = true,
|
|
3511
|
-
legacyHeaders = false,
|
|
3512
|
-
skipSuccessfulRequests = false,
|
|
3513
|
-
skipFailedRequests = false
|
|
3514
|
-
} = config;
|
|
3515
|
-
return (0, import_express_rate_limit.default)({
|
|
3516
|
-
windowMs,
|
|
3517
|
-
max,
|
|
3518
|
-
message: {
|
|
3519
|
-
error: message
|
|
3520
|
-
},
|
|
3521
|
-
standardHeaders,
|
|
3522
|
-
legacyHeaders,
|
|
3523
|
-
skipSuccessfulRequests,
|
|
3524
|
-
skipFailedRequests
|
|
3525
|
-
});
|
|
3526
|
-
}
|
|
3527
|
-
var defaultRateLimiter = createRateLimiter({
|
|
3528
|
-
windowMs: 15 * 60 * 1e3,
|
|
3529
|
-
// 15 minutes
|
|
3530
|
-
max: 100,
|
|
3531
|
-
message: "Too many requests from this IP, please try again later."
|
|
3532
|
-
});
|
|
3533
|
-
var strictRateLimiter = createRateLimiter({
|
|
3534
|
-
windowMs: 15 * 60 * 1e3,
|
|
3535
|
-
// 15 minutes
|
|
3536
|
-
max: 5,
|
|
3537
|
-
message: "Too many authentication attempts, please try again later."
|
|
3538
|
-
});
|
|
3539
|
-
var lenientRateLimiter = createRateLimiter({
|
|
3540
|
-
windowMs: 15 * 60 * 1e3,
|
|
3541
|
-
// 15 minutes
|
|
3542
|
-
max: 200,
|
|
3543
|
-
message: "Too many requests from this IP, please try again later."
|
|
3544
|
-
});
|
|
3545
|
-
|
|
3546
|
-
// modules/server/middleware/auto-rate-limit.ts
|
|
3547
|
-
function matchesStrictPattern(path25, patterns) {
|
|
3548
|
-
for (const pattern of patterns) {
|
|
3549
|
-
const regexPattern = pattern.replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*").replace(/\//g, "\\/");
|
|
3550
|
-
const regex = new RegExp(`^${regexPattern}$`);
|
|
3551
|
-
if (regex.test(path25)) {
|
|
3552
|
-
return true;
|
|
3553
|
-
}
|
|
3554
|
-
}
|
|
3555
|
-
return false;
|
|
3556
|
-
}
|
|
3557
|
-
function isRateLimiter(mw) {
|
|
3558
|
-
if (!mw) return false;
|
|
3559
|
-
if (mw === strictRateLimiter || mw === defaultRateLimiter || mw === lenientRateLimiter) {
|
|
3560
|
-
return true;
|
|
3561
|
-
}
|
|
3562
|
-
if (typeof mw === "function" && mw.name && mw.name.includes("rateLimit")) {
|
|
3563
|
-
return true;
|
|
3564
|
-
}
|
|
3565
|
-
if (mw && typeof mw === "function" && mw.skip || mw.resetKey) {
|
|
3566
|
-
return true;
|
|
3567
|
-
}
|
|
3568
|
-
return false;
|
|
3569
|
-
}
|
|
3570
|
-
function getAutoRateLimiter(route, strictPatterns = []) {
|
|
3571
|
-
const hasRateLimiter = route.middlewares?.some(isRateLimiter) || Object.values(route.methodMiddlewares || {}).some(
|
|
3572
|
-
(mws) => mws?.some(isRateLimiter)
|
|
3573
|
-
);
|
|
3574
|
-
if (hasRateLimiter) {
|
|
3575
|
-
return null;
|
|
3576
|
-
}
|
|
3577
|
-
if (strictPatterns.length > 0 && matchesStrictPattern(route.pattern, strictPatterns)) {
|
|
3578
|
-
console.log(`[rate-limit] Applying strict rate limiter to route: ${route.pattern}`);
|
|
3579
|
-
return strictRateLimiter;
|
|
3580
|
-
}
|
|
3581
|
-
return null;
|
|
3582
|
-
}
|
|
3583
|
-
|
|
3584
|
-
// modules/logger/index.ts
|
|
3585
|
-
var import_pino = __toESM(require("pino"));
|
|
3586
|
-
function createLogger(options = {}) {
|
|
4227
|
+
|
|
4228
|
+
// modules/logger/index.ts
|
|
4229
|
+
var import_pino = __toESM(require("pino"));
|
|
4230
|
+
function createLogger(options = {}) {
|
|
3587
4231
|
const {
|
|
3588
4232
|
level = process.env.LOG_LEVEL || (process.env.NODE_ENV === "development" ? "debug" : "info"),
|
|
3589
4233
|
enabled = process.env.LOG_ENABLED !== "false",
|
|
@@ -3639,8 +4283,8 @@ function resetLogger() {
|
|
|
3639
4283
|
loggerInstance = null;
|
|
3640
4284
|
}
|
|
3641
4285
|
var Logger = class _Logger {
|
|
3642
|
-
constructor(
|
|
3643
|
-
this.pino =
|
|
4286
|
+
constructor(logger5, context = {}) {
|
|
4287
|
+
this.pino = logger5 || getLogger();
|
|
3644
4288
|
this.context = context;
|
|
3645
4289
|
}
|
|
3646
4290
|
/**
|
|
@@ -3717,12 +4361,12 @@ var DEFAULT_IGNORED_PATHS = [
|
|
|
3717
4361
|
/^\/sockjs-node/
|
|
3718
4362
|
// Hot reload websocket
|
|
3719
4363
|
];
|
|
3720
|
-
function shouldIgnorePath(
|
|
4364
|
+
function shouldIgnorePath(path28, ignoredPaths) {
|
|
3721
4365
|
return ignoredPaths.some((pattern) => {
|
|
3722
4366
|
if (typeof pattern === "string") {
|
|
3723
|
-
return
|
|
4367
|
+
return path28 === pattern || path28.startsWith(pattern);
|
|
3724
4368
|
}
|
|
3725
|
-
return pattern.test(
|
|
4369
|
+
return pattern.test(path28);
|
|
3726
4370
|
});
|
|
3727
4371
|
}
|
|
3728
4372
|
function requestLoggerMiddleware(options = {}) {
|
|
@@ -3773,6 +4417,151 @@ function getRequestLogger(req) {
|
|
|
3773
4417
|
return req.logger || logger.child({ requestId: "unknown" });
|
|
3774
4418
|
}
|
|
3775
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
|
+
|
|
3776
4565
|
// modules/server/handlers/api.ts
|
|
3777
4566
|
async function handleApiRequest(options) {
|
|
3778
4567
|
const { apiRoutes, urlPath, req, res, env = "dev" } = options;
|
|
@@ -3804,7 +4593,8 @@ async function handleApiRequest(options) {
|
|
|
3804
4593
|
try {
|
|
3805
4594
|
const autoRateLimiter = getAutoRateLimiter(
|
|
3806
4595
|
route,
|
|
3807
|
-
options.strictRateLimitPatterns
|
|
4596
|
+
options.strictRateLimitPatterns,
|
|
4597
|
+
options.rateLimitConfig
|
|
3808
4598
|
);
|
|
3809
4599
|
const reqLogger = getRequestLogger(req);
|
|
3810
4600
|
if (autoRateLimiter) {
|
|
@@ -3816,26 +4606,46 @@ async function handleApiRequest(options) {
|
|
|
3816
4606
|
const globalMws = route.middlewares ?? [];
|
|
3817
4607
|
const perMethodMws = route.methodMiddlewares?.[method] ?? [];
|
|
3818
4608
|
const chain = autoRateLimiter ? [autoRateLimiter, ...globalMws, ...perMethodMws] : [...globalMws, ...perMethodMws];
|
|
3819
|
-
for (
|
|
3820
|
-
const
|
|
3821
|
-
if (
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
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);
|
|
3831
4635
|
}
|
|
3832
|
-
}
|
|
3833
|
-
|
|
3834
|
-
|
|
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"
|
|
3835
4647
|
});
|
|
3836
|
-
|
|
3837
|
-
await Promise.resolve(mw(ctx, async () => {
|
|
3838
|
-
}));
|
|
4648
|
+
throw error;
|
|
3839
4649
|
}
|
|
3840
4650
|
if (res.headersSent) {
|
|
3841
4651
|
return;
|
|
@@ -3886,34 +4696,260 @@ function createDocumentTree(options) {
|
|
|
3886
4696
|
titleFallback,
|
|
3887
4697
|
descriptionFallback,
|
|
3888
4698
|
chunkHref,
|
|
4699
|
+
entrypointFiles = [],
|
|
3889
4700
|
theme,
|
|
3890
4701
|
clientJsPath = "/static/client.js",
|
|
3891
4702
|
clientCssPath = "/static/client.css",
|
|
3892
|
-
nonce
|
|
4703
|
+
nonce,
|
|
4704
|
+
includeInlineScripts = true
|
|
4705
|
+
// Default true - scripts inline in body for both SSR and SSG
|
|
3893
4706
|
} = options;
|
|
3894
|
-
const metaObj = meta ??
|
|
3895
|
-
const title = metaObj
|
|
3896
|
-
const lang = metaObj
|
|
3897
|
-
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";
|
|
3898
4711
|
const extraMetaTags = [];
|
|
4712
|
+
const linkTags = [];
|
|
3899
4713
|
if (description) {
|
|
3900
4714
|
extraMetaTags.push(
|
|
3901
4715
|
import_react.default.createElement("meta", {
|
|
4716
|
+
key: "meta-description",
|
|
3902
4717
|
name: "description",
|
|
3903
4718
|
content: description
|
|
3904
4719
|
})
|
|
3905
4720
|
);
|
|
3906
4721
|
}
|
|
3907
|
-
if (
|
|
3908
|
-
|
|
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) {
|
|
4779
|
+
extraMetaTags.push(
|
|
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) => {
|
|
3909
4929
|
extraMetaTags.push(
|
|
3910
4930
|
import_react.default.createElement("meta", {
|
|
4931
|
+
key: `meta-custom-${index}`,
|
|
3911
4932
|
name: tag.name,
|
|
3912
4933
|
property: tag.property,
|
|
4934
|
+
httpEquiv: tag.httpEquiv,
|
|
3913
4935
|
content: tag.content
|
|
3914
4936
|
})
|
|
3915
4937
|
);
|
|
3916
|
-
}
|
|
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
|
+
});
|
|
3917
4953
|
}
|
|
3918
4954
|
const serialized = JSON.stringify({
|
|
3919
4955
|
...initialData,
|
|
@@ -3922,6 +4958,27 @@ function createDocumentTree(options) {
|
|
|
3922
4958
|
const routerSerialized = JSON.stringify({
|
|
3923
4959
|
...routerData
|
|
3924
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
|
+
);
|
|
4981
|
+
}
|
|
3925
4982
|
const documentTree = import_react.default.createElement(
|
|
3926
4983
|
"html",
|
|
3927
4984
|
{ lang },
|
|
@@ -3930,13 +4987,25 @@ function createDocumentTree(options) {
|
|
|
3930
4987
|
null,
|
|
3931
4988
|
import_react.default.createElement("meta", { charSet: "utf-8" }),
|
|
3932
4989
|
import_react.default.createElement("title", null, title),
|
|
4990
|
+
// Viewport: use custom if provided, otherwise default
|
|
3933
4991
|
import_react.default.createElement("meta", {
|
|
3934
4992
|
name: "viewport",
|
|
3935
|
-
content: "width=device-width, initial-scale=1"
|
|
4993
|
+
content: metaObj?.viewport ?? "width=device-width, initial-scale=1"
|
|
3936
4994
|
}),
|
|
3937
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
|
|
3938
5006
|
chunkHref && import_react.default.createElement("link", {
|
|
3939
|
-
|
|
5007
|
+
key: `preload-${chunkHref}`,
|
|
5008
|
+
rel: "preload",
|
|
3940
5009
|
href: chunkHref,
|
|
3941
5010
|
as: "script"
|
|
3942
5011
|
}),
|
|
@@ -3949,33 +5018,34 @@ function createDocumentTree(options) {
|
|
|
3949
5018
|
rel: "stylesheet",
|
|
3950
5019
|
href: clientCssPath
|
|
3951
5020
|
}),
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
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
|
+
]
|
|
3956
5038
|
),
|
|
3957
5039
|
import_react.default.createElement(
|
|
3958
5040
|
"body",
|
|
3959
5041
|
{
|
|
3960
5042
|
style: { margin: 0 },
|
|
3961
|
-
className: [initialData.className, theme].filter(Boolean).join(" "),
|
|
5043
|
+
className: [initialData.className || "", theme].filter(Boolean).join(" "),
|
|
3962
5044
|
suppressHydrationWarning: true
|
|
3963
5045
|
// Allow theme class to differ between server and client initially
|
|
3964
5046
|
},
|
|
3965
|
-
|
|
3966
|
-
)
|
|
3967
|
-
import_react.default.createElement("script", {
|
|
3968
|
-
nonce,
|
|
3969
|
-
dangerouslySetInnerHTML: {
|
|
3970
|
-
__html: `window.${WINDOW_DATA_KEY} = ${serialized};`
|
|
3971
|
-
}
|
|
3972
|
-
}),
|
|
3973
|
-
import_react.default.createElement("script", {
|
|
3974
|
-
nonce,
|
|
3975
|
-
dangerouslySetInnerHTML: {
|
|
3976
|
-
__html: `window.${ROUTER_DATA_KEY} = ${routerSerialized};`
|
|
3977
|
-
}
|
|
3978
|
-
})
|
|
5047
|
+
...bodyChildren
|
|
5048
|
+
)
|
|
3979
5049
|
);
|
|
3980
5050
|
return documentTree;
|
|
3981
5051
|
}
|
|
@@ -3991,7 +5061,7 @@ function buildInitialData(urlPath, params, loaderResult) {
|
|
|
3991
5061
|
params,
|
|
3992
5062
|
props,
|
|
3993
5063
|
metadata: loaderResult.metadata ?? null,
|
|
3994
|
-
className: loaderResult.className
|
|
5064
|
+
className: loaderResult.className,
|
|
3995
5065
|
error: false,
|
|
3996
5066
|
notFound: false
|
|
3997
5067
|
};
|
|
@@ -4007,24 +5077,83 @@ var buildRouterData = (req) => {
|
|
|
4007
5077
|
};
|
|
4008
5078
|
|
|
4009
5079
|
// modules/server/handlers/middleware.ts
|
|
5080
|
+
var import_path20 = __toESM(require("path"));
|
|
4010
5081
|
async function runRouteMiddlewares(route, ctx) {
|
|
4011
|
-
for (
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
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
|
+
}
|
|
4016
5099
|
if (ctx.res.headersSent) {
|
|
4017
5100
|
return;
|
|
4018
5101
|
}
|
|
4019
5102
|
}
|
|
4020
5103
|
}
|
|
4021
5104
|
|
|
4022
|
-
// modules/server/handlers/
|
|
4023
|
-
|
|
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
|
+
}
|
|
5141
|
+
async function runRouteServerHook(route, ctx) {
|
|
4024
5142
|
if (!route.loader) {
|
|
4025
5143
|
return { props: {} };
|
|
4026
5144
|
}
|
|
4027
|
-
|
|
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
|
+
}
|
|
4028
5157
|
}
|
|
4029
5158
|
|
|
4030
5159
|
// modules/server/handlers/response.ts
|
|
@@ -4065,26 +5194,26 @@ function handleNotFound(res, urlPath) {
|
|
|
4065
5194
|
|
|
4066
5195
|
// modules/server/handlers/ssg.ts
|
|
4067
5196
|
var import_fs17 = __toESM(require("fs"));
|
|
4068
|
-
var
|
|
4069
|
-
var
|
|
5197
|
+
var import_path22 = __toESM(require("path"));
|
|
5198
|
+
var logger3 = createModuleLogger("ssg");
|
|
4070
5199
|
function getSsgDirForPath(baseDir, urlPath) {
|
|
4071
5200
|
const clean = urlPath === "/" ? "" : urlPath.replace(/^\/+/, "");
|
|
4072
|
-
return
|
|
5201
|
+
return import_path22.default.join(baseDir, clean);
|
|
4073
5202
|
}
|
|
4074
5203
|
function getSsgHtmlPath(baseDir, urlPath) {
|
|
4075
5204
|
const dir = getSsgDirForPath(baseDir, urlPath);
|
|
4076
|
-
return
|
|
5205
|
+
return import_path22.default.join(dir, "index.html");
|
|
4077
5206
|
}
|
|
4078
5207
|
function getSsgDataPath(baseDir, urlPath) {
|
|
4079
5208
|
const dir = getSsgDirForPath(baseDir, urlPath);
|
|
4080
|
-
return
|
|
5209
|
+
return import_path22.default.join(dir, "data.json");
|
|
4081
5210
|
}
|
|
4082
5211
|
function tryServeSsgHtml(res, ssgOutDir, urlPath) {
|
|
4083
5212
|
const ssgHtmlPath = getSsgHtmlPath(ssgOutDir, urlPath);
|
|
4084
5213
|
if (!import_fs17.default.existsSync(ssgHtmlPath)) {
|
|
4085
5214
|
return false;
|
|
4086
5215
|
}
|
|
4087
|
-
|
|
5216
|
+
logger3.info("Serving SSG HTML", { urlPath, ssgHtmlPath });
|
|
4088
5217
|
res.setHeader(
|
|
4089
5218
|
"Content-Security-Policy",
|
|
4090
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';"
|
|
@@ -4106,13 +5235,43 @@ function tryServeSsgData(res, ssgOutDir, urlPath) {
|
|
|
4106
5235
|
res.status(200).end(raw);
|
|
4107
5236
|
return true;
|
|
4108
5237
|
} catch (err) {
|
|
4109
|
-
|
|
5238
|
+
logger3.error("Error reading SSG data", err, { urlPath, ssgDataPath });
|
|
4110
5239
|
return false;
|
|
4111
5240
|
}
|
|
4112
5241
|
}
|
|
4113
5242
|
|
|
4114
5243
|
// modules/server/handlers/pages.ts
|
|
4115
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
|
+
}
|
|
4116
5275
|
function isDataRequest(req) {
|
|
4117
5276
|
return req.query && req.query.__fw_data === "1" || req.headers["x-fw-data"] === "1";
|
|
4118
5277
|
}
|
|
@@ -4177,22 +5336,59 @@ async function handlePageRequestInternal(options) {
|
|
|
4177
5336
|
pathname: urlPath,
|
|
4178
5337
|
locals: {}
|
|
4179
5338
|
};
|
|
4180
|
-
|
|
5339
|
+
const layoutProps2 = {};
|
|
5340
|
+
if (notFoundPage.layoutServerHooks && notFoundPage.layoutServerHooks.length > 0) {
|
|
5341
|
+
for (let i = 0; i < notFoundPage.layoutServerHooks.length; i++) {
|
|
5342
|
+
const layoutServerHook = notFoundPage.layoutServerHooks[i];
|
|
5343
|
+
if (layoutServerHook) {
|
|
5344
|
+
try {
|
|
5345
|
+
const layoutResult = await layoutServerHook(ctx2);
|
|
5346
|
+
if (layoutResult.props) {
|
|
5347
|
+
Object.assign(layoutProps2, layoutResult.props);
|
|
5348
|
+
}
|
|
5349
|
+
} catch (error) {
|
|
5350
|
+
const reqLogger2 = getRequestLogger(req);
|
|
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
|
|
5358
|
+
});
|
|
5359
|
+
}
|
|
5360
|
+
}
|
|
5361
|
+
}
|
|
5362
|
+
}
|
|
5363
|
+
let loaderResult2 = await runRouteServerHook(notFoundPage, ctx2);
|
|
4181
5364
|
if (!loaderResult2.theme) {
|
|
4182
5365
|
loaderResult2.theme = theme;
|
|
4183
5366
|
}
|
|
4184
|
-
const
|
|
5367
|
+
const combinedProps2 = {
|
|
5368
|
+
...layoutProps2,
|
|
5369
|
+
...loaderResult2.props || {}
|
|
5370
|
+
};
|
|
5371
|
+
const combinedLoaderResult2 = {
|
|
5372
|
+
...loaderResult2,
|
|
5373
|
+
props: combinedProps2
|
|
5374
|
+
};
|
|
5375
|
+
const initialData2 = buildInitialData(urlPath, {}, combinedLoaderResult2);
|
|
4185
5376
|
const appTree2 = buildAppTree(notFoundPage, {}, initialData2.props);
|
|
4186
5377
|
initialData2.notFound = true;
|
|
4187
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
|
+
}
|
|
4188
5383
|
const documentTree2 = createDocumentTree({
|
|
4189
5384
|
appTree: appTree2,
|
|
4190
5385
|
initialData: initialData2,
|
|
4191
5386
|
routerData,
|
|
4192
|
-
meta:
|
|
5387
|
+
meta: combinedLoaderResult2.metadata ?? null,
|
|
4193
5388
|
titleFallback: "Not found",
|
|
4194
5389
|
descriptionFallback: "Loly demo",
|
|
4195
5390
|
chunkHref: null,
|
|
5391
|
+
entrypointFiles: entrypointFiles2,
|
|
4196
5392
|
theme,
|
|
4197
5393
|
clientJsPath,
|
|
4198
5394
|
clientCssPath,
|
|
@@ -4208,8 +5404,8 @@ async function handlePageRequestInternal(options) {
|
|
|
4208
5404
|
},
|
|
4209
5405
|
onShellError(err) {
|
|
4210
5406
|
didError2 = true;
|
|
4211
|
-
const
|
|
4212
|
-
|
|
5407
|
+
const reqLogger2 = getRequestLogger(req);
|
|
5408
|
+
reqLogger2.error("SSR shell error", err, { route: "not-found" });
|
|
4213
5409
|
if (!res.headersSent && errorPage) {
|
|
4214
5410
|
renderErrorPageWithStream(errorPage, req, res, err, routeChunks, theme, projectRoot, env);
|
|
4215
5411
|
} else if (!res.headersSent) {
|
|
@@ -4221,8 +5417,8 @@ async function handlePageRequestInternal(options) {
|
|
|
4221
5417
|
},
|
|
4222
5418
|
onError(err) {
|
|
4223
5419
|
didError2 = true;
|
|
4224
|
-
const
|
|
4225
|
-
|
|
5420
|
+
const reqLogger2 = getRequestLogger(req);
|
|
5421
|
+
reqLogger2.error("SSR error", err, { route: "not-found" });
|
|
4226
5422
|
}
|
|
4227
5423
|
});
|
|
4228
5424
|
req.on("close", () => abort2());
|
|
@@ -4244,17 +5440,62 @@ async function handlePageRequestInternal(options) {
|
|
|
4244
5440
|
if (res.headersSent) {
|
|
4245
5441
|
return;
|
|
4246
5442
|
}
|
|
5443
|
+
const layoutProps = {};
|
|
5444
|
+
const layoutMetadata = [];
|
|
5445
|
+
const reqLogger = getRequestLogger(req);
|
|
5446
|
+
if (route.layoutServerHooks && route.layoutServerHooks.length > 0) {
|
|
5447
|
+
for (let i = 0; i < route.layoutServerHooks.length; i++) {
|
|
5448
|
+
const layoutServerHook = route.layoutServerHooks[i];
|
|
5449
|
+
if (layoutServerHook) {
|
|
5450
|
+
try {
|
|
5451
|
+
const layoutResult = await layoutServerHook(ctx);
|
|
5452
|
+
if (layoutResult.props) {
|
|
5453
|
+
Object.assign(layoutProps, layoutResult.props);
|
|
5454
|
+
}
|
|
5455
|
+
if (layoutResult.metadata) {
|
|
5456
|
+
layoutMetadata.push(layoutResult.metadata);
|
|
5457
|
+
}
|
|
5458
|
+
} catch (error) {
|
|
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"
|
|
5468
|
+
});
|
|
5469
|
+
}
|
|
5470
|
+
}
|
|
5471
|
+
}
|
|
5472
|
+
}
|
|
4247
5473
|
let loaderResult;
|
|
4248
5474
|
try {
|
|
4249
|
-
loaderResult = await
|
|
5475
|
+
loaderResult = await runRouteServerHook(route, ctx);
|
|
4250
5476
|
if (!loaderResult.theme) {
|
|
4251
5477
|
loaderResult.theme = theme;
|
|
4252
5478
|
}
|
|
4253
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
|
+
});
|
|
4254
5489
|
if (isDataReq) {
|
|
4255
5490
|
res.statusCode = 500;
|
|
4256
5491
|
res.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
4257
|
-
|
|
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));
|
|
4258
5499
|
return;
|
|
4259
5500
|
} else {
|
|
4260
5501
|
if (errorPage) {
|
|
@@ -4265,8 +5506,28 @@ async function handlePageRequestInternal(options) {
|
|
|
4265
5506
|
}
|
|
4266
5507
|
}
|
|
4267
5508
|
}
|
|
5509
|
+
const combinedProps = {
|
|
5510
|
+
...layoutProps,
|
|
5511
|
+
// Props from layouts (stable)
|
|
5512
|
+
...loaderResult.props || {}
|
|
5513
|
+
// Props from page (overrides layout)
|
|
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
|
+
}
|
|
5524
|
+
const combinedLoaderResult = {
|
|
5525
|
+
...loaderResult,
|
|
5526
|
+
props: combinedProps,
|
|
5527
|
+
metadata: combinedMetadata
|
|
5528
|
+
};
|
|
4268
5529
|
if (isDataReq) {
|
|
4269
|
-
handleDataResponse(res,
|
|
5530
|
+
handleDataResponse(res, combinedLoaderResult, theme);
|
|
4270
5531
|
return;
|
|
4271
5532
|
}
|
|
4272
5533
|
if (loaderResult.redirect) {
|
|
@@ -4281,7 +5542,7 @@ async function handlePageRequestInternal(options) {
|
|
|
4281
5542
|
}
|
|
4282
5543
|
return;
|
|
4283
5544
|
}
|
|
4284
|
-
const initialData = buildInitialData(urlPath, params,
|
|
5545
|
+
const initialData = buildInitialData(urlPath, params, combinedLoaderResult);
|
|
4285
5546
|
const appTree = buildAppTree(route, params, initialData.props);
|
|
4286
5547
|
const chunkName = routeChunks[route.pattern];
|
|
4287
5548
|
let chunkHref = null;
|
|
@@ -4293,14 +5554,19 @@ async function handlePageRequestInternal(options) {
|
|
|
4293
5554
|
}
|
|
4294
5555
|
}
|
|
4295
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
|
+
}
|
|
4296
5561
|
const documentTree = createDocumentTree({
|
|
4297
5562
|
appTree,
|
|
4298
5563
|
initialData,
|
|
4299
5564
|
routerData,
|
|
4300
|
-
meta:
|
|
5565
|
+
meta: combinedLoaderResult.metadata,
|
|
4301
5566
|
titleFallback: "Loly framework",
|
|
4302
5567
|
descriptionFallback: "Loly demo",
|
|
4303
5568
|
chunkHref,
|
|
5569
|
+
entrypointFiles,
|
|
4304
5570
|
theme,
|
|
4305
5571
|
clientJsPath,
|
|
4306
5572
|
clientCssPath,
|
|
@@ -4318,8 +5584,17 @@ async function handlePageRequestInternal(options) {
|
|
|
4318
5584
|
},
|
|
4319
5585
|
onShellError(err) {
|
|
4320
5586
|
didError = true;
|
|
4321
|
-
const
|
|
4322
|
-
|
|
5587
|
+
const reqLogger2 = getRequestLogger(req);
|
|
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");
|
|
4323
5598
|
if (!res.headersSent && errorPage) {
|
|
4324
5599
|
renderErrorPageWithStream(errorPage, req, res, err, routeChunks, theme, projectRoot, env);
|
|
4325
5600
|
} else if (!res.headersSent) {
|
|
@@ -4331,8 +5606,12 @@ async function handlePageRequestInternal(options) {
|
|
|
4331
5606
|
},
|
|
4332
5607
|
onError(err) {
|
|
4333
5608
|
didError = true;
|
|
4334
|
-
const
|
|
4335
|
-
|
|
5609
|
+
const reqLogger2 = getRequestLogger(req);
|
|
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}`);
|
|
4336
5615
|
}
|
|
4337
5616
|
});
|
|
4338
5617
|
req.on("close", () => {
|
|
@@ -4349,11 +5628,43 @@ async function renderErrorPageWithStream(errorPage, req, res, error, routeChunks
|
|
|
4349
5628
|
pathname: req.path,
|
|
4350
5629
|
locals: { error }
|
|
4351
5630
|
};
|
|
4352
|
-
|
|
5631
|
+
const layoutProps = {};
|
|
5632
|
+
const reqLogger = getRequestLogger(req);
|
|
5633
|
+
if (errorPage.layoutServerHooks && errorPage.layoutServerHooks.length > 0) {
|
|
5634
|
+
for (let i = 0; i < errorPage.layoutServerHooks.length; i++) {
|
|
5635
|
+
const layoutServerHook = errorPage.layoutServerHooks[i];
|
|
5636
|
+
if (layoutServerHook) {
|
|
5637
|
+
try {
|
|
5638
|
+
const layoutResult = await layoutServerHook(ctx);
|
|
5639
|
+
if (layoutResult.props) {
|
|
5640
|
+
Object.assign(layoutProps, layoutResult.props);
|
|
5641
|
+
}
|
|
5642
|
+
} catch (err) {
|
|
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
|
|
5650
|
+
});
|
|
5651
|
+
}
|
|
5652
|
+
}
|
|
5653
|
+
}
|
|
5654
|
+
}
|
|
5655
|
+
let loaderResult = await runRouteServerHook(errorPage, ctx);
|
|
4353
5656
|
if (!loaderResult.theme && theme) {
|
|
4354
5657
|
loaderResult.theme = theme;
|
|
4355
5658
|
}
|
|
4356
|
-
const
|
|
5659
|
+
const combinedProps = {
|
|
5660
|
+
...layoutProps,
|
|
5661
|
+
...loaderResult.props || {}
|
|
5662
|
+
};
|
|
5663
|
+
const combinedLoaderResult = {
|
|
5664
|
+
...loaderResult,
|
|
5665
|
+
props: combinedProps
|
|
5666
|
+
};
|
|
5667
|
+
const initialData = buildInitialData(req.path, { error: String(error) }, combinedLoaderResult);
|
|
4357
5668
|
const routerData = buildRouterData(req);
|
|
4358
5669
|
initialData.error = true;
|
|
4359
5670
|
if (isDataReq) {
|
|
@@ -4363,8 +5674,8 @@ async function renderErrorPageWithStream(errorPage, req, res, error, routeChunks
|
|
|
4363
5674
|
error: true,
|
|
4364
5675
|
message: String(error),
|
|
4365
5676
|
props: initialData.props,
|
|
4366
|
-
metadata:
|
|
4367
|
-
theme:
|
|
5677
|
+
metadata: combinedLoaderResult.metadata ?? null,
|
|
5678
|
+
theme: combinedLoaderResult.theme ?? theme ?? null
|
|
4368
5679
|
}));
|
|
4369
5680
|
return;
|
|
4370
5681
|
}
|
|
@@ -4382,11 +5693,15 @@ async function renderErrorPageWithStream(errorPage, req, res, error, routeChunks
|
|
|
4382
5693
|
}
|
|
4383
5694
|
}
|
|
4384
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
|
+
}
|
|
4385
5700
|
const documentTree = createDocumentTree({
|
|
4386
5701
|
appTree,
|
|
4387
5702
|
initialData,
|
|
4388
5703
|
routerData,
|
|
4389
|
-
meta:
|
|
5704
|
+
meta: combinedLoaderResult.metadata ?? null,
|
|
4390
5705
|
titleFallback: "Error",
|
|
4391
5706
|
descriptionFallback: "An error occurred",
|
|
4392
5707
|
chunkHref,
|
|
@@ -4407,8 +5722,8 @@ async function renderErrorPageWithStream(errorPage, req, res, error, routeChunks
|
|
|
4407
5722
|
},
|
|
4408
5723
|
onShellError(err) {
|
|
4409
5724
|
didError = true;
|
|
4410
|
-
const
|
|
4411
|
-
|
|
5725
|
+
const reqLogger2 = getRequestLogger(req);
|
|
5726
|
+
reqLogger2.error("Error rendering error page", err, { type: "shellError" });
|
|
4412
5727
|
if (!res.headersSent) {
|
|
4413
5728
|
res.statusCode = 500;
|
|
4414
5729
|
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
@@ -4418,8 +5733,8 @@ async function renderErrorPageWithStream(errorPage, req, res, error, routeChunks
|
|
|
4418
5733
|
},
|
|
4419
5734
|
onError(err) {
|
|
4420
5735
|
didError = true;
|
|
4421
|
-
const
|
|
4422
|
-
|
|
5736
|
+
const reqLogger2 = getRequestLogger(req);
|
|
5737
|
+
reqLogger2.error("Error in error page", err);
|
|
4423
5738
|
}
|
|
4424
5739
|
});
|
|
4425
5740
|
req.on("close", () => {
|
|
@@ -4507,7 +5822,7 @@ async function getServerConfig(projectRoot) {
|
|
|
4507
5822
|
}
|
|
4508
5823
|
|
|
4509
5824
|
// modules/server/routes.ts
|
|
4510
|
-
var
|
|
5825
|
+
var import_path24 = __toESM(require("path"));
|
|
4511
5826
|
function setupRoutes(options) {
|
|
4512
5827
|
const {
|
|
4513
5828
|
app,
|
|
@@ -4522,21 +5837,23 @@ function setupRoutes(options) {
|
|
|
4522
5837
|
config
|
|
4523
5838
|
} = options;
|
|
4524
5839
|
const routeChunks = routeLoader.loadRouteChunks();
|
|
4525
|
-
const ssgOutDir =
|
|
4526
|
-
config ? getBuildDir(projectRoot, config) :
|
|
5840
|
+
const ssgOutDir = import_path24.default.join(
|
|
5841
|
+
config ? getBuildDir(projectRoot, config) : import_path24.default.join(projectRoot, BUILD_FOLDER_NAME),
|
|
4527
5842
|
"ssg"
|
|
4528
5843
|
);
|
|
4529
5844
|
app.all("/api/*", async (req, res) => {
|
|
4530
5845
|
const apiRoutes = isDev && getRoutes ? getRoutes().apiRoutes : initialApiRoutes;
|
|
4531
5846
|
const serverConfig = await getServerConfig(projectRoot);
|
|
4532
5847
|
const strictPatterns = serverConfig.rateLimit?.strictPatterns || [];
|
|
5848
|
+
const rateLimitConfig = serverConfig.rateLimit;
|
|
4533
5849
|
await handleApiRequest({
|
|
4534
5850
|
apiRoutes,
|
|
4535
5851
|
urlPath: req.path,
|
|
4536
5852
|
req,
|
|
4537
5853
|
res,
|
|
4538
5854
|
env: isDev ? "dev" : "prod",
|
|
4539
|
-
strictRateLimitPatterns: strictPatterns
|
|
5855
|
+
strictRateLimitPatterns: strictPatterns,
|
|
5856
|
+
rateLimitConfig
|
|
4540
5857
|
});
|
|
4541
5858
|
});
|
|
4542
5859
|
app.get("*", async (req, res) => {
|
|
@@ -4789,12 +6106,29 @@ var setupApplication = async ({
|
|
|
4789
6106
|
corsOptions.origin = process.env.NODE_ENV === "development";
|
|
4790
6107
|
}
|
|
4791
6108
|
app.use((0, import_cors.default)(corsOptions));
|
|
4792
|
-
if (rateLimit2
|
|
4793
|
-
const
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
|
|
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
|
+
}
|
|
4798
6132
|
}
|
|
4799
6133
|
app.use((0, import_cookie_parser.default)());
|
|
4800
6134
|
app.use(import_express2.default.json({ limit: bodyLimit }));
|
|
@@ -4809,22 +6143,31 @@ var setupApplication = async ({
|
|
|
4809
6143
|
|
|
4810
6144
|
// src/server.ts
|
|
4811
6145
|
var import_dotenv2 = __toESM(require("dotenv"));
|
|
4812
|
-
var envPath =
|
|
6146
|
+
var envPath = import_path25.default.join(process.cwd(), ".env");
|
|
4813
6147
|
if (import_fs18.default.existsSync(envPath)) {
|
|
4814
6148
|
import_dotenv2.default.config({ path: envPath });
|
|
4815
6149
|
} else {
|
|
4816
6150
|
import_dotenv2.default.config();
|
|
4817
6151
|
}
|
|
4818
|
-
var
|
|
6152
|
+
var logger4 = createModuleLogger("server");
|
|
4819
6153
|
async function startServer(options = {}) {
|
|
4820
6154
|
const isDev = options.isDev ?? process.env.NODE_ENV === "development";
|
|
4821
6155
|
const projectRoot = options.rootDir ?? process.cwd();
|
|
4822
|
-
|
|
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
|
+
}
|
|
4823
6166
|
const port = options.port ?? (process.env.PORT ? parseInt(process.env.PORT, 10) : void 0) ?? config.server.port;
|
|
4824
6167
|
const host = process.env.HOST ?? (!isDev ? "0.0.0.0" : void 0) ?? config.server.host;
|
|
4825
|
-
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"));
|
|
4826
6169
|
if (!isDev && !import_fs18.default.existsSync(appDir)) {
|
|
4827
|
-
|
|
6170
|
+
logger4.error("Compiled directory not found", void 0, {
|
|
4828
6171
|
buildDir: config.directories.build,
|
|
4829
6172
|
appDir,
|
|
4830
6173
|
environment: "production"
|
|
@@ -4841,7 +6184,7 @@ async function startServer(options = {}) {
|
|
|
4841
6184
|
isDev,
|
|
4842
6185
|
config
|
|
4843
6186
|
});
|
|
4844
|
-
const routeLoader = isDev ? new FilesystemRouteLoader(appDir) : new ManifestRouteLoader(projectRoot);
|
|
6187
|
+
const routeLoader = isDev ? new FilesystemRouteLoader(appDir, projectRoot) : new ManifestRouteLoader(projectRoot);
|
|
4845
6188
|
setupWssEvents({
|
|
4846
6189
|
httpServer,
|
|
4847
6190
|
wssRoutes
|
|
@@ -4893,10 +6236,10 @@ async function startProdServer(options = {}) {
|
|
|
4893
6236
|
}
|
|
4894
6237
|
|
|
4895
6238
|
// modules/build/ssg/builder.ts
|
|
4896
|
-
var
|
|
6239
|
+
var import_path29 = __toESM(require("path"));
|
|
4897
6240
|
|
|
4898
6241
|
// modules/build/ssg/path.ts
|
|
4899
|
-
var
|
|
6242
|
+
var import_path26 = __toESM(require("path"));
|
|
4900
6243
|
function buildPathFromPattern(pattern, params) {
|
|
4901
6244
|
const segments = pattern.split("/").filter(Boolean);
|
|
4902
6245
|
const parts = [];
|
|
@@ -4925,12 +6268,12 @@ function buildPathFromPattern(pattern, params) {
|
|
|
4925
6268
|
}
|
|
4926
6269
|
function pathToOutDir(baseDir, urlPath) {
|
|
4927
6270
|
const clean = urlPath === "/" ? "" : urlPath.replace(/^\/+/, "");
|
|
4928
|
-
return
|
|
6271
|
+
return import_path26.default.join(baseDir, clean);
|
|
4929
6272
|
}
|
|
4930
6273
|
|
|
4931
6274
|
// modules/build/ssg/renderer.ts
|
|
4932
6275
|
var import_fs19 = __toESM(require("fs"));
|
|
4933
|
-
var
|
|
6276
|
+
var import_path27 = __toESM(require("path"));
|
|
4934
6277
|
var import_server3 = require("react-dom/server");
|
|
4935
6278
|
init_globals();
|
|
4936
6279
|
async function renderStaticRoute(projectRoot, ssgOutDir, route, urlPath, params) {
|
|
@@ -4947,6 +6290,10 @@ async function renderStaticRoute(projectRoot, ssgOutDir, route, urlPath, params)
|
|
|
4947
6290
|
chunkHref = `${STATIC_PATH}/${chunkName}.js`;
|
|
4948
6291
|
}
|
|
4949
6292
|
}
|
|
6293
|
+
const entrypointFiles = [];
|
|
6294
|
+
if (assetManifest?.entrypoints?.client) {
|
|
6295
|
+
entrypointFiles.push(...assetManifest.entrypoints.client.map((file) => `${STATIC_PATH}/${file}`));
|
|
6296
|
+
}
|
|
4950
6297
|
const req = {
|
|
4951
6298
|
method: "GET",
|
|
4952
6299
|
headers: {},
|
|
@@ -4976,32 +6323,79 @@ async function renderStaticRoute(projectRoot, ssgOutDir, route, urlPath, params)
|
|
|
4976
6323
|
})
|
|
4977
6324
|
);
|
|
4978
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
|
+
}
|
|
4979
6352
|
let loaderResult = { props: {} };
|
|
4980
6353
|
if (route.loader) {
|
|
4981
6354
|
loaderResult = await route.loader(ctx);
|
|
4982
6355
|
}
|
|
4983
|
-
|
|
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) {
|
|
4984
6375
|
return;
|
|
4985
6376
|
}
|
|
4986
|
-
const initialData = buildInitialData(urlPath, params,
|
|
6377
|
+
const initialData = buildInitialData(urlPath, params, combinedLoaderResult);
|
|
4987
6378
|
const routerData = buildRouterData(req);
|
|
4988
6379
|
const appTree = buildAppTree(route, params, initialData.props);
|
|
4989
6380
|
const documentTree = createDocumentTree({
|
|
4990
6381
|
appTree,
|
|
4991
6382
|
initialData,
|
|
4992
6383
|
routerData,
|
|
4993
|
-
meta:
|
|
6384
|
+
meta: combinedLoaderResult.metadata,
|
|
4994
6385
|
titleFallback: "My Framework Dev",
|
|
4995
6386
|
descriptionFallback: "Static page generated by @lolyjs/core.",
|
|
4996
6387
|
chunkHref,
|
|
6388
|
+
entrypointFiles,
|
|
4997
6389
|
clientJsPath,
|
|
4998
|
-
clientCssPath
|
|
6390
|
+
clientCssPath,
|
|
6391
|
+
includeInlineScripts: true
|
|
6392
|
+
// SSG needs inline scripts (renderToString doesn't support bootstrapScripts)
|
|
4999
6393
|
});
|
|
5000
6394
|
const html = "<!DOCTYPE html>" + (0, import_server3.renderToString)(documentTree);
|
|
5001
6395
|
const dir = pathToOutDir(ssgOutDir, urlPath);
|
|
5002
6396
|
ensureDir(dir);
|
|
5003
|
-
const htmlFile =
|
|
5004
|
-
const dataFile =
|
|
6397
|
+
const htmlFile = import_path27.default.join(dir, "index.html");
|
|
6398
|
+
const dataFile = import_path27.default.join(dir, "data.json");
|
|
5005
6399
|
import_fs19.default.writeFileSync(htmlFile, html, "utf-8");
|
|
5006
6400
|
import_fs19.default.writeFileSync(dataFile, JSON.stringify(initialData, null, 2), "utf-8");
|
|
5007
6401
|
}
|
|
@@ -5009,7 +6403,7 @@ async function renderStaticRoute(projectRoot, ssgOutDir, route, urlPath, params)
|
|
|
5009
6403
|
// modules/build/ssg/builder.ts
|
|
5010
6404
|
init_globals();
|
|
5011
6405
|
async function buildStaticPages(projectRoot, routes) {
|
|
5012
|
-
const ssgOutDir =
|
|
6406
|
+
const ssgOutDir = import_path29.default.join(projectRoot, BUILD_FOLDER_NAME, "ssg");
|
|
5013
6407
|
ensureDir(ssgOutDir);
|
|
5014
6408
|
for (const route of routes) {
|
|
5015
6409
|
if (route.dynamic !== "force-static") continue;
|
|
@@ -5019,12 +6413,15 @@ async function buildStaticPages(projectRoot, routes) {
|
|
|
5019
6413
|
} else {
|
|
5020
6414
|
if (!route.generateStaticParams) {
|
|
5021
6415
|
console.warn(
|
|
5022
|
-
|
|
6416
|
+
`\u26A0\uFE0F [framework][ssg] Route "${route.pattern}" is marked as force-static but has no generateStaticParams function`
|
|
5023
6417
|
);
|
|
6418
|
+
console.warn(` \u{1F4A1} Add a generateStaticParams export to enable static generation for this route`);
|
|
6419
|
+
console.warn(` Skipping this route...
|
|
6420
|
+
`);
|
|
5024
6421
|
continue;
|
|
5025
6422
|
}
|
|
5026
6423
|
try {
|
|
5027
|
-
console.log(
|
|
6424
|
+
console.log(`\u{1F4E6} [framework][ssg] Generating static params for route: ${route.pattern}`);
|
|
5028
6425
|
let timeoutId = null;
|
|
5029
6426
|
const timeoutPromise = new Promise((_, reject) => {
|
|
5030
6427
|
timeoutId = setTimeout(() => {
|
|
@@ -5039,12 +6436,16 @@ async function buildStaticPages(projectRoot, routes) {
|
|
|
5039
6436
|
clearTimeout(timeoutId);
|
|
5040
6437
|
}
|
|
5041
6438
|
allParams = sp;
|
|
5042
|
-
console.log(`
|
|
6439
|
+
console.log(` \u2705 Generated ${sp.length} static params for route: ${route.pattern}`);
|
|
5043
6440
|
} catch (error) {
|
|
5044
|
-
console.error(
|
|
5045
|
-
|
|
5046
|
-
|
|
5047
|
-
)
|
|
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
|
+
`);
|
|
5048
6449
|
throw error;
|
|
5049
6450
|
}
|
|
5050
6451
|
}
|
|
@@ -5053,11 +6454,11 @@ async function buildStaticPages(projectRoot, routes) {
|
|
|
5053
6454
|
await renderStaticRoute(projectRoot, ssgOutDir, route, urlPath, params);
|
|
5054
6455
|
}
|
|
5055
6456
|
}
|
|
5056
|
-
console.log(
|
|
6457
|
+
console.log(`\u2705 [framework][ssg] Finished building all static pages`);
|
|
5057
6458
|
}
|
|
5058
6459
|
|
|
5059
6460
|
// modules/build/bundler/server.ts
|
|
5060
|
-
var
|
|
6461
|
+
var import_path31 = __toESM(require("path"));
|
|
5061
6462
|
var import_fs20 = __toESM(require("fs"));
|
|
5062
6463
|
var import_esbuild = __toESM(require("esbuild"));
|
|
5063
6464
|
init_globals();
|
|
@@ -5067,7 +6468,7 @@ function collectAppSources(appDir) {
|
|
|
5067
6468
|
function walk(dir) {
|
|
5068
6469
|
const items = import_fs20.default.readdirSync(dir, { withFileTypes: true });
|
|
5069
6470
|
for (const item of items) {
|
|
5070
|
-
const full =
|
|
6471
|
+
const full = import_path31.default.join(dir, item.name);
|
|
5071
6472
|
if (item.isDirectory()) {
|
|
5072
6473
|
walk(full);
|
|
5073
6474
|
continue;
|
|
@@ -5084,7 +6485,7 @@ function collectAppSources(appDir) {
|
|
|
5084
6485
|
return entries;
|
|
5085
6486
|
}
|
|
5086
6487
|
async function buildServerApp(projectRoot, appDir) {
|
|
5087
|
-
const outDir =
|
|
6488
|
+
const outDir = import_path31.default.join(projectRoot, BUILD_FOLDER_NAME, "server");
|
|
5088
6489
|
const entryPoints = collectAppSources(appDir);
|
|
5089
6490
|
ensureDir(outDir);
|
|
5090
6491
|
if (entryPoints.length === 0) {
|
|
@@ -5102,12 +6503,12 @@ async function buildServerApp(projectRoot, appDir) {
|
|
|
5102
6503
|
bundle: true,
|
|
5103
6504
|
splitting: false,
|
|
5104
6505
|
logLevel: "info",
|
|
5105
|
-
tsconfig:
|
|
6506
|
+
tsconfig: import_path31.default.join(projectRoot, "tsconfig.json"),
|
|
5106
6507
|
packages: "external"
|
|
5107
6508
|
});
|
|
5108
6509
|
for (const fileName of SERVER_FILES) {
|
|
5109
|
-
const initTS =
|
|
5110
|
-
const initJS =
|
|
6510
|
+
const initTS = import_path31.default.join(projectRoot, `${fileName}.ts`);
|
|
6511
|
+
const initJS = import_path31.default.join(outDir, `${fileName}.js`);
|
|
5111
6512
|
if (import_fs20.default.existsSync(initTS)) {
|
|
5112
6513
|
await import_esbuild.default.build({
|
|
5113
6514
|
entryPoints: [initTS],
|
|
@@ -5119,7 +6520,7 @@ async function buildServerApp(projectRoot, appDir) {
|
|
|
5119
6520
|
sourcemap: true,
|
|
5120
6521
|
bundle: false,
|
|
5121
6522
|
logLevel: "info",
|
|
5122
|
-
tsconfig:
|
|
6523
|
+
tsconfig: import_path31.default.join(projectRoot, "tsconfig.json")
|
|
5123
6524
|
});
|
|
5124
6525
|
}
|
|
5125
6526
|
}
|
|
@@ -5262,22 +6663,158 @@ function matchRouteClient(pathWithSearch, routes) {
|
|
|
5262
6663
|
}
|
|
5263
6664
|
|
|
5264
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
|
+
}
|
|
5265
6690
|
function applyMetadata(md) {
|
|
5266
6691
|
if (!md) return;
|
|
5267
6692
|
if (md.title) {
|
|
5268
6693
|
document.title = md.title;
|
|
5269
6694
|
}
|
|
5270
6695
|
if (md.description) {
|
|
5271
|
-
|
|
5272
|
-
'meta[name="description"]'
|
|
5273
|
-
);
|
|
5274
|
-
if (!meta) {
|
|
5275
|
-
meta = document.createElement("meta");
|
|
5276
|
-
meta.name = "description";
|
|
5277
|
-
document.head.appendChild(meta);
|
|
5278
|
-
}
|
|
6696
|
+
const meta = getOrCreateMeta('meta[name="description"]', { name: "description" });
|
|
5279
6697
|
meta.content = md.description;
|
|
5280
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
|
+
}
|
|
5281
6818
|
}
|
|
5282
6819
|
|
|
5283
6820
|
// modules/runtime/client/AppShell.tsx
|
|
@@ -5494,10 +7031,15 @@ async function handleErrorRoute(nextUrl, json, errorRoute, setState) {
|
|
|
5494
7031
|
});
|
|
5495
7032
|
return true;
|
|
5496
7033
|
} catch (loadError) {
|
|
5497
|
-
console.error(
|
|
5498
|
-
|
|
5499
|
-
|
|
5500
|
-
|
|
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");
|
|
5501
7043
|
window.location.href = nextUrl;
|
|
5502
7044
|
return false;
|
|
5503
7045
|
}
|
|
@@ -5602,7 +7144,11 @@ async function handleNormalRoute(nextUrl, json, routes, setState) {
|
|
|
5602
7144
|
searchParams: Object.fromEntries(url.searchParams.entries())
|
|
5603
7145
|
};
|
|
5604
7146
|
setRouterData(routerData);
|
|
5605
|
-
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
|
+
}
|
|
5606
7152
|
window.scrollTo({
|
|
5607
7153
|
top: 0,
|
|
5608
7154
|
behavior: "smooth"
|
|
@@ -5641,7 +7187,7 @@ async function navigate(nextUrl, handlers, options) {
|
|
|
5641
7187
|
}
|
|
5642
7188
|
}
|
|
5643
7189
|
if (!ok) {
|
|
5644
|
-
if (json
|
|
7190
|
+
if (json?.redirect) {
|
|
5645
7191
|
window.location.href = json.redirect.destination;
|
|
5646
7192
|
return;
|
|
5647
7193
|
}
|
|
@@ -5662,6 +7208,47 @@ async function navigate(nextUrl, handlers, options) {
|
|
|
5662
7208
|
window.location.href = nextUrl;
|
|
5663
7209
|
}
|
|
5664
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
|
+
}
|
|
5665
7252
|
function createClickHandler(navigate2) {
|
|
5666
7253
|
return function handleClick(ev) {
|
|
5667
7254
|
try {
|
|
@@ -5772,17 +7359,20 @@ function AppShell({
|
|
|
5772
7359
|
}
|
|
5773
7360
|
const handleClick = createClickHandler(handleNavigateInternal);
|
|
5774
7361
|
const handlePopState = createPopStateHandler(handleNavigateInternal);
|
|
7362
|
+
const handleHover = createHoverHandler(routes, notFoundRoute);
|
|
5775
7363
|
window.addEventListener("click", handleClick, false);
|
|
5776
7364
|
window.addEventListener("popstate", handlePopState, false);
|
|
7365
|
+
window.addEventListener("mouseover", handleHover, false);
|
|
5777
7366
|
return () => {
|
|
5778
7367
|
isMounted = false;
|
|
5779
7368
|
window.removeEventListener("click", handleClick, false);
|
|
5780
7369
|
window.removeEventListener("popstate", handlePopState, false);
|
|
7370
|
+
window.removeEventListener("mouseover", handleHover, false);
|
|
5781
7371
|
};
|
|
5782
|
-
}, []);
|
|
7372
|
+
}, [routes, notFoundRoute]);
|
|
5783
7373
|
(0, import_react3.useEffect)(() => {
|
|
5784
7374
|
const handleDataRefresh = () => {
|
|
5785
|
-
const freshData = window
|
|
7375
|
+
const freshData = window[WINDOW_DATA_KEY2];
|
|
5786
7376
|
if (!freshData) return;
|
|
5787
7377
|
const currentPathname = window.location.pathname;
|
|
5788
7378
|
const freshPathname = freshData.pathname;
|
|
@@ -5809,6 +7399,91 @@ function AppShell({
|
|
|
5809
7399
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(RouterContext.Provider, { value: { navigate: handleNavigate }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(RouterView, { state }, routeKey) });
|
|
5810
7400
|
}
|
|
5811
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
|
+
|
|
5812
7487
|
// modules/runtime/client/bootstrap.tsx
|
|
5813
7488
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
5814
7489
|
async function loadInitialRoute(initialUrl, initialData, routes, notFoundRoute, errorRoute) {
|
|
@@ -5850,101 +7525,91 @@ async function loadInitialRoute(initialUrl, initialData, routes, notFoundRoute,
|
|
|
5850
7525
|
props: initialData?.props ?? {}
|
|
5851
7526
|
};
|
|
5852
7527
|
}
|
|
5853
|
-
function
|
|
5854
|
-
|
|
5855
|
-
|
|
5856
|
-
|
|
5857
|
-
|
|
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);
|
|
5858
7538
|
}
|
|
7539
|
+
}
|
|
7540
|
+
async function hydrateInitialRoute(container, initialUrl, initialData, routes, notFoundRoute, errorRoute) {
|
|
5859
7541
|
try {
|
|
5860
|
-
|
|
5861
|
-
|
|
5862
|
-
|
|
5863
|
-
|
|
5864
|
-
|
|
5865
|
-
|
|
5866
|
-
|
|
5867
|
-
|
|
5868
|
-
|
|
5869
|
-
|
|
5870
|
-
|
|
5871
|
-
|
|
5872
|
-
console.log("[hot-reload] Reloading page...");
|
|
5873
|
-
window.location.reload();
|
|
5874
|
-
}, 500);
|
|
5875
|
-
}
|
|
5876
|
-
});
|
|
5877
|
-
eventSource.addEventListener("ping", () => {
|
|
5878
|
-
console.log("[hot-reload] \u2713 Connected to hot reload server");
|
|
5879
|
-
});
|
|
5880
|
-
eventSource.onopen = () => {
|
|
5881
|
-
console.log("[hot-reload] \u2713 SSE connection opened");
|
|
5882
|
-
};
|
|
5883
|
-
eventSource.onerror = (error) => {
|
|
5884
|
-
const states = ["CONNECTING", "OPEN", "CLOSED"];
|
|
5885
|
-
const state = states[eventSource.readyState] || "UNKNOWN";
|
|
5886
|
-
if (eventSource.readyState === EventSource.CONNECTING) {
|
|
5887
|
-
console.log("[hot-reload] Connecting...");
|
|
5888
|
-
} else if (eventSource.readyState === EventSource.OPEN) {
|
|
5889
|
-
console.warn("[hot-reload] Connection error (but connection is open):", error);
|
|
5890
|
-
} else {
|
|
5891
|
-
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);
|
|
5892
7554
|
}
|
|
5893
|
-
}
|
|
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
|
+
);
|
|
5894
7568
|
} catch (error) {
|
|
5895
|
-
console.
|
|
7569
|
+
console.error(
|
|
7570
|
+
"[client] Error loading initial route components for",
|
|
7571
|
+
initialUrl,
|
|
7572
|
+
error
|
|
7573
|
+
);
|
|
7574
|
+
throw error;
|
|
5896
7575
|
}
|
|
5897
7576
|
}
|
|
5898
7577
|
function bootstrapClient(routes, notFoundRoute, errorRoute = null) {
|
|
5899
|
-
console.log("[client] Bootstrap starting, setting up hot reload...");
|
|
5900
7578
|
setupHotReload2();
|
|
5901
|
-
(async
|
|
5902
|
-
const container = document.getElementById(APP_CONTAINER_ID2);
|
|
5903
|
-
const initialData = getWindowData();
|
|
5904
|
-
if (!container) {
|
|
5905
|
-
console.error(`Container #${APP_CONTAINER_ID2} not found for hydration`);
|
|
5906
|
-
return;
|
|
5907
|
-
}
|
|
5908
|
-
const initialUrl = window.location.pathname + window.location.search;
|
|
5909
|
-
let routerData = getRouterData();
|
|
5910
|
-
if (!routerData) {
|
|
5911
|
-
const url = new URL(initialUrl, window.location.origin);
|
|
5912
|
-
routerData = {
|
|
5913
|
-
pathname: url.pathname,
|
|
5914
|
-
params: initialData?.params || {},
|
|
5915
|
-
searchParams: Object.fromEntries(url.searchParams.entries())
|
|
5916
|
-
};
|
|
5917
|
-
setRouterData(routerData);
|
|
5918
|
-
}
|
|
7579
|
+
(async () => {
|
|
5919
7580
|
try {
|
|
5920
|
-
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,
|
|
5921
7596
|
initialUrl,
|
|
5922
7597
|
initialData,
|
|
5923
7598
|
routes,
|
|
5924
7599
|
notFoundRoute,
|
|
5925
7600
|
errorRoute
|
|
5926
7601
|
);
|
|
5927
|
-
if (initialData?.metadata) {
|
|
5928
|
-
applyMetadata(initialData.metadata);
|
|
5929
|
-
}
|
|
5930
|
-
(0, import_client5.hydrateRoot)(
|
|
5931
|
-
container,
|
|
5932
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
5933
|
-
AppShell,
|
|
5934
|
-
{
|
|
5935
|
-
initialState,
|
|
5936
|
-
routes,
|
|
5937
|
-
notFoundRoute,
|
|
5938
|
-
errorRoute
|
|
5939
|
-
}
|
|
5940
|
-
)
|
|
5941
|
-
);
|
|
5942
7602
|
} catch (error) {
|
|
5943
|
-
console.error(
|
|
5944
|
-
|
|
5945
|
-
|
|
5946
|
-
error
|
|
5947
|
-
|
|
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");
|
|
5948
7613
|
window.location.reload();
|
|
5949
7614
|
}
|
|
5950
7615
|
})();
|
|
@@ -5972,11 +7637,11 @@ var ValidationError = class extends Error {
|
|
|
5972
7637
|
format() {
|
|
5973
7638
|
const formatted = {};
|
|
5974
7639
|
for (const error of this.errors) {
|
|
5975
|
-
const
|
|
5976
|
-
if (!formatted[
|
|
5977
|
-
formatted[
|
|
7640
|
+
const path28 = error.path.join(".");
|
|
7641
|
+
if (!formatted[path28]) {
|
|
7642
|
+
formatted[path28] = [];
|
|
5978
7643
|
}
|
|
5979
|
-
formatted[
|
|
7644
|
+
formatted[path28].push(error.message);
|
|
5980
7645
|
}
|
|
5981
7646
|
return formatted;
|
|
5982
7647
|
}
|