@jay-framework/dev-server 0.25.1 → 0.25.3

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.ts CHANGED
@@ -186,6 +186,13 @@ interface DevServer {
186
186
  freezeStore?: FreezeStore;
187
187
  /** Public API for design board applications and CLI (DL#128) */
188
188
  service: DevServerService;
189
+ /**
190
+ * Express handler that renders the designed error page for a status (DL#218). Mount
191
+ * `errorPageHandler(404)` as a catch-all **after** all route registrations so unmatched paths
192
+ * get the branded 404 instead of Express's default. Falls back to plain text when no error page
193
+ * matches the status.
194
+ */
195
+ errorPageHandler: (status: number) => RequestHandler;
189
196
  }
190
197
  declare function mkDevServer(rawOptions: DevServerOptions): Promise<DevServer>;
191
198
 
package/dist/index.js CHANGED
@@ -13,7 +13,7 @@ import { createRequire as createRequire$1 } from "node:module";
13
13
  import * as fs from "node:fs";
14
14
  import fs__default$1 from "node:fs";
15
15
  import { scanRoutes, createRoute, routeToExpressRoute } from "@jay-framework/stack-route-scanner";
16
- import { makePassthroughInstanceComponent, actionRegistry, scanPlugins, serializeHeadTags, mergeHeadTags, serializeScripts, discoverPluginsWithInit, sortPluginsByDependencies, executePluginServerInits, runInitCallbacks, runShutdownCallbacks, clearLifecycleCallbacks, clearServiceRegistry, clearClientInitData, runLoadParams, DevSlowlyChangingPhase, preparePluginClientInits, registerService, getServiceRegistry, parseCookies, renderFastChangingData, getClientInitData, slowRenderInstances, normalizeInstancePropNames } from "@jay-framework/stack-server-runtime";
16
+ import { makePassthroughInstanceComponent, actionRegistry, scanPlugins, serializeHeadTags, mergeHeadTags, serializeScripts, discoverPluginsWithInit, sortPluginsByDependencies, executePluginServerInits, runInitCallbacks, runShutdownCallbacks, clearLifecycleCallbacks, clearServiceRegistry, clearClientInitData, runLoadParams, DevSlowlyChangingPhase, preparePluginClientInits, registerService, getServiceRegistry, parseCookies, renderFastChangingData, getClientInitData, slowRenderInstances, normalizeInstancePropNames, resolveErrorPageToken, buildErrorViewState } from "@jay-framework/stack-server-runtime";
17
17
  import * as fs$1 from "node:fs/promises";
18
18
  import fs__default from "node:fs/promises";
19
19
  import jsBeautify from "js-beautify";
@@ -4405,10 +4405,22 @@ class DevServerService {
4405
4405
  let _watchLinkedFiles = () => {
4406
4406
  };
4407
4407
  async function initRoutes(pagesBaseFolder) {
4408
- return await scanRoutes(pagesBaseFolder, {
4408
+ const { routes } = await scanRoutes(pagesBaseFolder, {
4409
4409
  jayHtmlFilename: "page.jay-html",
4410
4410
  compFilename: "page.ts"
4411
4411
  });
4412
+ return routes;
4413
+ }
4414
+ async function initErrorPages(pagesBaseFolder) {
4415
+ const { errorPages } = await scanRoutes(pagesBaseFolder, {
4416
+ jayHtmlFilename: "page.jay-html",
4417
+ compFilename: "page.ts"
4418
+ });
4419
+ const map = /* @__PURE__ */ new Map();
4420
+ for (const ep of errorPages) {
4421
+ map.set(ep.token, createRoute(`/_errors/${ep.token}`, ep.jayHtmlPath, ep.compPath));
4422
+ }
4423
+ return map;
4412
4424
  }
4413
4425
  async function scanPluginRoutes(projectRoot, projectRoutes) {
4414
4426
  const plugins = await scanPlugins({ projectRoot, includeDevDeps: true });
@@ -4538,12 +4550,81 @@ function defaults(options) {
4538
4550
  httpServer: options.httpServer
4539
4551
  };
4540
4552
  }
4541
- function handleOtherResponseCodes(res, renderedResult) {
4542
- if (renderedResult.kind === `ServerError`)
4543
- res.status(renderedResult.status).end("server error");
4544
- else if (renderedResult.kind === `ClientError`)
4545
- res.status(renderedResult.status).end("client error");
4546
- else res.status(renderedResult.status).end("redirect to " + renderedResult.location);
4553
+ let _renderDevErrorPage = async (res, info) => {
4554
+ if (!res.headersSent)
4555
+ res.status(info.status).type("text/plain").send(info.message || info.title || "Error");
4556
+ };
4557
+ async function renderDevErrorPage(vite, options, errorRoutes, projectInit, allPluginsWithInit, allPluginClientInits, res, info) {
4558
+ const plainText = () => {
4559
+ if (!res.headersSent)
4560
+ res.status(info.status).type("text/plain").send(info.message || info.title || "Error");
4561
+ };
4562
+ try {
4563
+ const token = resolveErrorPageToken(errorRoutes.keys(), info.status);
4564
+ if (!token) return plainText();
4565
+ const route = errorRoutes.get(token);
4566
+ const partsResult = await loadPageParts(
4567
+ vite,
4568
+ route,
4569
+ options.pagesRootFolder,
4570
+ options.projectRootFolder,
4571
+ options.jayRollupConfig
4572
+ );
4573
+ if (!partsResult.val) {
4574
+ getLogger().warn(
4575
+ `[DevServer] Error page "${token}" failed to load: ${partsResult.validations.join("; ")}`
4576
+ );
4577
+ return plainText();
4578
+ }
4579
+ const { parts, clientTrackByMap, usedPackages, scripts } = partsResult.val;
4580
+ const pluginsForPage = filterPluginsForPage(
4581
+ allPluginClientInits,
4582
+ allPluginsWithInit,
4583
+ usedPackages
4584
+ );
4585
+ const fullViewState = buildErrorViewState(info, {});
4586
+ await sendResponse(
4587
+ vite,
4588
+ res,
4589
+ info.url,
4590
+ route.jayHtmlPath,
4591
+ route.jayHtmlPath,
4592
+ parts,
4593
+ fullViewState,
4594
+ {},
4595
+ clientTrackByMap,
4596
+ projectInit,
4597
+ pluginsForPage,
4598
+ options,
4599
+ routeToExpressRoute(route),
4600
+ getRouteDir(route),
4601
+ {},
4602
+ void 0,
4603
+ void 0,
4604
+ void 0,
4605
+ scripts,
4606
+ info.status
4607
+ );
4608
+ } catch (err) {
4609
+ getLogger().error(
4610
+ `[DevServer] Error page render failed (${info.status} ${info.url}): ${err?.message}`
4611
+ );
4612
+ plainText();
4613
+ }
4614
+ }
4615
+ async function handleOtherResponseCodes(res, renderedResult, url) {
4616
+ if (renderedResult.kind === `Redirect`) {
4617
+ res.status(renderedResult.status).end("redirect to " + renderedResult.location);
4618
+ return;
4619
+ }
4620
+ await _renderDevErrorPage(res, {
4621
+ status: renderedResult.status,
4622
+ url,
4623
+ message: renderedResult.message,
4624
+ title: renderedResult.title,
4625
+ code: renderedResult.code,
4626
+ details: renderedResult.details
4627
+ });
4547
4628
  }
4548
4629
  function filterPluginsForPage(allPluginClientInits, allPluginsWithInit, usedPackages) {
4549
4630
  const pluginsByPackage = /* @__PURE__ */ new Map();
@@ -4671,7 +4752,12 @@ function mkRoute(route, vite, slowlyPhase, options, slowRenderCache, freezeStore
4671
4752
  } catch (e2) {
4672
4753
  vite?.ssrFixStacktrace(e2);
4673
4754
  getLogger().error(e2.stack);
4674
- res.status(500).end(e2.stack);
4755
+ const errUrl = (req.originalUrl || req.url || "/").split("?")[0];
4756
+ await _renderDevErrorPage(res, {
4757
+ status: 500,
4758
+ url: errUrl,
4759
+ message: e2.message
4760
+ });
4675
4761
  timing?.end();
4676
4762
  }
4677
4763
  };
@@ -4734,7 +4820,7 @@ async function handleCachedRequest(vite, route, options, cachedEntry, pageParams
4734
4820
  );
4735
4821
  timing?.recordFastRender(Date.now() - fastStart);
4736
4822
  if (renderedFast.kind !== "PhaseOutput") {
4737
- handleOtherResponseCodes(res, renderedFast);
4823
+ await handleOtherResponseCodes(res, renderedFast, url);
4738
4824
  timing?.end();
4739
4825
  return;
4740
4826
  }
@@ -4810,7 +4896,7 @@ async function handlePreRenderRequest(vite, route, options, slowlyPhase, slowRen
4810
4896
  );
4811
4897
  if (renderedSlowly.kind !== "PhaseOutput") {
4812
4898
  timing?.recordSlowRender(Date.now() - slowStart);
4813
- handleOtherResponseCodes(res, renderedSlowly);
4899
+ await handleOtherResponseCodes(res, renderedSlowly, url);
4814
4900
  timing?.end();
4815
4901
  return;
4816
4902
  }
@@ -4920,7 +5006,7 @@ async function handleClientOnlyRequest(vite, route, options, slowlyPhase, pagePa
4920
5006
  );
4921
5007
  if (renderedSlowly.kind !== "PhaseOutput") {
4922
5008
  timing?.recordSlowRender(Date.now() - slowStart);
4923
- handleOtherResponseCodes(res, renderedSlowly);
5009
+ await handleOtherResponseCodes(res, renderedSlowly, url);
4924
5010
  timing?.end();
4925
5011
  return;
4926
5012
  }
@@ -4941,7 +5027,7 @@ async function handleClientOnlyRequest(vite, route, options, slowlyPhase, pagePa
4941
5027
  );
4942
5028
  timing?.recordFastRender(Date.now() - fastStart);
4943
5029
  if (renderedFast.kind !== "PhaseOutput") {
4944
- handleOtherResponseCodes(res, renderedFast);
5030
+ await handleOtherResponseCodes(res, renderedFast, url);
4945
5031
  timing?.end();
4946
5032
  return;
4947
5033
  }
@@ -4985,7 +5071,7 @@ async function handleClientOnlyRequest(vite, route, options, slowlyPhase, pagePa
4985
5071
  res.status(200).set({ "Content-Type": "text/html" }).send(compiledPageHtml);
4986
5072
  timing?.end();
4987
5073
  }
4988
- async function sendResponse(vite, res, url, jayHtmlPath, sourceJayHtmlPath, pageParts, viewState, carryForward, clientTrackByMap, projectInit, pluginsForPage, options, routePattern, routeDir, slowViewState, timing, preLoadedContent, headTags, scripts) {
5074
+ async function sendResponse(vite, res, url, jayHtmlPath, sourceJayHtmlPath, pageParts, viewState, carryForward, clientTrackByMap, projectInit, pluginsForPage, options, routePattern, routeDir, slowViewState, timing, preLoadedContent, headTags, scripts, statusCode = 200) {
4989
5075
  let pageHtml;
4990
5076
  try {
4991
5077
  const jayHtmlDir = path__default.dirname(jayHtmlPath);
@@ -5048,7 +5134,7 @@ async function sendResponse(vite, res, url, jayHtmlPath, sourceJayHtmlPath, page
5048
5134
  const viteStart = Date.now();
5049
5135
  const compiledPageHtml = await vite.transformIndexHtml(!!url ? url : "/", pageHtml);
5050
5136
  timing?.recordViteClient(Date.now() - viteStart);
5051
- res.status(200).set({ "Content-Type": "text/html" }).send(compiledPageHtml);
5137
+ res.status(statusCode).set({ "Content-Type": "text/html" }).send(compiledPageHtml);
5052
5138
  timing?.end();
5053
5139
  }
5054
5140
  async function handleFrozenRequest(vite, route, options, freezeStore, slowRenderCache, freezeId, pageParams, format, res, url, timing) {
@@ -5230,6 +5316,7 @@ async function mkDevServer(rawOptions) {
5230
5316
  setupActionRouter(vite, buildFolder);
5231
5317
  const projectRoutes = await initRoutes(pagesRootFolder);
5232
5318
  const filteredProjectRoutes = buildFolder ? projectRoutes.filter((route) => !route.jayHtmlPath.startsWith(buildFolder)) : projectRoutes;
5319
+ const errorRoutes = await initErrorPages(pagesRootFolder);
5233
5320
  const pluginRoutes = await scanPluginRoutes(projectRootFolder, filteredProjectRoutes);
5234
5321
  const routes = [...filteredProjectRoutes, ...pluginRoutes];
5235
5322
  const slowlyPhase = new DevSlowlyChangingPhase();
@@ -5243,6 +5330,16 @@ async function mkDevServer(rawOptions) {
5243
5330
  const projectInit = lifecycleManager.getProjectInit() ?? void 0;
5244
5331
  const pluginsWithInit = lifecycleManager.getPluginsWithInit();
5245
5332
  const pluginClientInits = preparePluginClientInits(pluginsWithInit);
5333
+ _renderDevErrorPage = (res, info) => renderDevErrorPage(
5334
+ vite,
5335
+ options,
5336
+ errorRoutes,
5337
+ projectInit,
5338
+ pluginsWithInit,
5339
+ pluginClientInits,
5340
+ res,
5341
+ info
5342
+ );
5246
5343
  const freezeStore = buildFolder ? new FreezeStore(buildFolder) : void 0;
5247
5344
  if (freezeStore) {
5248
5345
  setupFreezeEndpoint(vite, freezeStore);
@@ -5314,7 +5411,11 @@ async function mkDevServer(rawOptions) {
5314
5411
  routes: devServerRoutes,
5315
5412
  lifecycleManager,
5316
5413
  freezeStore,
5317
- service
5414
+ service,
5415
+ errorPageHandler: (status) => async (req, res) => {
5416
+ const url = (req.originalUrl || req.url || "/").split("?")[0];
5417
+ await _renderDevErrorPage(res, { status, url });
5418
+ }
5318
5419
  };
5319
5420
  }
5320
5421
  function setupGracefulShutdown(lifecycleManager) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jay-framework/dev-server",
3
- "version": "0.25.1",
3
+ "version": "0.25.3",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "main": "dist/index.js",
@@ -23,24 +23,24 @@
23
23
  "test:watch": "vitest"
24
24
  },
25
25
  "dependencies": {
26
- "@jay-framework/compiler-jay-stack": "^0.25.1",
27
- "@jay-framework/compiler-shared": "^0.25.1",
28
- "@jay-framework/component": "^0.25.1",
29
- "@jay-framework/fullstack-component": "^0.25.1",
30
- "@jay-framework/logger": "^0.25.1",
31
- "@jay-framework/runtime": "^0.25.1",
32
- "@jay-framework/stack-client-runtime": "^0.25.1",
33
- "@jay-framework/stack-route-scanner": "^0.25.1",
34
- "@jay-framework/stack-server-build": "^0.25.1",
35
- "@jay-framework/stack-server-runtime": "^0.25.1",
36
- "@jay-framework/view-state-merge": "^0.25.1",
26
+ "@jay-framework/compiler-jay-stack": "^0.25.3",
27
+ "@jay-framework/compiler-shared": "^0.25.3",
28
+ "@jay-framework/component": "^0.25.3",
29
+ "@jay-framework/fullstack-component": "^0.25.3",
30
+ "@jay-framework/logger": "^0.25.3",
31
+ "@jay-framework/runtime": "^0.25.3",
32
+ "@jay-framework/stack-client-runtime": "^0.25.3",
33
+ "@jay-framework/stack-route-scanner": "^0.25.3",
34
+ "@jay-framework/stack-server-build": "^0.25.3",
35
+ "@jay-framework/stack-server-runtime": "^0.25.3",
36
+ "@jay-framework/view-state-merge": "^0.25.3",
37
37
  "busboy": "^1.6.0",
38
38
  "vite": "^5.0.11"
39
39
  },
40
40
  "devDependencies": {
41
- "@jay-framework/dev-environment": "^0.25.1",
42
- "@jay-framework/jay-cli": "^0.25.1",
43
- "@jay-framework/stack-client-runtime": "^0.25.1",
41
+ "@jay-framework/dev-environment": "^0.25.3",
42
+ "@jay-framework/jay-cli": "^0.25.3",
43
+ "@jay-framework/stack-client-runtime": "^0.25.3",
44
44
  "@playwright/test": "^1.58.2",
45
45
  "@types/busboy": "^1.5.4",
46
46
  "@types/express": "^5.0.2",