evolit 0.1.0-alpha.2 → 0.1.0-alpha.21

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.
@@ -1,22 +1,29 @@
1
1
  import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
- import { compileModuleGraph } from "./compiler.js";
3
+ import {
4
+ compileModuleGraph,
5
+ invalidateDevelopmentCompilationCache,
6
+ } from "./compiler.js";
4
7
  import {
5
8
  collectTransitiveAssetPreloads,
6
9
  collectTransitiveStyleUrls,
7
10
  createAssetResolver,
8
11
  createHydrationBootstrap,
9
12
  createStaticAssetPublicUrlMap,
13
+ buildSharedVendorRuntime,
10
14
  emitBundledClientAssetsWithState,
11
15
  getAssetByPublicUrl,
12
16
  getSharedOutputRoot,
13
17
  normalizeHydrationDataForClient,
14
18
  normalizeClientAssetManifest,
19
+ resetDevelopmentAssetCaches,
20
+ resolveRouteClientImports,
15
21
  resolveSharedVendorModuleUrl,
16
22
  resolveBrowserPackageAssetFilePath,
17
23
  rewriteHydrationDataScript,
18
24
  } from "./client-assets.js";
19
25
  import { loadEvolitConfig } from "./config.js";
26
+ import { DEV_DIRECTORY, INTERNAL_DIRECTORY } from "./constants.js";
20
27
  import { createRouteResolver } from "./render.js";
21
28
  import {
22
29
  createCachedRouteResponse,
@@ -25,6 +32,7 @@ import {
25
32
  resolveResponseCacheRuntime,
26
33
  } from "./response-cache.js";
27
34
  import { createSsrAdapter, renderRouteTreeWithAdapter } from "./ssr-adapter.js";
35
+ import { createNavigationResponseFromDocument } from "./route-segments.js";
28
36
  const CONTENT_TYPE_BY_EXTENSION = new Map([
29
37
  [".css", "text/css; charset=utf-8"],
30
38
  [".svg", "image/svg+xml"],
@@ -208,7 +216,14 @@ export function createResponseCacheController({ responseCacheRuntime }) {
208
216
  };
209
217
  }
210
218
 
211
- export async function createRequestRenderer({ projectRoot, mode, assetManifest, routeResolver, responseCacheRuntime }) {
219
+ export async function createRequestRenderer({
220
+ projectRoot,
221
+ mode,
222
+ assetManifest,
223
+ routeResolver,
224
+ responseCacheRuntime,
225
+ onDevelopmentEvent,
226
+ }) {
212
227
  let currentAssetManifest = normalizeClientAssetManifest(assetManifest);
213
228
  let currentAssetResolver = createAssetResolver(projectRoot, {
214
229
  assetManifest: currentAssetManifest,
@@ -218,25 +233,70 @@ export async function createRequestRenderer({ projectRoot, mode, assetManifest,
218
233
  mode,
219
234
  "@litsx/ssr/hydration",
220
235
  );
236
+ let currentNavigationModuleUrl = await resolveSharedVendorModuleUrl(
237
+ projectRoot,
238
+ mode,
239
+ "evolit/navigation",
240
+ );
221
241
  const devBundledEntries = new Set();
242
+ const devPreparedClientModules = new Set();
243
+ const devClientModuleDependencies = new Map();
222
244
  let devRollupCache = null;
245
+ let devClientAssetWork = Promise.resolve();
246
+ const developmentMetrics = {
247
+ clientArtifactBuilds: 0,
248
+ clientArtifactCacheHits: 0,
249
+ invalidations: 0,
250
+ selectivelyInvalidatedClientEntries: 0,
251
+ };
223
252
  const usesFrameworkRouteResolver = !routeResolver;
253
+ let sharedSegmentRenderCache = null;
254
+
255
+ function enqueueDevClientAssetWork(work) {
256
+ const nextWork = devClientAssetWork.then(work, work);
257
+ devClientAssetWork = nextWork.catch(() => {});
258
+ return nextWork;
259
+ }
260
+
261
+ function reportDevelopmentEvent(event) {
262
+ if (mode === "development" && typeof onDevelopmentEvent === "function") {
263
+ onDevelopmentEvent(event);
264
+ }
265
+ }
266
+
267
+ function getDevClientModuleKey(clientModule) {
268
+ return path.resolve(projectRoot, clientModule);
269
+ }
224
270
  async function createFrameworkRouteResolver() {
225
- return createRouteResolver(projectRoot, mode, {
271
+ const resolver = await createRouteResolver(projectRoot, mode, {
272
+ onDevelopmentEvent,
273
+ segmentRenderCache: sharedSegmentRenderCache,
226
274
  getStaticAssetPublicUrls() {
227
275
  return createStaticAssetPublicUrlMap(currentAssetManifest);
228
276
  },
277
+ assetResolver(moduleId) {
278
+ return currentAssetResolver(moduleId);
279
+ },
229
280
  });
281
+ sharedSegmentRenderCache ??= resolver.segmentRenderCache;
282
+ return resolver;
230
283
  }
231
284
  let effectiveRouteResolver = routeResolver ?? await createFrameworkRouteResolver();
232
285
  const ssrAdapter = createSsrAdapter({
233
286
  assetResolver(moduleId) {
234
287
  return currentAssetResolver(moduleId);
235
288
  },
236
- async resolveAdditionalHead({ result }) {
237
- const clientImports = Array.isArray(result.clientImports) ? result.clientImports : [];
238
- const urls = currentAssetManifest
239
- ? collectTransitiveAssetPreloads(clientImports, currentAssetManifest)
289
+ async resolveAdditionalHead({ routeResult, result }) {
290
+ const clientImports = [
291
+ ...(Array.isArray(result.clientImports) ? result.clientImports : []),
292
+ ...resolveRouteClientImports(routeResult, projectRoot, currentAssetManifest),
293
+ ];
294
+ const hydratedClientImports = Array.isArray(result.clientImports) ? result.clientImports : [];
295
+ const urls = currentAssetManifest && hydratedClientImports.length > 0
296
+ ? [...new Set([
297
+ ...hydratedClientImports,
298
+ ...collectTransitiveAssetPreloads(hydratedClientImports, currentAssetManifest),
299
+ ])]
240
300
  : [];
241
301
  const styleUrls = currentAssetManifest
242
302
  ? collectTransitiveStyleUrls(clientImports, currentAssetManifest)
@@ -246,23 +306,42 @@ export async function createRequestRenderer({ projectRoot, mode, assetManifest,
246
306
  }
247
307
 
248
308
  return [
249
- ...urls.map((href) => `<link rel="modulepreload" href="${href}">`),
250
- ...styleUrls.map((href) => `<link rel="stylesheet" href="${href}">`),
309
+ ...urls.map((href) => `<link rel="modulepreload" href="${href}" data-evolit-route-asset="preload">`),
310
+ ...styleUrls.map((href) => `<link rel="stylesheet" href="${href}" data-evolit-route-asset="style">`),
251
311
  ].join("\n");
252
312
  },
253
- resolveBootstrap({ result }) {
313
+ resolveBootstrap({ routeResult, result }) {
314
+ const routeClientImports = resolveRouteClientImports(
315
+ routeResult,
316
+ projectRoot,
317
+ currentAssetManifest,
318
+ );
254
319
  return createHydrationBootstrap({
255
- hydrationData: normalizeHydrationDataForClient(result.hydrationData, projectRoot),
320
+ hydrationData: normalizeHydrationDataForClient(
321
+ result.hydrationData,
322
+ projectRoot,
323
+ routeClientImports,
324
+ ),
256
325
  assetResolver(moduleId) {
257
326
  return currentAssetResolver(moduleId);
258
327
  },
259
328
  hydrationModuleUrl: currentHydrationModuleUrl,
329
+ navigationModuleUrl: currentNavigationModuleUrl,
260
330
  });
261
331
  },
262
- transformDocument({ result, document }) {
332
+ transformDocument({ routeResult, result, document }) {
333
+ const routeClientImports = resolveRouteClientImports(
334
+ routeResult,
335
+ projectRoot,
336
+ currentAssetManifest,
337
+ );
263
338
  return rewriteHydrationDataScript(
264
339
  document,
265
- normalizeHydrationDataForClient(result.hydrationData, projectRoot),
340
+ normalizeHydrationDataForClient(
341
+ result.hydrationData,
342
+ projectRoot,
343
+ routeClientImports,
344
+ ),
266
345
  );
267
346
  },
268
347
  });
@@ -278,21 +357,52 @@ export async function createRequestRenderer({ projectRoot, mode, assetManifest,
278
357
  return effectiveRouteResolver;
279
358
  },
280
359
  responseCacheController,
281
- async invalidateDevelopmentState() {
360
+ async invalidateDevelopmentState(changedPaths = null) {
282
361
  if (mode !== "development") {
283
362
  return;
284
363
  }
285
364
 
286
- currentAssetManifest = null;
287
- currentAssetResolver = createAssetResolver(projectRoot, {
288
- assetManifest: currentAssetManifest,
289
- });
290
- devBundledEntries.clear();
291
- devRollupCache = null;
365
+ return enqueueDevClientAssetWork(async () => {
366
+ developmentMetrics.invalidations += 1;
367
+ const normalizedChangedPaths = Array.isArray(changedPaths) && changedPaths.length > 0
368
+ ? new Set(changedPaths.map((changedPath) => path.resolve(changedPath)))
369
+ : null;
370
+ invalidateDevelopmentCompilationCache(projectRoot, changedPaths);
371
+ const affectedClientModules = normalizedChangedPaths == null
372
+ ? new Set(devPreparedClientModules)
373
+ : new Set(
374
+ [...devClientModuleDependencies]
375
+ .filter(([, dependencies]) =>
376
+ [...dependencies].some((dependency) => normalizedChangedPaths.has(dependency))
377
+ )
378
+ .map(([clientModule]) => clientModule),
379
+ );
380
+
381
+ if (affectedClientModules.size > 0 || normalizedChangedPaths == null) {
382
+ developmentMetrics.selectivelyInvalidatedClientEntries += affectedClientModules.size;
383
+ currentAssetManifest = null;
384
+ currentAssetResolver = createAssetResolver(projectRoot, {
385
+ assetManifest: currentAssetManifest,
386
+ });
387
+ devRollupCache = null;
388
+ for (const clientModule of affectedClientModules) {
389
+ devPreparedClientModules.delete(clientModule);
390
+ devClientModuleDependencies.delete(clientModule);
391
+ }
392
+ }
292
393
 
293
- if (usesFrameworkRouteResolver) {
294
- effectiveRouteResolver = await createFrameworkRouteResolver();
295
- }
394
+ reportDevelopmentEvent({
395
+ type: "invalidated",
396
+ changedPathCount: normalizedChangedPaths?.size ?? null,
397
+ affectedClientEntryCount: affectedClientModules.size,
398
+ });
399
+
400
+ if (usesFrameworkRouteResolver) {
401
+ const segmentEntryCount = effectiveRouteResolver.invalidateSegmentCache?.(changedPaths) ?? 0;
402
+ reportDevelopmentEvent({ type: "segment-cache-invalidated", entryCount: segmentEntryCount });
403
+ effectiveRouteResolver = await createFrameworkRouteResolver();
404
+ }
405
+ });
296
406
  },
297
407
  async prepareRouteClientArtifacts(routeResult) {
298
408
  if (routeResult.type !== "route" && !routeResult.boundaryModule) {
@@ -304,19 +414,57 @@ export async function createRequestRenderer({ projectRoot, mode, assetManifest,
304
414
  ...(routeResult.boundaryModule ? [routeResult.boundaryModule] : []),
305
415
  ];
306
416
 
307
- for (const clientModule of new Set(clientModules)) {
308
- const clientBuild = await compileModuleGraph(clientModule, {
309
- projectRoot,
310
- mode,
311
- sourceMaps: mode === "development",
312
- target: "client",
313
- });
314
- devBundledEntries.add(
315
- path.relative(clientBuild.outputRoot, clientBuild.entrypoint).split(path.sep).join("/"),
316
- );
417
+ if (mode !== "development") {
418
+ for (const clientModule of new Set(clientModules)) {
419
+ await compileModuleGraph(clientModule, {
420
+ projectRoot,
421
+ mode,
422
+ sourceMaps: false,
423
+ target: "client",
424
+ onDevelopmentEvent,
425
+ });
426
+ }
427
+ return;
317
428
  }
318
429
 
319
- if (mode === "development") {
430
+ return enqueueDevClientAssetWork(async () => {
431
+ let discoveredClientEntry = false;
432
+ for (const clientModule of new Set(clientModules)) {
433
+ const clientModuleKey = getDevClientModuleKey(clientModule);
434
+ if (devPreparedClientModules.has(clientModuleKey)) {
435
+ continue;
436
+ }
437
+
438
+ const clientBuild = await compileModuleGraph(clientModule, {
439
+ projectRoot,
440
+ mode,
441
+ sourceMaps: true,
442
+ target: "client",
443
+ onDevelopmentEvent,
444
+ });
445
+ devPreparedClientModules.add(clientModuleKey);
446
+ devClientModuleDependencies.set(
447
+ clientModuleKey,
448
+ new Set(clientBuild.sourceFiles ?? [clientModuleKey]),
449
+ );
450
+ devBundledEntries.add(
451
+ path.relative(clientBuild.outputRoot, clientBuild.entrypoint).split(path.sep).join("/"),
452
+ );
453
+ discoveredClientEntry = true;
454
+ }
455
+
456
+ if (!discoveredClientEntry && currentAssetManifest) {
457
+ developmentMetrics.clientArtifactCacheHits += 1;
458
+ reportDevelopmentEvent({ type: "client-assets-cache-hit" });
459
+ return;
460
+ }
461
+
462
+ developmentMetrics.clientArtifactBuilds += 1;
463
+ const buildStartedAt = performance.now();
464
+ reportDevelopmentEvent({
465
+ type: "client-assets-building",
466
+ entryCount: devBundledEntries.size,
467
+ });
320
468
  const bundledClientAssets = await emitBundledClientAssetsWithState(projectRoot, {
321
469
  mode: "development",
322
470
  entryClientModules: devBundledEntries,
@@ -336,19 +484,25 @@ export async function createRequestRenderer({ projectRoot, mode, assetManifest,
336
484
  },
337
485
  ) ?? currentHydrationModuleUrl;
338
486
  devRollupCache = bundledClientAssets.rollupCache;
339
- }
487
+ reportDevelopmentEvent({
488
+ type: "client-assets-ready",
489
+ durationMs: Math.round(performance.now() - buildStartedAt),
490
+ entryCount: devBundledEntries.size,
491
+ });
492
+ });
340
493
  },
341
494
  async resolveRoutePolicy(request) {
342
495
  return effectiveRouteResolver.resolveRoutePolicy(request);
343
496
  },
344
497
  async renderRoute(request, routePolicyResult = null) {
345
498
  const shouldPrepareBeforeResolve = mode === "development" && !currentAssetManifest;
499
+ let resolvedRoutePolicyResult = routePolicyResult;
346
500
  if (shouldPrepareBeforeResolve) {
347
- const resolvedRoutePolicyResult = routePolicyResult ?? await effectiveRouteResolver.resolveRoutePolicy(request);
501
+ resolvedRoutePolicyResult ??= await effectiveRouteResolver.resolveRoutePolicy(request);
348
502
  await this.prepareRouteClientArtifacts(resolvedRoutePolicyResult);
349
503
  }
350
504
 
351
- const routeResult = await effectiveRouteResolver.resolveRequest(request);
505
+ const routeResult = await effectiveRouteResolver.resolveRequest(request, resolvedRoutePolicyResult);
352
506
  if (!shouldPrepareBeforeResolve || routeResult.boundaryModule) {
353
507
  await this.prepareRouteClientArtifacts(routeResult);
354
508
  }
@@ -361,6 +515,9 @@ export async function createRequestRenderer({ projectRoot, mode, assetManifest,
361
515
  get assetManifest() {
362
516
  return currentAssetManifest;
363
517
  },
518
+ get developmentMetrics() {
519
+ return mode === "development" ? { ...developmentMetrics } : null;
520
+ },
364
521
  };
365
522
  }
366
523
 
@@ -370,7 +527,26 @@ export async function createDeploymentRuntime({
370
527
  assetManifest,
371
528
  responseCacheRuntime,
372
529
  routeResolver,
530
+ onDevelopmentEvent,
373
531
  } = {}) {
532
+ function reportDevelopmentEvent(event) {
533
+ if (mode === "development" && typeof onDevelopmentEvent === "function") {
534
+ onDevelopmentEvent(event);
535
+ }
536
+ }
537
+
538
+ if (mode === "development") {
539
+ reportDevelopmentEvent({ type: "initializing" });
540
+ const developmentRoot = path.join(projectRoot, INTERNAL_DIRECTORY, DEV_DIRECTORY);
541
+ await fs.rm(developmentRoot, { recursive: true, force: true });
542
+ invalidateDevelopmentCompilationCache(projectRoot);
543
+ resetDevelopmentAssetCaches(projectRoot);
544
+ await buildSharedVendorRuntime(projectRoot, "development", {
545
+ additionalEntrySpecifiers: [],
546
+ });
547
+ reportDevelopmentEvent({ type: "vendor-runtime-ready" });
548
+ }
549
+
374
550
  const effectiveResponseCacheRuntime = responseCacheRuntime
375
551
  ?? await resolveResponseCacheRuntime(
376
552
  projectRoot,
@@ -378,8 +554,18 @@ export async function createDeploymentRuntime({
378
554
  await loadEvolitConfig(projectRoot),
379
555
  );
380
556
  const runtimeState = {
381
- assetManifest: normalizeClientAssetManifest(assetManifest),
557
+ assetManifest: mode === "development" ? null : normalizeClientAssetManifest(assetManifest),
382
558
  };
559
+
560
+ function formatResponseForRequest(request, response) {
561
+ const representation = request.headers.get("accept")?.includes("application/vnd.evolit.navigation+json")
562
+ ? createNavigationResponseFromDocument(response)
563
+ : response;
564
+ // The same URL has an HTML document representation and a navigation
565
+ // delta representation. Tell intermediary HTTP caches that Accept selects
566
+ // between them.
567
+ return addNavigationVaryHeader(representation);
568
+ }
383
569
  const assets = createPublicAssetOrigin({
384
570
  projectRoot,
385
571
  mode,
@@ -393,18 +579,19 @@ export async function createDeploymentRuntime({
393
579
  assetManifest: runtimeState.assetManifest,
394
580
  responseCacheRuntime: effectiveResponseCacheRuntime,
395
581
  routeResolver,
582
+ onDevelopmentEvent,
396
583
  });
397
584
 
398
585
  return {
399
586
  assets,
400
587
  cache: renderer.responseCacheController,
401
588
  renderer,
402
- async invalidateDevelopmentState() {
589
+ async invalidateDevelopmentState(changedPaths = null) {
403
590
  if (mode !== "development") {
404
591
  return;
405
592
  }
406
593
 
407
- await renderer.invalidateDevelopmentState();
594
+ await renderer.invalidateDevelopmentState(changedPaths);
408
595
  if (typeof effectiveResponseCacheRuntime.store.clear === "function") {
409
596
  await effectiveResponseCacheRuntime.store.clear();
410
597
  }
@@ -422,7 +609,7 @@ export async function createDeploymentRuntime({
422
609
  const cachedResponse = await renderer.responseCacheController.read(request, routePolicyResult);
423
610
  if (cachedResponse) {
424
611
  if (cachedResponse.fresh) {
425
- return cachedResponse.response;
612
+ return formatResponseForRequest(request, cachedResponse.response);
426
613
  }
427
614
 
428
615
  if (
@@ -446,12 +633,15 @@ export async function createDeploymentRuntime({
446
633
  revalidationTasks.set(cachedResponse.cacheKey, revalidationTask);
447
634
  }
448
635
 
449
- return cachedResponse.response;
636
+ return formatResponseForRequest(request, cachedResponse.response);
450
637
  }
451
638
 
452
639
  const { routeResult, response } = await renderer.renderRoute(request, routePolicyResult);
453
640
  runtimeState.assetManifest = normalizeClientAssetManifest(renderer.assetManifest);
454
- return renderer.responseCacheController.write(request, routeResult, response);
641
+ return formatResponseForRequest(
642
+ request,
643
+ await renderer.responseCacheController.write(request, routeResult, response),
644
+ );
455
645
  },
456
646
  async close() {
457
647
  const revalidationTasks = this.revalidationTasks;
@@ -461,3 +651,16 @@ export async function createDeploymentRuntime({
461
651
  },
462
652
  };
463
653
  }
654
+
655
+ function addNavigationVaryHeader(response) {
656
+ if (!response?.headers) return response;
657
+ const fields = String(response.headers.vary ?? "")
658
+ .split(",")
659
+ .map((field) => field.trim())
660
+ .filter(Boolean);
661
+ if (!fields.some((field) => field.toLowerCase() === "accept")) fields.push("accept");
662
+ return {
663
+ ...response,
664
+ headers: { ...response.headers, vary: fields.join(", ") },
665
+ };
666
+ }
@@ -0,0 +1,96 @@
1
+ import { terminal } from "./terminal.js";
2
+
3
+ /**
4
+ * Creates the default development-event sink used by the CLI and dev server.
5
+ * Keeping presentation here lets integrations replace it with onDevelopmentEvent
6
+ * without losing any lifecycle, build, or request diagnostics.
7
+ */
8
+ export function createDevelopmentEventReporter() {
9
+ return (event) => {
10
+ switch (event.type) {
11
+ case "server-starting":
12
+ console.log(`${terminal.cyan("▲")} ${terminal.bold("Evolit")} ${terminal.cyan(event.mode ?? "dev")}`);
13
+ break;
14
+ case "usage":
15
+ console.log(`${terminal.bold("Usage:")} evolit <directory>`);
16
+ console.log(` evolit ${terminal.cyan("init")} <directory>`);
17
+ console.log(` evolit ${terminal.cyan("<dev|build|start>")} [--port 3000]`);
18
+ break;
19
+ case "site-created":
20
+ console.log(`${terminal.green("✓")} Created evolit site: ${terminal.cyan(event.directory)}`);
21
+ break;
22
+ case "build-ready":
23
+ console.log(`${terminal.green("✓")} Built Evolit app: ${terminal.cyan(event.manifestPath)}`);
24
+ break;
25
+ case "initializing":
26
+ console.log(`${terminal.cyan("[evolit]")} ${terminal.yellow("○")} Preparing development runtime…`);
27
+ break;
28
+ case "vendor-runtime-ready":
29
+ console.log(`${terminal.cyan("[evolit]")} ${terminal.green("✓")} Vendor runtime ready.`);
30
+ break;
31
+ case "client-assets-building":
32
+ console.log(`${terminal.cyan("[evolit]")} ${terminal.magenta("○")} Compiling client assets (${event.entryCount} entries)…`);
33
+ break;
34
+ case "client-assets-ready":
35
+ console.log(`${terminal.cyan("[evolit]")} ${terminal.green("✓")} Client assets compiled in ${terminal.dim(`${event.durationMs}ms`)} (${event.entryCount} entries).`);
36
+ break;
37
+ case "client-assets-cache-hit":
38
+ console.log(`${terminal.cyan("[evolit]")} ${terminal.blue("●")} Client assets served from cache.`);
39
+ break;
40
+ case "segment-cache-hit":
41
+ console.log(`${terminal.cyan("[evolit]")} ${terminal.blue("●")} Reused route segment ${terminal.dim(event.modulePath)}.`);
42
+ break;
43
+ case "segment-cache-miss":
44
+ console.log(`${terminal.cyan("[evolit]")} ${terminal.magenta("○")} Rendering route segment ${terminal.dim(event.modulePath)} ${terminal.dim(`(${event.reason})`)}.`);
45
+ break;
46
+ case "segment-cache-invalidated":
47
+ console.log(`${terminal.cyan("[evolit]")} ${terminal.yellow("△")} Invalidated ${event.entryCount} cached route segments.`);
48
+ break;
49
+ case "invalidated":
50
+ console.log(
51
+ `${terminal.cyan("[evolit]")} ${terminal.yellow("△")} Source change detected${event.changedPathCount == null ? "" : ` (${event.changedPathCount} files)`}; invalidated ${event.affectedClientEntryCount} client entries.`,
52
+ );
53
+ break;
54
+ case "unmanaged-import":
55
+ console.warn(
56
+ `${terminal.yellow("[evolit]")} ${terminal.yellow("⚠")} Unmanaged import ${terminal.cyan(JSON.stringify(event.resolvedImportPath))} from ${terminal.cyan(JSON.stringify(event.sourcePath))} is outside the project root. Evolit does not watch it; changes may not trigger a rebuild, and it may be unavailable in another checkout or deployment. It will be emitted under ${terminal.cyan("/_evolit/static/__unmanaged__/")}.`,
57
+ );
58
+ break;
59
+ case "server-ready":
60
+ console.log(`${terminal.green("✓")} Ready on ${terminal.cyan(`http://localhost:${event.port}`)}`);
61
+ break;
62
+ case "request": {
63
+ const statusColor = event.status >= 500
64
+ ? terminal.red
65
+ : event.status >= 400
66
+ ? terminal.yellow
67
+ : event.status >= 300
68
+ ? terminal.blue
69
+ : terminal.green;
70
+ console.log(
71
+ `${terminal.cyan("[evolit]")} ${terminal.dim(event.method)} ${event.pathname} ${statusColor(event.status)} in ${terminal.dim(`${event.durationMs}ms`)}${event.cacheState ? ` ${terminal.blue(`(${event.cacheState})`)}` : ""}`,
72
+ );
73
+ break;
74
+ }
75
+ case "request-error":
76
+ console.error(
77
+ `${terminal.red("[evolit]")} ${terminal.red("✖")} ${event.method} ${event.pathname} failed`,
78
+ event.error,
79
+ );
80
+ break;
81
+ case "fatal-error":
82
+ console.error(
83
+ `${terminal.red("[evolit]")} ${terminal.red("✖")} ${event.error instanceof Error ? event.error.stack ?? event.error.message : String(event.error)}`,
84
+ );
85
+ break;
86
+ case "route-error":
87
+ console.error(
88
+ `${terminal.red("[evolit]")} ${terminal.red("✖")} Route rendering failed ${terminal.dim(`(${event.digest})`)} at ${terminal.cyan(event.pathname)}`,
89
+ event.error,
90
+ );
91
+ break;
92
+ default:
93
+ break;
94
+ }
95
+ };
96
+ }