@lolyjs/core 0.2.0-alpha.13 → 0.2.0-alpha.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -168,6 +168,10 @@ interface LoaderResult {
168
168
  className?: string;
169
169
  theme?: string;
170
170
  }
171
+ /**
172
+ * Server loader function type (getServerSideProps).
173
+ * This function is exported from server.hook.ts files.
174
+ */
171
175
  type ServerLoader = (ctx: ServerContext) => Promise<LoaderResult>;
172
176
  interface PageMetadata {
173
177
  title?: string;
package/dist/index.d.ts CHANGED
@@ -168,6 +168,10 @@ interface LoaderResult {
168
168
  className?: string;
169
169
  theme?: string;
170
170
  }
171
+ /**
172
+ * Server loader function type (getServerSideProps).
173
+ * This function is exported from server.hook.ts files.
174
+ */
171
175
  type ServerLoader = (ctx: ServerContext) => Promise<LoaderResult>;
172
176
  interface PageMetadata {
173
177
  title?: string;
package/dist/index.js CHANGED
@@ -204,7 +204,7 @@ function loadLayoutsForDir(pageDir, appDir) {
204
204
  };
205
205
  }
206
206
 
207
- // modules/router/loader.ts
207
+ // modules/router/server-hook.ts
208
208
  import fs3 from "fs";
209
209
  import path3 from "path";
210
210
  var NAMING = {
@@ -216,14 +216,16 @@ var NAMING = {
216
216
  // Files
217
217
  SERVER_HOOK: "server.hook"
218
218
  };
219
- function loadLoaderForDir(currentDir) {
220
- const loaderTs = path3.join(currentDir, `${NAMING.SERVER_HOOK}.ts`);
221
- const loaderJs = path3.join(currentDir, `${NAMING.SERVER_HOOK}.js`);
222
- const file = fs3.existsSync(loaderTs) ? loaderTs : fs3.existsSync(loaderJs) ? loaderJs : null;
219
+ function loadServerHookForDir(currentDir) {
220
+ const pageServerHookTs = path3.join(currentDir, `page.server.hook.ts`);
221
+ const pageServerHookJs = path3.join(currentDir, `page.server.hook.js`);
222
+ const serverHookTs = path3.join(currentDir, `${NAMING.SERVER_HOOK}.ts`);
223
+ const serverHookJs = path3.join(currentDir, `${NAMING.SERVER_HOOK}.js`);
224
+ const file = fs3.existsSync(pageServerHookTs) ? pageServerHookTs : fs3.existsSync(pageServerHookJs) ? pageServerHookJs : fs3.existsSync(serverHookTs) ? serverHookTs : fs3.existsSync(serverHookJs) ? serverHookJs : null;
223
225
  if (!file) {
224
226
  return {
225
227
  middlewares: [],
226
- loader: null,
228
+ serverHook: null,
227
229
  dynamic: "auto",
228
230
  generateStaticParams: null
229
231
  };
@@ -239,12 +241,12 @@ function loadLoaderForDir(currentDir) {
239
241
  mod = __require(file);
240
242
  } catch (error) {
241
243
  console.error(
242
- `[framework][loader] Error loading server hook from ${file}:`,
244
+ `[framework][server-hook] Error loading server hook from ${file}:`,
243
245
  error
244
246
  );
245
247
  return {
246
248
  middlewares: [],
247
- loader: null,
249
+ serverHook: null,
248
250
  dynamic: "auto",
249
251
  generateStaticParams: null
250
252
  };
@@ -252,16 +254,43 @@ function loadLoaderForDir(currentDir) {
252
254
  const middlewares = Array.isArray(
253
255
  mod?.[NAMING.BEFORE_MIDDLEWARES]
254
256
  ) ? mod[NAMING.BEFORE_MIDDLEWARES] : [];
255
- const loader = typeof mod?.[NAMING.GET_SERVER_DATA_FN] === "function" ? mod[NAMING.GET_SERVER_DATA_FN] : null;
257
+ const serverHook = typeof mod?.[NAMING.GET_SERVER_DATA_FN] === "function" ? mod[NAMING.GET_SERVER_DATA_FN] : null;
256
258
  const dynamic = mod?.[NAMING.RENDER_TYPE_CONST] === "force-static" || mod?.[NAMING.RENDER_TYPE_CONST] === "force-dynamic" ? mod.dynamic : "auto";
257
259
  const generateStaticParams = typeof mod?.[NAMING.GENERATE_SSG_PARAMS] === "function" ? mod[NAMING.GENERATE_SSG_PARAMS] : null;
258
260
  return {
259
261
  middlewares,
260
- loader,
262
+ serverHook,
261
263
  dynamic,
262
264
  generateStaticParams
263
265
  };
264
266
  }
267
+ function loadLayoutServerHook(layoutFile) {
268
+ const layoutDir = path3.dirname(layoutFile);
269
+ const layoutBasename = path3.basename(layoutFile, path3.extname(layoutFile));
270
+ const serverHookTs = path3.join(layoutDir, `${layoutBasename}.server.hook.ts`);
271
+ const serverHookJs = path3.join(layoutDir, `${layoutBasename}.server.hook.js`);
272
+ const file = fs3.existsSync(serverHookTs) ? serverHookTs : fs3.existsSync(serverHookJs) ? serverHookJs : null;
273
+ if (!file) {
274
+ return null;
275
+ }
276
+ if (file.endsWith(".ts") || file.endsWith(".tsx")) {
277
+ try {
278
+ __require("tsx/cjs");
279
+ } catch (e) {
280
+ }
281
+ }
282
+ try {
283
+ const mod = __require(file);
284
+ const serverHook = typeof mod?.getServerSideProps === "function" ? mod.getServerSideProps : null;
285
+ return serverHook;
286
+ } catch (error) {
287
+ console.error(
288
+ `[framework][server-hook] Error loading layout server hook from ${file}:`,
289
+ error
290
+ );
291
+ return null;
292
+ }
293
+ }
265
294
 
266
295
  // modules/router/loader-pages.ts
267
296
  function loadRoutes(appDir) {
@@ -293,7 +322,12 @@ function loadRoutes(appDir) {
293
322
  currentDir,
294
323
  appDir
295
324
  );
296
- const { middlewares, loader, dynamic, generateStaticParams } = loadLoaderForDir(currentDir);
325
+ const layoutServerHooks = [];
326
+ for (const layoutFile of layoutFiles) {
327
+ const layoutServerHook = loadLayoutServerHook(layoutFile);
328
+ layoutServerHooks.push(layoutServerHook);
329
+ }
330
+ const { middlewares, serverHook, dynamic, generateStaticParams } = loadServerHookForDir(currentDir);
297
331
  routes.push({
298
332
  pattern: routePath,
299
333
  regex,
@@ -303,7 +337,10 @@ function loadRoutes(appDir) {
303
337
  pageFile: fullPath,
304
338
  layoutFiles,
305
339
  middlewares,
306
- loader,
340
+ loader: serverHook,
341
+ // Keep 'loader' field name for backward compatibility
342
+ layoutServerHooks,
343
+ // Server hooks for each layout (same order as layouts)
307
344
  dynamic,
308
345
  generateStaticParams
309
346
  });
@@ -822,7 +859,12 @@ function loadRoutesFromManifest(projectRoot) {
822
859
  (f) => path8.join(projectRoot, f)
823
860
  );
824
861
  const pageDir = path8.dirname(pageFile);
825
- const { middlewares, loader, dynamic, generateStaticParams } = loadLoaderForDir(pageDir);
862
+ const layoutServerHooks = [];
863
+ for (const layoutFile of layoutFiles) {
864
+ const layoutServerHook = loadLayoutServerHook(layoutFile);
865
+ layoutServerHooks.push(layoutServerHook);
866
+ }
867
+ const { middlewares, serverHook, dynamic, generateStaticParams } = loadServerHookForDir(pageDir);
826
868
  pageRoutes.push({
827
869
  pattern: entry.pattern,
828
870
  regex,
@@ -832,7 +874,10 @@ function loadRoutesFromManifest(projectRoot) {
832
874
  pageFile,
833
875
  layoutFiles,
834
876
  middlewares,
835
- loader,
877
+ loader: serverHook,
878
+ // Keep 'loader' field name for backward compatibility
879
+ layoutServerHooks,
880
+ // Server hooks for each layout (same order as layouts)
836
881
  dynamic: entry.dynamic ?? dynamic,
837
882
  generateStaticParams
838
883
  });
@@ -1086,7 +1131,12 @@ function loadNotFoundRouteFromFilesystem(appDir) {
1086
1131
  notFoundDir,
1087
1132
  appDir
1088
1133
  );
1089
- const { middlewares, loader, dynamic, generateStaticParams } = loadLoaderForDir(notFoundDir);
1134
+ const layoutServerHooks = [];
1135
+ for (const layoutFile of layoutFiles) {
1136
+ const layoutServerHook = loadLayoutServerHook(layoutFile);
1137
+ layoutServerHooks.push(layoutServerHook);
1138
+ }
1139
+ const { middlewares, serverHook, dynamic, generateStaticParams } = loadServerHookForDir(notFoundDir);
1090
1140
  return {
1091
1141
  pattern: NOT_FOUND_PATTERN,
1092
1142
  regex: new RegExp(`^${NOT_FOUND_PATTERN}/?$`),
@@ -1096,7 +1146,10 @@ function loadNotFoundRouteFromFilesystem(appDir) {
1096
1146
  pageFile: notFoundFile,
1097
1147
  layoutFiles,
1098
1148
  middlewares,
1099
- loader,
1149
+ loader: serverHook,
1150
+ // Keep 'loader' field name for backward compatibility
1151
+ layoutServerHooks,
1152
+ // Server hooks for each layout (same order as layouts)
1100
1153
  dynamic,
1101
1154
  generateStaticParams
1102
1155
  };
@@ -1127,7 +1180,12 @@ function loadErrorRouteFromFilesystem(appDir) {
1127
1180
  appDir,
1128
1181
  appDir
1129
1182
  );
1130
- const { middlewares, loader, dynamic, generateStaticParams } = loadLoaderForDir(appDir);
1183
+ const layoutServerHooks = [];
1184
+ for (const layoutFile of layoutFiles) {
1185
+ const layoutServerHook = loadLayoutServerHook(layoutFile);
1186
+ layoutServerHooks.push(layoutServerHook);
1187
+ }
1188
+ const { middlewares, serverHook, dynamic, generateStaticParams } = loadServerHookForDir(appDir);
1131
1189
  return {
1132
1190
  pattern: ERROR_PATTERN,
1133
1191
  regex: new RegExp(`^${ERROR_PATTERN}/?$`),
@@ -1137,7 +1195,10 @@ function loadErrorRouteFromFilesystem(appDir) {
1137
1195
  pageFile: errorFile,
1138
1196
  layoutFiles,
1139
1197
  middlewares,
1140
- loader,
1198
+ loader: serverHook,
1199
+ // Keep 'loader' field name for backward compatibility
1200
+ layoutServerHooks,
1201
+ // Server hooks for each layout (same order as layouts)
1141
1202
  dynamic,
1142
1203
  generateStaticParams
1143
1204
  };
@@ -3711,7 +3772,6 @@ function requestLoggerMiddleware(options = {}) {
3711
3772
  const startTime = Date.now();
3712
3773
  res.on("finish", () => {
3713
3774
  const duration = Date.now() - startTime;
3714
- const shouldLogSuccess = logRequests === true;
3715
3775
  if (res.statusCode >= 500) {
3716
3776
  reqLogger.error(`${req.method} ${req.path} ${res.statusCode}`, {
3717
3777
  statusCode: res.statusCode,
@@ -3722,11 +3782,6 @@ function requestLoggerMiddleware(options = {}) {
3722
3782
  statusCode: res.statusCode,
3723
3783
  duration: `${duration}ms`
3724
3784
  });
3725
- } else if (shouldLogSuccess) {
3726
- reqLogger.debug(`${req.method} ${req.path} ${res.statusCode}`, {
3727
- statusCode: res.statusCode,
3728
- duration: `${duration}ms`
3729
- });
3730
3785
  }
3731
3786
  });
3732
3787
  }
@@ -3983,8 +4038,8 @@ async function runRouteMiddlewares(route, ctx) {
3983
4038
  }
3984
4039
  }
3985
4040
 
3986
- // modules/server/handlers/loader.ts
3987
- async function runRouteLoader(route, ctx) {
4041
+ // modules/server/handlers/server-hook.ts
4042
+ async function runRouteServerHook(route, ctx) {
3988
4043
  if (!route.loader) {
3989
4044
  return { props: {} };
3990
4045
  }
@@ -4141,11 +4196,39 @@ async function handlePageRequestInternal(options) {
4141
4196
  pathname: urlPath,
4142
4197
  locals: {}
4143
4198
  };
4144
- let loaderResult2 = await runRouteLoader(notFoundPage, ctx2);
4199
+ const layoutProps2 = {};
4200
+ if (notFoundPage.layoutServerHooks && notFoundPage.layoutServerHooks.length > 0) {
4201
+ for (let i = 0; i < notFoundPage.layoutServerHooks.length; i++) {
4202
+ const layoutServerHook = notFoundPage.layoutServerHooks[i];
4203
+ if (layoutServerHook) {
4204
+ try {
4205
+ const layoutResult = await layoutServerHook(ctx2);
4206
+ if (layoutResult.props) {
4207
+ Object.assign(layoutProps2, layoutResult.props);
4208
+ }
4209
+ } catch (error) {
4210
+ const reqLogger2 = getRequestLogger(req);
4211
+ reqLogger2.warn(`Layout server hook ${i} failed for not-found`, {
4212
+ error,
4213
+ layoutFile: notFoundPage.layoutFiles[i]
4214
+ });
4215
+ }
4216
+ }
4217
+ }
4218
+ }
4219
+ let loaderResult2 = await runRouteServerHook(notFoundPage, ctx2);
4145
4220
  if (!loaderResult2.theme) {
4146
4221
  loaderResult2.theme = theme;
4147
4222
  }
4148
- const initialData2 = buildInitialData(urlPath, {}, loaderResult2);
4223
+ const combinedProps2 = {
4224
+ ...layoutProps2,
4225
+ ...loaderResult2.props || {}
4226
+ };
4227
+ const combinedLoaderResult2 = {
4228
+ ...loaderResult2,
4229
+ props: combinedProps2
4230
+ };
4231
+ const initialData2 = buildInitialData(urlPath, {}, combinedLoaderResult2);
4149
4232
  const appTree2 = buildAppTree(notFoundPage, {}, initialData2.props);
4150
4233
  initialData2.notFound = true;
4151
4234
  const nonce2 = res.locals.nonce || void 0;
@@ -4153,7 +4236,7 @@ async function handlePageRequestInternal(options) {
4153
4236
  appTree: appTree2,
4154
4237
  initialData: initialData2,
4155
4238
  routerData,
4156
- meta: loaderResult2.metadata ?? null,
4239
+ meta: combinedLoaderResult2.metadata ?? null,
4157
4240
  titleFallback: "Not found",
4158
4241
  descriptionFallback: "Loly demo",
4159
4242
  chunkHref: null,
@@ -4172,8 +4255,8 @@ async function handlePageRequestInternal(options) {
4172
4255
  },
4173
4256
  onShellError(err) {
4174
4257
  didError2 = true;
4175
- const reqLogger = getRequestLogger(req);
4176
- reqLogger.error("SSR shell error", err, { route: "not-found" });
4258
+ const reqLogger2 = getRequestLogger(req);
4259
+ reqLogger2.error("SSR shell error", err, { route: "not-found" });
4177
4260
  if (!res.headersSent && errorPage) {
4178
4261
  renderErrorPageWithStream(errorPage, req, res, err, routeChunks, theme, projectRoot, env);
4179
4262
  } else if (!res.headersSent) {
@@ -4185,8 +4268,8 @@ async function handlePageRequestInternal(options) {
4185
4268
  },
4186
4269
  onError(err) {
4187
4270
  didError2 = true;
4188
- const reqLogger = getRequestLogger(req);
4189
- reqLogger.error("SSR error", err, { route: "not-found" });
4271
+ const reqLogger2 = getRequestLogger(req);
4272
+ reqLogger2.error("SSR error", err, { route: "not-found" });
4190
4273
  }
4191
4274
  });
4192
4275
  req.on("close", () => abort2());
@@ -4208,9 +4291,30 @@ async function handlePageRequestInternal(options) {
4208
4291
  if (res.headersSent) {
4209
4292
  return;
4210
4293
  }
4294
+ const layoutProps = {};
4295
+ const reqLogger = getRequestLogger(req);
4296
+ if (route.layoutServerHooks && route.layoutServerHooks.length > 0) {
4297
+ for (let i = 0; i < route.layoutServerHooks.length; i++) {
4298
+ const layoutServerHook = route.layoutServerHooks[i];
4299
+ if (layoutServerHook) {
4300
+ try {
4301
+ const layoutResult = await layoutServerHook(ctx);
4302
+ if (layoutResult.props) {
4303
+ Object.assign(layoutProps, layoutResult.props);
4304
+ }
4305
+ } catch (error) {
4306
+ reqLogger.warn(`Layout server hook ${i} failed`, {
4307
+ error,
4308
+ layoutFile: route.layoutFiles[i],
4309
+ route: route.pattern
4310
+ });
4311
+ }
4312
+ }
4313
+ }
4314
+ }
4211
4315
  let loaderResult;
4212
4316
  try {
4213
- loaderResult = await runRouteLoader(route, ctx);
4317
+ loaderResult = await runRouteServerHook(route, ctx);
4214
4318
  if (!loaderResult.theme) {
4215
4319
  loaderResult.theme = theme;
4216
4320
  }
@@ -4229,8 +4333,18 @@ async function handlePageRequestInternal(options) {
4229
4333
  }
4230
4334
  }
4231
4335
  }
4336
+ const combinedProps = {
4337
+ ...layoutProps,
4338
+ // Props from layouts (stable)
4339
+ ...loaderResult.props || {}
4340
+ // Props from page (overrides layout)
4341
+ };
4342
+ const combinedLoaderResult = {
4343
+ ...loaderResult,
4344
+ props: combinedProps
4345
+ };
4232
4346
  if (isDataReq) {
4233
- handleDataResponse(res, loaderResult, theme);
4347
+ handleDataResponse(res, combinedLoaderResult, theme);
4234
4348
  return;
4235
4349
  }
4236
4350
  if (loaderResult.redirect) {
@@ -4245,7 +4359,7 @@ async function handlePageRequestInternal(options) {
4245
4359
  }
4246
4360
  return;
4247
4361
  }
4248
- const initialData = buildInitialData(urlPath, params, loaderResult);
4362
+ const initialData = buildInitialData(urlPath, params, combinedLoaderResult);
4249
4363
  const appTree = buildAppTree(route, params, initialData.props);
4250
4364
  const chunkName = routeChunks[route.pattern];
4251
4365
  let chunkHref = null;
@@ -4261,7 +4375,7 @@ async function handlePageRequestInternal(options) {
4261
4375
  appTree,
4262
4376
  initialData,
4263
4377
  routerData,
4264
- meta: loaderResult.metadata,
4378
+ meta: combinedLoaderResult.metadata,
4265
4379
  titleFallback: "Loly framework",
4266
4380
  descriptionFallback: "Loly demo",
4267
4381
  chunkHref,
@@ -4282,8 +4396,8 @@ async function handlePageRequestInternal(options) {
4282
4396
  },
4283
4397
  onShellError(err) {
4284
4398
  didError = true;
4285
- const reqLogger = getRequestLogger(req);
4286
- reqLogger.error("SSR shell error", err, { route: matched?.route?.pattern || "unknown" });
4399
+ const reqLogger2 = getRequestLogger(req);
4400
+ reqLogger2.error("SSR shell error", err, { route: matched?.route?.pattern || "unknown" });
4287
4401
  if (!res.headersSent && errorPage) {
4288
4402
  renderErrorPageWithStream(errorPage, req, res, err, routeChunks, theme, projectRoot, env);
4289
4403
  } else if (!res.headersSent) {
@@ -4295,8 +4409,8 @@ async function handlePageRequestInternal(options) {
4295
4409
  },
4296
4410
  onError(err) {
4297
4411
  didError = true;
4298
- const reqLogger = getRequestLogger(req);
4299
- reqLogger.error("SSR error", err, { route: matched?.route?.pattern || "unknown" });
4412
+ const reqLogger2 = getRequestLogger(req);
4413
+ reqLogger2.error("SSR error", err, { route: matched?.route?.pattern || "unknown" });
4300
4414
  }
4301
4415
  });
4302
4416
  req.on("close", () => {
@@ -4313,11 +4427,39 @@ async function renderErrorPageWithStream(errorPage, req, res, error, routeChunks
4313
4427
  pathname: req.path,
4314
4428
  locals: { error }
4315
4429
  };
4316
- let loaderResult = await runRouteLoader(errorPage, ctx);
4430
+ const layoutProps = {};
4431
+ const reqLogger = getRequestLogger(req);
4432
+ if (errorPage.layoutServerHooks && errorPage.layoutServerHooks.length > 0) {
4433
+ for (let i = 0; i < errorPage.layoutServerHooks.length; i++) {
4434
+ const layoutServerHook = errorPage.layoutServerHooks[i];
4435
+ if (layoutServerHook) {
4436
+ try {
4437
+ const layoutResult = await layoutServerHook(ctx);
4438
+ if (layoutResult.props) {
4439
+ Object.assign(layoutProps, layoutResult.props);
4440
+ }
4441
+ } catch (err) {
4442
+ reqLogger.warn(`Layout server hook ${i} failed for error page`, {
4443
+ error: err,
4444
+ layoutFile: errorPage.layoutFiles[i]
4445
+ });
4446
+ }
4447
+ }
4448
+ }
4449
+ }
4450
+ let loaderResult = await runRouteServerHook(errorPage, ctx);
4317
4451
  if (!loaderResult.theme && theme) {
4318
4452
  loaderResult.theme = theme;
4319
4453
  }
4320
- const initialData = buildInitialData(req.path, { error: String(error) }, loaderResult);
4454
+ const combinedProps = {
4455
+ ...layoutProps,
4456
+ ...loaderResult.props || {}
4457
+ };
4458
+ const combinedLoaderResult = {
4459
+ ...loaderResult,
4460
+ props: combinedProps
4461
+ };
4462
+ const initialData = buildInitialData(req.path, { error: String(error) }, combinedLoaderResult);
4321
4463
  const routerData = buildRouterData(req);
4322
4464
  initialData.error = true;
4323
4465
  if (isDataReq) {
@@ -4327,8 +4469,8 @@ async function renderErrorPageWithStream(errorPage, req, res, error, routeChunks
4327
4469
  error: true,
4328
4470
  message: String(error),
4329
4471
  props: initialData.props,
4330
- metadata: loaderResult.metadata ?? null,
4331
- theme: loaderResult.theme ?? theme ?? null
4472
+ metadata: combinedLoaderResult.metadata ?? null,
4473
+ theme: combinedLoaderResult.theme ?? theme ?? null
4332
4474
  }));
4333
4475
  return;
4334
4476
  }
@@ -4350,7 +4492,7 @@ async function renderErrorPageWithStream(errorPage, req, res, error, routeChunks
4350
4492
  appTree,
4351
4493
  initialData,
4352
4494
  routerData,
4353
- meta: loaderResult.metadata ?? null,
4495
+ meta: combinedLoaderResult.metadata ?? null,
4354
4496
  titleFallback: "Error",
4355
4497
  descriptionFallback: "An error occurred",
4356
4498
  chunkHref,
@@ -4371,8 +4513,8 @@ async function renderErrorPageWithStream(errorPage, req, res, error, routeChunks
4371
4513
  },
4372
4514
  onShellError(err) {
4373
4515
  didError = true;
4374
- const reqLogger = getRequestLogger(req);
4375
- reqLogger.error("Error rendering error page", err, { type: "shellError" });
4516
+ const reqLogger2 = getRequestLogger(req);
4517
+ reqLogger2.error("Error rendering error page", err, { type: "shellError" });
4376
4518
  if (!res.headersSent) {
4377
4519
  res.statusCode = 500;
4378
4520
  res.setHeader("Content-Type", "text/html; charset=utf-8");
@@ -4382,8 +4524,8 @@ async function renderErrorPageWithStream(errorPage, req, res, error, routeChunks
4382
4524
  },
4383
4525
  onError(err) {
4384
4526
  didError = true;
4385
- const reqLogger = getRequestLogger(req);
4386
- reqLogger.error("Error in error page", err);
4527
+ const reqLogger2 = getRequestLogger(req);
4528
+ reqLogger2.error("Error in error page", err);
4387
4529
  }
4388
4530
  });
4389
4531
  req.on("close", () => {
@@ -4824,26 +4966,22 @@ async function startServer(options = {}) {
4824
4966
  });
4825
4967
  httpServer.listen(port, host, () => {
4826
4968
  if (isDev) {
4827
- logger3.info("\u{1F680} Dev server running", {
4828
- url: `http://${host}:${port}`,
4829
- appDir
4830
- });
4831
- logger3.info("\u{1F9ED} Reading routes from", { appDir });
4832
- logger3.info("\u{1F4E6} Client served from /static/client.js");
4969
+ const reset = "\x1B[0m";
4970
+ const cyan = "\x1B[36m";
4971
+ const green = "\x1B[32m";
4972
+ const dim = "\x1B[2m";
4973
+ const bold = "\x1B[1m";
4974
+ const url = `http://${host === "0.0.0.0" ? "localhost" : host}:${port}`;
4975
+ console.log();
4976
+ console.log(`${bold}${green}\u2713${reset} ${bold}Dev server ready${reset}`);
4977
+ console.log(`${dim} Local:${reset} ${cyan}${url}${reset}`);
4978
+ if (routes.length > 0 || apiRoutes.length > 0 || wssRoutes.length > 0) {
4979
+ console.log(`${dim} Routes:${reset} ${routes.length} pages, ${apiRoutes.length} API, ${wssRoutes.length} WSS`);
4980
+ }
4981
+ console.log();
4833
4982
  } else {
4834
- const buildDir = config.directories.build;
4835
- logger3.info("\u{1F680} Prod server running", {
4836
- url: `http://${host}:${port}`,
4837
- appDir,
4838
- buildDir
4839
- });
4840
- logger3.info("\u{1F9ED} Reading compiled routes from", { appDir });
4841
- logger3.info("\u{1F4E6} Client served from", {
4842
- path: `/static (${buildDir}/client)`
4843
- });
4844
- logger3.info("\u{1F4C4} SSG served from", {
4845
- path: `${buildDir}/ssg (if exists)`
4846
- });
4983
+ const url = `http://${host}:${port}`;
4984
+ console.log(`\u{1F680} Server running on ${url}`);
4847
4985
  }
4848
4986
  });
4849
4987
  }