@vistagenic/vista 0.2.12 → 0.2.13

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.
Files changed (77) hide show
  1. package/bin/vista.js +171 -35
  2. package/dist/bin/build-rsc-flashpack.d.ts +4 -0
  3. package/dist/bin/build-rsc-flashpack.js +29 -0
  4. package/dist/bin/build-rsc.js +47 -16
  5. package/dist/bin/build.js +36 -13
  6. package/dist/bin/devtools-indicator-snippet.js +30 -0
  7. package/dist/bin/file-scanner.d.ts +1 -1
  8. package/dist/bin/file-scanner.js +8 -0
  9. package/dist/bin/flashpack-runner.d.ts +1 -0
  10. package/dist/bin/flashpack-runner.js +61 -0
  11. package/dist/bin/server-component-plugin.d.ts +6 -4
  12. package/dist/bin/server-component-plugin.js +22 -69
  13. package/dist/bin/webpack.config.d.ts +3 -0
  14. package/dist/bin/webpack.config.js +12 -3
  15. package/dist/build/manifest.d.ts +17 -3
  16. package/dist/build/manifest.js +99 -23
  17. package/dist/build/rsc/compiler.d.ts +2 -0
  18. package/dist/build/rsc/compiler.js +25 -5
  19. package/dist/build/rsc/react-client-reference-manifest.d.ts +22 -0
  20. package/dist/build/rsc/react-client-reference-manifest.js +219 -0
  21. package/dist/build/rsc/server-manifest.d.ts +23 -2
  22. package/dist/build/rsc/server-manifest.js +162 -24
  23. package/dist/build/standalone.d.ts +31 -0
  24. package/dist/build/standalone.js +334 -0
  25. package/dist/client/rsc-router.d.ts +31 -0
  26. package/dist/client/rsc-router.js +89 -0
  27. package/dist/config.d.ts +23 -0
  28. package/dist/config.js +106 -5
  29. package/dist/constants.d.ts +2 -0
  30. package/dist/constants.js +3 -1
  31. package/dist/flashpack/command.d.ts +8 -0
  32. package/dist/flashpack/command.js +134 -0
  33. package/dist/flashpack/runtime.d.ts +39 -0
  34. package/dist/flashpack/runtime.js +249 -0
  35. package/dist/server/app-router-runtime.d.ts +26 -0
  36. package/dist/server/app-router-runtime.js +321 -0
  37. package/dist/server/artifact-validator.js +21 -1
  38. package/dist/server/cache.d.ts +10 -0
  39. package/dist/server/cache.js +270 -0
  40. package/dist/server/client-boundary.js +20 -2
  41. package/dist/server/engine.js +236 -159
  42. package/dist/server/fetch-policy.d.ts +2 -0
  43. package/dist/server/fetch-policy.js +123 -0
  44. package/dist/server/index.d.ts +7 -0
  45. package/dist/server/index.js +131 -22
  46. package/dist/server/module-boundary-validator.d.ts +15 -0
  47. package/dist/server/module-boundary-validator.js +262 -0
  48. package/dist/server/module-compile-hook.d.ts +7 -0
  49. package/dist/server/module-compile-hook.js +662 -0
  50. package/dist/server/ppr.d.ts +18 -0
  51. package/dist/server/ppr.js +59 -0
  52. package/dist/server/request-context.d.ts +31 -0
  53. package/dist/server/request-context.js +95 -0
  54. package/dist/server/rsc-engine-flashpack.d.ts +4 -0
  55. package/dist/server/rsc-engine-flashpack.js +27 -0
  56. package/dist/server/rsc-engine.d.ts +2 -0
  57. package/dist/server/rsc-engine.js +589 -317
  58. package/dist/server/rsc-upstream.js +539 -233
  59. package/dist/server/runtime-actions.d.ts +5 -0
  60. package/dist/server/runtime-actions.js +80 -0
  61. package/dist/server/runtime-artifacts.d.ts +11 -0
  62. package/dist/server/runtime-artifacts.js +35 -0
  63. package/dist/server/segment-config.d.ts +37 -0
  64. package/dist/server/segment-config.js +205 -0
  65. package/dist/server/spawn-permissions.d.ts +2 -0
  66. package/dist/server/spawn-permissions.js +21 -0
  67. package/dist/server/static-cache.d.ts +15 -1
  68. package/dist/server/static-cache.js +83 -3
  69. package/dist/server/static-generator.js +254 -100
  70. package/dist/server/structure-validator.d.ts +1 -1
  71. package/dist/server/structure-validator.js +26 -5
  72. package/dist/server/structure-watch.js +1 -1
  73. package/dist/server/typed-api-runtime.d.ts +1 -0
  74. package/dist/server/typed-api-runtime.js +145 -25
  75. package/dist/server/vista-import-map.d.ts +1 -0
  76. package/dist/server/vista-import-map.js +123 -0
  77. package/package.json +13 -1
@@ -25,11 +25,13 @@ const child_process_1 = require("child_process");
25
25
  const url_1 = require("url");
26
26
  const middleware_runner_1 = require("./middleware-runner");
27
27
  const image_optimizer_1 = require("./image-optimizer");
28
+ const ppr_1 = require("./ppr");
28
29
  const registry_1 = require("../font/registry");
29
30
  const logger_1 = require("./logger");
30
31
  const not_found_page_1 = require("./not-found-page");
31
32
  const static_cache_1 = require("./static-cache");
32
33
  const static_generator_1 = require("./static-generator");
34
+ const react_client_reference_manifest_1 = require("../build/rsc/react-client-reference-manifest");
33
35
  const constants_1 = require("../constants");
34
36
  const CjsModule = require('module');
35
37
  // ---------------------------------------------------------------------------
@@ -66,11 +68,18 @@ const config_1 = require("../config");
66
68
  const dev_error_1 = require("../dev-error");
67
69
  const artifact_validator_1 = require("./artifact-validator");
68
70
  const root_resolver_1 = require("./root-resolver");
71
+ const runtime_artifacts_1 = require("./runtime-artifacts");
72
+ const spawn_permissions_1 = require("./spawn-permissions");
69
73
  const structure_watch_1 = require("./structure-watch");
70
74
  const structure_log_1 = require("./structure-log");
71
75
  const error_boundary_1 = require("../components/error-boundary");
72
76
  const route_suspense_1 = require("../components/route-suspense");
73
77
  const typed_api_runtime_1 = require("./typed-api-runtime");
78
+ const module_compile_hook_1 = require("./module-compile-hook");
79
+ const request_context_1 = require("./request-context");
80
+ const app_router_runtime_1 = require("./app-router-runtime");
81
+ const fetch_policy_1 = require("./fetch-policy");
82
+ const vista_import_map_1 = require("./vista-import-map");
74
83
  // Support CSS imports on server runtime
75
84
  // - Regular .css: ignored (handled by PostCSS)
76
85
  // - .module.css: return empty class mapping (webpack build handles real mappings)
@@ -84,7 +93,7 @@ require.extensions['.css'] = (m, filename) => {
84
93
  * Includes the PostCSS globals and CSS Modules extracted stylesheet.
85
94
  */
86
95
  function getCSSLinks(projectRoot) {
87
- const root = projectRoot || process.cwd();
96
+ const root = projectRoot || process.env.VISTA_ARTIFACT_ROOT || process.cwd();
88
97
  const links = ['<link rel="stylesheet" href="/styles.css" />'];
89
98
  // Check for extracted CSS modules (from MiniCssExtractPlugin)
90
99
  const chunksDir = path_1.default.join(root, constants_1.BUILD_DIR, 'static', 'chunks');
@@ -114,6 +123,47 @@ function resolvePort(raw, fallback) {
114
123
  }
115
124
  return value;
116
125
  }
126
+ function removeStaticArtifacts(vistaDirRoot, urlPath) {
127
+ const staticDir = path_1.default.join(vistaDirRoot, 'static', 'pages');
128
+ const safePath = urlPath === '/' ? '/index' : urlPath;
129
+ const artifactPaths = ['.html', '.meta.json', '.rsc'].map((extension) => path_1.default.join(staticDir, `${safePath}${extension}`));
130
+ for (const absolutePath of artifactPaths) {
131
+ try {
132
+ if (fs_1.default.existsSync(absolutePath)) {
133
+ fs_1.default.unlinkSync(absolutePath);
134
+ }
135
+ }
136
+ catch {
137
+ // ignore cache cleanup failures
138
+ }
139
+ }
140
+ }
141
+ function parseRevalidationHeader(rawValue) {
142
+ if (!rawValue) {
143
+ return [];
144
+ }
145
+ try {
146
+ const parsed = JSON.parse(rawValue);
147
+ return Array.isArray(parsed) ? parsed.map((entry) => String(entry || '').trim()).filter(Boolean) : [];
148
+ }
149
+ catch {
150
+ return [];
151
+ }
152
+ }
153
+ function applyUpstreamRevalidations(upstream, vistaDirRoot) {
154
+ const revalidatedPaths = parseRevalidationHeader(upstream.headers.get('x-vista-revalidated-paths'));
155
+ for (const urlPath of revalidatedPaths) {
156
+ (0, static_cache_1.invalidateCachedPage)(urlPath);
157
+ removeStaticArtifacts(vistaDirRoot, urlPath);
158
+ }
159
+ const revalidatedTags = parseRevalidationHeader(upstream.headers.get('x-vista-revalidated-tags'));
160
+ for (const tag of revalidatedTags) {
161
+ const affectedPaths = (0, static_cache_1.invalidateCachedPagesByTag)(tag);
162
+ for (const urlPath of affectedPaths) {
163
+ removeStaticArtifacts(vistaDirRoot, urlPath);
164
+ }
165
+ }
166
+ }
117
167
  function normalizeModuleCachePath(filePath) {
118
168
  return filePath.replace(/\\/g, '/').toLowerCase();
119
169
  }
@@ -156,15 +206,24 @@ function resolveFromWorkspace(specifier, cwd) {
156
206
  }
157
207
  function setupTypeScriptRuntime(cwd) {
158
208
  try {
159
- const swcPath = require.resolve('@swc-node/register', { paths: [cwd] });
160
- require(swcPath);
209
+ const swcRegisterPath = resolveFromWorkspace('@swc-node/register/register', cwd);
210
+ const typescriptPath = resolveFromWorkspace('typescript', cwd);
211
+ const { register } = require(swcRegisterPath);
212
+ const ts = require(typescriptPath);
213
+ register({
214
+ module: ts.ModuleKind.CommonJS,
215
+ jsx: ts.JsxEmit.ReactJSX,
216
+ moduleResolution: ts.ModuleResolutionKind.Node16,
217
+ esModuleInterop: true,
218
+ allowJs: true,
219
+ });
161
220
  return;
162
221
  }
163
222
  catch {
164
223
  // fallback
165
224
  }
166
225
  try {
167
- const tsNodePath = require.resolve('ts-node', { paths: [cwd] });
226
+ const tsNodePath = resolveFromWorkspace('ts-node', cwd);
168
227
  require(tsNodePath).register({
169
228
  transpileOnly: true,
170
229
  compilerOptions: {
@@ -172,6 +231,7 @@ function setupTypeScriptRuntime(cwd) {
172
231
  jsx: 'react-jsx',
173
232
  moduleResolution: 'node16',
174
233
  esModuleInterop: true,
234
+ allowJs: true,
175
235
  },
176
236
  });
177
237
  return;
@@ -180,8 +240,8 @@ function setupTypeScriptRuntime(cwd) {
180
240
  // fallback
181
241
  }
182
242
  try {
183
- require.resolve('tsx', { paths: [cwd] });
184
- require('tsx/cjs');
243
+ const tsxPath = resolveFromWorkspace('tsx/cjs', cwd);
244
+ require(tsxPath);
185
245
  return;
186
246
  }
187
247
  catch (e) {
@@ -191,6 +251,9 @@ function setupTypeScriptRuntime(cwd) {
191
251
  }
192
252
  let reactResolutionInstalled = false;
193
253
  let originalResolveFilename = null;
254
+ function resolveVistaInternalRequest(request) {
255
+ return (0, vista_import_map_1.resolveVistaSourceRequest)(request, path_1.default.resolve(__dirname, '..'));
256
+ }
194
257
  function installSingleReactResolution(cwd) {
195
258
  if (reactResolutionInstalled)
196
259
  return;
@@ -211,6 +274,9 @@ function installSingleReactResolution(cwd) {
211
274
  }
212
275
  originalResolveFilename = CjsModule._resolveFilename;
213
276
  CjsModule._resolveFilename = function (request, parent, isMain, options) {
277
+ const vistaResolvedPath = resolveVistaInternalRequest(request);
278
+ if (vistaResolvedPath)
279
+ return vistaResolvedPath;
214
280
  if (request === 'react')
215
281
  return reactPath;
216
282
  if (request === 'react-dom')
@@ -357,7 +423,7 @@ function normalizeSSRManifest(manifest) {
357
423
  return manifest;
358
424
  }
359
425
  function loadSSRManifestFromDisk(absolutePath) {
360
- const manifest = JSON.parse(fs_1.default.readFileSync(absolutePath, 'utf-8'));
426
+ const manifest = (0, react_client_reference_manifest_1.normalizeReactServerConsumerManifest)(JSON.parse(fs_1.default.readFileSync(absolutePath, 'utf-8')));
361
427
  return normalizeSSRManifest(manifest);
362
428
  }
363
429
  function matchPattern(pathname, pattern) {
@@ -422,80 +488,152 @@ function extractParams(pathname, route) {
422
488
  }
423
489
  return params;
424
490
  }
425
- async function createRouteElement(route, context, isDev, rootLayout, cwd) {
491
+ async function createRenderableRouteModuleElement(modulePath, context, options = {}) {
426
492
  const { params, searchParams, req } = context;
427
- if (isDev) {
428
- clearProjectRequireCache(cwd);
493
+ const RouteModule = require(modulePath);
494
+ const RouteComponent = RouteModule.default;
495
+ if (!RouteComponent) {
496
+ throw new Error(`Route module does not export default component: ${modulePath}`);
429
497
  }
430
- const PageModule = require(route.pagePath);
431
- const PageComponent = PageModule.default;
432
- if (!PageComponent) {
433
- throw new Error(`Page module does not export default component: ${route.pagePath}`);
498
+ if (options.evaluateMetadata && typeof RouteModule.generateMetadata === 'function') {
499
+ await RouteModule.generateMetadata({ params, searchParams }, RouteModule.metadata ?? {});
434
500
  }
435
- const pageProps = typeof PageModule.getServerProps === 'function'
436
- ? await PageModule.getServerProps({ query: req.query, params, req })
501
+ const routeProps = typeof RouteModule.getServerProps === 'function'
502
+ ? await RouteModule.getServerProps({ query: req.query, params, req })
437
503
  : {};
438
- let metadata = { ...(rootLayout.metadata || {}) };
439
- if (PageModule.metadata) {
440
- metadata = { ...metadata, ...PageModule.metadata };
441
- }
442
- if (typeof PageModule.generateMetadata === 'function') {
443
- try {
444
- const dynamicMeta = await PageModule.generateMetadata({ params, searchParams }, metadata);
445
- metadata = { ...metadata, ...dynamicMeta };
446
- }
447
- catch (error) {
448
- console.error('[vista:rsc] Error in generateMetadata:', error);
449
- }
504
+ const moduleStem = path_1.default.basename(modulePath).replace(/\.[jt]sx?$/, '');
505
+ if (moduleStem === 'default' || moduleStem === 'not-found') {
506
+ const eagerResult = await RouteComponent({
507
+ ...routeProps,
508
+ params,
509
+ searchParams,
510
+ });
511
+ return react_1.default.isValidElement(eagerResult)
512
+ ? eagerResult
513
+ : react_1.default.createElement(react_1.default.Fragment, null, eagerResult);
450
514
  }
451
- let element = react_1.default.createElement(PageComponent, {
452
- ...pageProps,
515
+ return react_1.default.createElement(RouteComponent, {
516
+ ...routeProps,
453
517
  params,
454
518
  searchParams,
455
519
  });
456
- // Wrap page in loading/error boundaries if discovered
457
- if (route.loadingPath || route.errorPath) {
458
- const loadingComponent = route.loadingPath
459
- ? (() => {
460
- try {
461
- return require(route.loadingPath).default;
462
- }
463
- catch {
464
- return undefined;
465
- }
466
- })()
467
- : undefined;
468
- const errorComponent = route.errorPath
469
- ? (() => {
470
- try {
471
- return require(route.errorPath).default;
472
- }
473
- catch {
474
- return undefined;
475
- }
476
- })()
477
- : undefined;
478
- if (loadingComponent) {
479
- element = react_1.default.createElement(route_suspense_1.RouteSuspense, {
480
- loadingComponent,
481
- children: element,
482
- });
483
- }
484
- if (errorComponent) {
485
- element = react_1.default.createElement(error_boundary_1.RouteErrorBoundary, {
486
- fallbackComponent: errorComponent,
487
- children: element,
488
- });
489
- }
520
+ }
521
+ function applySegmentBoundaries(dir, element) {
522
+ const loadingPath = (0, app_router_runtime_1.resolveConventionModule)(dir, 'loading');
523
+ const errorPath = (0, app_router_runtime_1.resolveConventionModule)(dir, 'error');
524
+ const loadingComponent = loadingPath
525
+ ? (() => {
526
+ try {
527
+ return require(loadingPath).default;
528
+ }
529
+ catch {
530
+ return undefined;
531
+ }
532
+ })()
533
+ : undefined;
534
+ const errorComponent = errorPath
535
+ ? (() => {
536
+ try {
537
+ return require(errorPath).default;
538
+ }
539
+ catch {
540
+ return undefined;
541
+ }
542
+ })()
543
+ : undefined;
544
+ let wrappedElement = element;
545
+ if (loadingComponent) {
546
+ wrappedElement = react_1.default.createElement(route_suspense_1.RouteSuspense, {
547
+ loadingComponent,
548
+ children: wrappedElement,
549
+ });
550
+ }
551
+ if (errorComponent) {
552
+ wrappedElement = react_1.default.createElement(error_boundary_1.RouteErrorBoundary, {
553
+ fallbackComponent: errorComponent,
554
+ children: wrappedElement,
555
+ });
490
556
  }
491
- for (let i = route.layoutPaths.length - 1; i >= 0; i--) {
492
- const layoutPath = route.layoutPaths[i];
557
+ return wrappedElement;
558
+ }
559
+ async function renderAppSubtreeElement(input) {
560
+ const appDir = path_1.default.join(input.cwd, 'app');
561
+ let element = await createRenderableRouteModuleElement(input.entryFilePath, {
562
+ params: input.params,
563
+ searchParams: input.searchParams,
564
+ req: input.req,
565
+ }, {
566
+ evaluateMetadata: input.evaluateLeafMetadata,
567
+ });
568
+ const directoryChain = (0, app_router_runtime_1.resolveDirectoryChain)(input.subtreeRootDir, input.entryFilePath);
569
+ for (let i = directoryChain.length - 1; i >= 0; i--) {
570
+ const dir = directoryChain[i];
571
+ element = applySegmentBoundaries(dir, element);
572
+ const layoutPath = (0, app_router_runtime_1.resolveConventionModule)(dir, 'root') ?? (0, app_router_runtime_1.resolveConventionModule)(dir, 'layout');
573
+ if (!layoutPath || path_1.default.resolve(layoutPath) === path_1.default.resolve(input.entryFilePath)) {
574
+ continue;
575
+ }
493
576
  const LayoutModule = require(layoutPath);
494
577
  const LayoutComponent = LayoutModule.default;
495
- if (!LayoutComponent)
578
+ if (!LayoutComponent) {
496
579
  continue;
497
- element = react_1.default.createElement(LayoutComponent, { params, searchParams }, element);
580
+ }
581
+ const slotProps = {};
582
+ if (!input.disableParallelSlots) {
583
+ const slotMatches = (0, app_router_runtime_1.resolveParallelSlotMatches)({
584
+ appDir,
585
+ layoutPath,
586
+ pathname: input.pathname,
587
+ });
588
+ for (const slotMatch of slotMatches) {
589
+ slotProps[slotMatch.slotName] = await renderAppSubtreeElement({
590
+ subtreeRootDir: slotMatch.slotRootDir,
591
+ entryFilePath: slotMatch.filePath,
592
+ pathname: input.pathname,
593
+ params: {
594
+ ...input.params,
595
+ ...slotMatch.params,
596
+ },
597
+ searchParams: input.searchParams,
598
+ req: input.req,
599
+ cwd: input.cwd,
600
+ evaluateLeafMetadata: true,
601
+ });
602
+ }
603
+ }
604
+ element = react_1.default.createElement(LayoutComponent, {
605
+ params: input.params,
606
+ searchParams: input.searchParams,
607
+ ...slotProps,
608
+ }, element);
498
609
  }
610
+ return element;
611
+ }
612
+ async function createRouteElement(route, context, isDev, rootLayout, runtimeRoot, options = {}) {
613
+ const { params, searchParams, req } = context;
614
+ if (isDev) {
615
+ clearProjectRequireCache(runtimeRoot);
616
+ }
617
+ const PageModule = require(route.pagePath);
618
+ let metadata = { ...(rootLayout.metadata || {}) };
619
+ if (PageModule.metadata) {
620
+ metadata = { ...metadata, ...PageModule.metadata };
621
+ }
622
+ if (typeof PageModule.generateMetadata === 'function') {
623
+ const dynamicMeta = await PageModule.generateMetadata({ params, searchParams }, metadata);
624
+ metadata = { ...metadata, ...dynamicMeta };
625
+ }
626
+ const element = await renderAppSubtreeElement({
627
+ subtreeRootDir: path_1.default.join(runtimeRoot, 'app'),
628
+ entryFilePath: route.pagePath,
629
+ pathname: req.path,
630
+ params,
631
+ searchParams,
632
+ req,
633
+ cwd: runtimeRoot,
634
+ evaluateLeafMetadata: false,
635
+ disableParallelSlots: options.disableParallelSlots,
636
+ });
499
637
  return { element, metadata, rootMode: rootLayout.mode };
500
638
  }
501
639
  function injectBeforeClosingTag(html, tagName, injection) {
@@ -540,9 +678,19 @@ function createHtmlDocument(appHtml, metadataHtml, chunkFiles, rootMode = 'legac
540
678
  </body>
541
679
  </html>`;
542
680
  }
543
- async function handleApiRoute(req, res, cwd, isDev, typedApiConfig) {
681
+ function appendVaryHeader(existing, nextValue) {
682
+ const values = String(existing || '')
683
+ .split(',')
684
+ .map((entry) => entry.trim())
685
+ .filter(Boolean);
686
+ if (!values.includes(nextValue)) {
687
+ values.push(nextValue);
688
+ }
689
+ return values.join(', ');
690
+ }
691
+ async function handleApiRoute(req, res, runtimeRoot, isDev, typedApiConfig) {
544
692
  try {
545
- const legacyApiPath = (0, typed_api_runtime_1.resolveLegacyApiRoutePath)(cwd, req.path);
693
+ const legacyApiPath = (0, typed_api_runtime_1.resolveLegacyApiRoutePath)(runtimeRoot, req.path);
546
694
  if (legacyApiPath) {
547
695
  await (0, typed_api_runtime_1.runLegacyApiRoute)({
548
696
  req,
@@ -555,7 +703,7 @@ async function handleApiRoute(req, res, cwd, isDev, typedApiConfig) {
555
703
  const typedHandled = await (0, typed_api_runtime_1.runTypedApiRoute)({
556
704
  req,
557
705
  res,
558
- cwd,
706
+ cwd: runtimeRoot,
559
707
  isDev,
560
708
  config: typedApiConfig,
561
709
  });
@@ -569,17 +717,33 @@ async function handleApiRoute(req, res, cwd, isDev, typedApiConfig) {
569
717
  res.status(500).json({ error: 'Internal Server Error' });
570
718
  }
571
719
  }
572
- function spawnUpstream(cwd, upstreamPort) {
720
+ function spawnUpstream(cwd, runtimeRoot, upstreamPort) {
573
721
  const upstreamScript = path_1.default.join(__dirname, 'rsc-upstream.js');
574
- return (0, child_process_1.spawn)(process.execPath, ['--conditions', 'react-server', upstreamScript, '--port', String(upstreamPort)], {
575
- cwd,
576
- env: {
577
- ...process.env,
578
- NODE_ENV: process.env.NODE_ENV || 'development',
579
- RSC_UPSTREAM_PORT: String(upstreamPort),
580
- },
581
- stdio: 'pipe',
582
- });
722
+ try {
723
+ return {
724
+ child: (0, child_process_1.spawn)(process.execPath, ['--conditions', 'react-server', upstreamScript, '--port', String(upstreamPort)], {
725
+ cwd,
726
+ env: {
727
+ ...process.env,
728
+ NODE_ENV: process.env.NODE_ENV || 'development',
729
+ RSC_UPSTREAM_PORT: String(upstreamPort),
730
+ VISTA_ARTIFACT_ROOT: cwd,
731
+ VISTA_RUNTIME_ROOT: runtimeRoot,
732
+ },
733
+ stdio: 'pipe',
734
+ }),
735
+ unavailableReason: null,
736
+ };
737
+ }
738
+ catch (error) {
739
+ if ((0, spawn_permissions_1.isPermissionDeniedSpawnError)(error)) {
740
+ return {
741
+ child: null,
742
+ unavailableReason: `spawn blocked by environment permissions (${(0, spawn_permissions_1.getErrorMessage)(error)})`,
743
+ };
744
+ }
745
+ throw error;
746
+ }
583
747
  }
584
748
  // ---------------------------------------------------------------------------
585
749
  // Flight-Based SSR Rendering Helpers
@@ -598,13 +762,6 @@ async function renderFlightToHTMLStream(upstreamOrigin, pathname, search, metada
598
762
  if (!upstream.ok && upstream.status !== 404) {
599
763
  throw new Error(`Upstream returned ${upstream.status}: ${await upstream.text()}`);
600
764
  }
601
- // Short-circuit 404: serve the styled standalone page directly
602
- // (the Flight element is a bare <div> with no document shell, so streaming it
603
- // would produce HTML without <html>/<body> tags → browser default margins)
604
- if (upstream.status === 404) {
605
- res.status(404).type('text/html').send((0, not_found_page_1.getStyledNotFoundHTML)());
606
- return;
607
- }
608
765
  if (!upstream.body) {
609
766
  throw new Error('Upstream returned empty body');
610
767
  }
@@ -774,10 +931,16 @@ function renderCompilePendingHTML() {
774
931
  }
775
932
  function startRSCServer(options = {}) {
776
933
  const app = (0, express_1.default)();
777
- const cwd = process.cwd();
934
+ const cwd = path_1.default.resolve(options.projectRoot || process.env.VISTA_ARTIFACT_ROOT || process.cwd());
935
+ const runtimeRoot = (0, runtime_artifacts_1.resolveRuntimeProjectRoot)(cwd, options.runtimeRoot);
778
936
  const isDev = process.env.NODE_ENV !== 'production';
779
- const vistaConfig = (0, config_1.loadConfig)(cwd);
937
+ const vistaConfig = (0, config_1.loadConfig)(runtimeRoot);
938
+ const cacheComponentsConfig = (0, config_1.resolveCacheComponentsConfig)(vistaConfig);
939
+ const engineVariant = (0, config_1.resolveAndApplyEngineVariant)(vistaConfig);
780
940
  const typedApiConfig = (0, config_1.resolveTypedApiConfig)(vistaConfig);
941
+ if (process.env.VISTA_DEBUG) {
942
+ (0, logger_1.logInfo)(`Engine variant: ${engineVariant}`);
943
+ }
781
944
  // Clean stale hot-update files from previous runs
782
945
  cleanHotUpdateFiles(cwd);
783
946
  // Request logger — logs GET/POST with timing
@@ -785,8 +948,13 @@ function startRSCServer(options = {}) {
785
948
  const port = resolvePort(String(options.port || vistaConfig.server?.port || 3003), 3003);
786
949
  const upstreamPort = resolvePort(String(process.env.RSC_UPSTREAM_PORT || port + 1), port + 1);
787
950
  const upstreamOrigin = `http://127.0.0.1:${upstreamPort}`;
788
- installSingleReactResolution(cwd);
789
- setupTypeScriptRuntime(cwd);
951
+ installSingleReactResolution(runtimeRoot);
952
+ setupTypeScriptRuntime(runtimeRoot);
953
+ (0, module_compile_hook_1.installModuleCompileHook)({
954
+ cwd: runtimeRoot,
955
+ cacheComponentsEnabled: cacheComponentsConfig.enabled,
956
+ });
957
+ (0, fetch_policy_1.installSegmentFetchPolicyShim)();
790
958
  installSSRWebpackShim();
791
959
  const serverManifestPath = path_1.default.join(cwd, constants_1.BUILD_DIR, 'server', 'server-manifest.json');
792
960
  if (!fs_1.default.existsSync(serverManifestPath)) {
@@ -794,7 +962,9 @@ function startRSCServer(options = {}) {
794
962
  process.exit(1);
795
963
  }
796
964
  try {
797
- (0, artifact_validator_1.assertVistaArtifacts)(cwd, 'rsc');
965
+ if (!isDev || !options.compiler) {
966
+ (0, artifact_validator_1.assertVistaArtifacts)(cwd, 'rsc');
967
+ }
798
968
  }
799
969
  catch (error) {
800
970
  console.error(error.message);
@@ -853,32 +1023,40 @@ function startRSCServer(options = {}) {
853
1023
  (0, logger_1.logInfo)(`Loaded ${loadedStaticPages} pre-rendered page(s) from cache`);
854
1024
  }
855
1025
  }
856
- const upstreamChild = spawnUpstream(cwd, upstreamPort);
857
- upstreamChild.stdout.setEncoding('utf8');
858
- upstreamChild.stderr.setEncoding('utf8');
859
- // Always capture stderr so we can log crash reasons
860
1026
  let upstreamStderr = '';
861
- if (process.env.VISTA_DEBUG) {
862
- upstreamChild.stdout.on('data', (chunk) => process.stdout.write(chunk));
863
- upstreamChild.stderr.on('data', (chunk) => {
864
- upstreamStderr += chunk;
865
- process.stderr.write(chunk);
1027
+ const upstreamLaunch = spawnUpstream(cwd, runtimeRoot, upstreamPort);
1028
+ const upstreamChild = upstreamLaunch.child;
1029
+ let upstreamUnavailableReason = upstreamLaunch.unavailableReason;
1030
+ if (upstreamChild) {
1031
+ upstreamChild.stdout.setEncoding('utf8');
1032
+ upstreamChild.stderr.setEncoding('utf8');
1033
+ // Always capture stderr so we can log crash reasons
1034
+ if (process.env.VISTA_DEBUG) {
1035
+ upstreamChild.stdout.on('data', (chunk) => process.stdout.write(chunk));
1036
+ upstreamChild.stderr.on('data', (chunk) => {
1037
+ upstreamStderr += chunk;
1038
+ process.stderr.write(chunk);
1039
+ });
1040
+ }
1041
+ else {
1042
+ upstreamChild.stdout.on('data', () => { }); // drain
1043
+ upstreamChild.stderr.on('data', (chunk) => {
1044
+ upstreamStderr += chunk;
1045
+ });
1046
+ }
1047
+ upstreamChild.on('exit', (code, signal) => {
1048
+ if (code !== 0 || (code === null && !shutdownCalled)) {
1049
+ upstreamUnavailableReason = `process exited unexpectedly (code=${code ?? 'unknown'}, signal=${signal ?? 'null'})`;
1050
+ (0, logger_1.logError)(`Upstream exited unexpectedly (code=${code}, signal=${signal ?? 'null'})`);
1051
+ if (upstreamStderr.trim()) {
1052
+ (0, logger_1.logError)(`Upstream stderr:\n${upstreamStderr.trim()}`);
1053
+ }
1054
+ }
866
1055
  });
867
1056
  }
868
- else {
869
- upstreamChild.stdout.on('data', () => { }); // drain
870
- upstreamChild.stderr.on('data', (chunk) => {
871
- upstreamStderr += chunk;
872
- });
1057
+ else if (upstreamUnavailableReason) {
1058
+ (0, logger_1.logError)(`[vista:rsc] ${upstreamUnavailableReason}`);
873
1059
  }
874
- upstreamChild.on('exit', (code, signal) => {
875
- if (code !== 0 && code !== null) {
876
- (0, logger_1.logError)(`Upstream exited unexpectedly (code=${code}, signal=${signal ?? 'null'})`);
877
- if (upstreamStderr.trim()) {
878
- (0, logger_1.logError)(`Upstream stderr:\n${upstreamStderr.trim()}`);
879
- }
880
- }
881
- });
882
1060
  // Graceful shutdown — populated after all resources are created
883
1061
  let shutdownCalled = false;
884
1062
  let httpServer = null;
@@ -888,7 +1066,7 @@ function startRSCServer(options = {}) {
888
1066
  return;
889
1067
  shutdownCalled = true;
890
1068
  // 1. Kill upstream RSC child
891
- if (!upstreamChild.killed) {
1069
+ if (upstreamChild && !upstreamChild.killed) {
892
1070
  upstreamChild.kill('SIGTERM');
893
1071
  setTimeout(() => {
894
1072
  if (!upstreamChild.killed)
@@ -971,7 +1149,7 @@ function startRSCServer(options = {}) {
971
1149
  reloadTimer = setTimeout(() => {
972
1150
  (0, logger_1.logEvent)('Source changed, reloading...');
973
1151
  pushSSE('reload');
974
- }, 140);
1152
+ }, 70);
975
1153
  };
976
1154
  try {
977
1155
  const chokidar = require('chokidar');
@@ -1124,12 +1302,13 @@ function startRSCServer(options = {}) {
1124
1302
  res.status(404).type('text/css').send('/* CSS not found */');
1125
1303
  });
1126
1304
  // Image optimization endpoint
1127
- const imageHandler = (0, image_optimizer_1.createImageHandler)(cwd, isDev);
1305
+ const imageHandler = (0, image_optimizer_1.createImageHandler)(runtimeRoot, isDev);
1128
1306
  app.get(constants_1.IMAGE_ENDPOINT, imageHandler);
1129
- app.use(express_1.default.static(path_1.default.join(cwd, 'public')));
1307
+ app.use(express_1.default.static(path_1.default.join(runtimeRoot, 'public')));
1130
1308
  app.use(`${constants_1.URL_PREFIX}/static`, express_1.default.static(path_1.default.join(cwd, constants_1.BUILD_DIR, 'static')));
1131
1309
  app.use(constants_1.URL_PREFIX, express_1.default.static(path_1.default.join(cwd, constants_1.BUILD_DIR)));
1132
1310
  app.use(express_1.default.static(path_1.default.join(cwd, constants_1.BUILD_DIR)));
1311
+ const getUpstreamUnavailableMessage = () => `RSC upstream unavailable (${upstreamOrigin}/rsc): ${upstreamUnavailableReason || 'upstream process is not running'}`;
1133
1312
  const proxyRSCRequest = async (req, res) => {
1134
1313
  if (isDev && options.compiler) {
1135
1314
  if (clientCompileState === 'compiling') {
@@ -1141,6 +1320,10 @@ function startRSCServer(options = {}) {
1141
1320
  return;
1142
1321
  }
1143
1322
  }
1323
+ if (upstreamUnavailableReason) {
1324
+ res.status(503).type('text/plain').send(getUpstreamUnavailableMessage());
1325
+ return;
1326
+ }
1144
1327
  try {
1145
1328
  const fetchOptions = {
1146
1329
  method: req.method,
@@ -1164,6 +1347,7 @@ function startRSCServer(options = {}) {
1164
1347
  fetchOptions.body = Buffer.concat(chunks);
1165
1348
  }
1166
1349
  const upstream = await withTimeout(`${upstreamOrigin}${req.originalUrl}`, fetchOptions);
1350
+ applyUpstreamRevalidations(upstream, vistaDirRoot);
1167
1351
  res.status(upstream.status);
1168
1352
  const contentType = upstream.headers.get('content-type');
1169
1353
  if (contentType)
@@ -1176,10 +1360,7 @@ function startRSCServer(options = {}) {
1176
1360
  stream_1.Readable.fromWeb(upstream.body).pipe(res);
1177
1361
  }
1178
1362
  catch (error) {
1179
- res
1180
- .status(503)
1181
- .type('text/plain')
1182
- .send(`RSC upstream unavailable (${upstreamOrigin}/rsc): ${error.message}`);
1363
+ res.status(503).type('text/plain').send(getUpstreamUnavailableMessage());
1183
1364
  }
1184
1365
  };
1185
1366
  app.get('/rsc*', proxyRSCRequest);
@@ -1199,7 +1380,7 @@ function startRSCServer(options = {}) {
1199
1380
  req.path.startsWith('/_rsc')) {
1200
1381
  return next();
1201
1382
  }
1202
- const result = await (0, middleware_runner_1.runMiddleware)(req, cwd, isDev);
1383
+ const result = await (0, middleware_runner_1.runMiddleware)(req, runtimeRoot, isDev);
1203
1384
  const finalized = (0, middleware_runner_1.applyMiddlewareResult)(result, req, res);
1204
1385
  if (finalized)
1205
1386
  return; // response already sent (redirect / short-circuit)
@@ -1214,206 +1395,241 @@ function startRSCServer(options = {}) {
1214
1395
  req.path.startsWith('/_rsc')) {
1215
1396
  return next();
1216
1397
  }
1217
- // ======================================================================
1218
- // Structure validation gate (strict-block in dev)
1219
- // ======================================================================
1220
- if (isDev &&
1221
- structureConfig.enabled &&
1222
- structureConfig.mode === 'strict' &&
1223
- currentStructureState?.state === 'error') {
1224
- const overlayMessage = (0, structure_log_1.formatIssuesForOverlay)(currentStructureState, structureConfig.includeWarningsInOverlay);
1225
- const errorInfo = {
1226
- type: 'build',
1227
- message: `Structure Validation Failed\n\n${overlayMessage}`,
1228
- };
1229
- res
1230
- .status(500)
1231
- .type('text/html')
1232
- .send((0, dev_error_1.renderErrorHTML)([errorInfo]));
1233
- return;
1234
- }
1235
- if (isDev && options.compiler) {
1236
- if (clientCompileState === 'compiling') {
1237
- res.status(503).type('text/html').send(renderCompilePendingHTML());
1398
+ await (0, request_context_1.runWithRequestContext)({
1399
+ req,
1400
+ res,
1401
+ cwd: runtimeRoot,
1402
+ vistaDirRoot,
1403
+ urlPath: req.path,
1404
+ }, async () => {
1405
+ // ======================================================================
1406
+ // Structure validation gate (strict-block in dev)
1407
+ // ======================================================================
1408
+ if (isDev &&
1409
+ structureConfig.enabled &&
1410
+ structureConfig.mode === 'strict' &&
1411
+ currentStructureState?.state === 'error') {
1412
+ const overlayMessage = (0, structure_log_1.formatIssuesForOverlay)(currentStructureState, structureConfig.includeWarningsInOverlay);
1413
+ const errorInfo = {
1414
+ type: 'build',
1415
+ message: `Structure Validation Failed\n\n${overlayMessage}`,
1416
+ };
1417
+ res
1418
+ .status(500)
1419
+ .type('text/html')
1420
+ .send((0, dev_error_1.renderErrorHTML)([errorInfo]));
1238
1421
  return;
1239
1422
  }
1240
- if (clientCompileState === 'error') {
1241
- const errorInfos = (clientCompileErrors.length > 0
1242
- ? clientCompileErrors
1243
- : ['Unknown client build error.']).map((message) => ({ type: 'build', message }));
1244
- res.status(500).type('text/html').send((0, dev_error_1.renderErrorHTML)(errorInfos));
1245
- return;
1423
+ if (isDev && options.compiler) {
1424
+ if (clientCompileState === 'compiling') {
1425
+ res.status(503).type('text/html').send(renderCompilePendingHTML());
1426
+ return;
1427
+ }
1428
+ if (clientCompileState === 'error') {
1429
+ const errorInfos = (clientCompileErrors.length > 0
1430
+ ? clientCompileErrors
1431
+ : ['Unknown client build error.']).map((message) => ({ type: 'build', message }));
1432
+ res.status(500).type('text/html').send((0, dev_error_1.renderErrorHTML)(errorInfos));
1433
+ return;
1434
+ }
1246
1435
  }
1247
- }
1248
- if (req.path.startsWith('/api/')) {
1249
- await handleApiRoute(req, res, cwd, isDev, typedApiConfig);
1250
- return;
1251
- }
1252
- // ==================================================================
1253
- // Static / ISR Cache Check
1254
- // ==================================================================
1255
- // Before dynamic rendering, check if we have a pre-rendered page.
1256
- // For ISR pages whose revalidate window has expired, serve the stale
1257
- // cached version immediately and kick off background revalidation.
1258
- // ==================================================================
1259
- if (!isDev) {
1260
- const cached = (0, static_cache_1.getCachedPage)(req.path);
1261
- if (cached.page) {
1262
- if (cached.stale) {
1263
- // ISR: serve stale page immediately, revalidate in background
1264
- const route = matchRoute(req.path, serverManifest.routes);
1265
- if (route && !(0, static_cache_1.isRevalidating)(req.path)) {
1266
- const urlPath = req.path;
1267
- // Fire-and-forget background revalidation
1268
- (0, static_generator_1.revalidatePath)(urlPath, route, undefined, cwd, vistaDirRoot).catch((err) => {
1269
- console.error('[vista:isr] Background revalidation error:', err);
1270
- });
1271
- }
1436
+ const routeHandlerPath = (0, typed_api_runtime_1.resolveLegacyRouteHandlerPath)(runtimeRoot, req.path);
1437
+ if (routeHandlerPath) {
1438
+ try {
1439
+ await (0, typed_api_runtime_1.runLegacyApiRoute)({
1440
+ req,
1441
+ res,
1442
+ apiPath: routeHandlerPath,
1443
+ isDev,
1444
+ });
1445
+ return;
1272
1446
  }
1273
- res
1274
- .status(200)
1275
- .type('text/html')
1276
- .setHeader('X-Vista-Cache', cached.stale ? 'STALE' : 'HIT')
1277
- .send(cached.page.html);
1447
+ catch (error) {
1448
+ console.error('[vista:rsc] Route handler error:', error);
1449
+ res.status(500).json({ error: 'Internal Server Error' });
1450
+ return;
1451
+ }
1452
+ }
1453
+ if (req.path.startsWith('/api/')) {
1454
+ await handleApiRoute(req, res, runtimeRoot, isDev, typedApiConfig);
1278
1455
  return;
1279
1456
  }
1280
- }
1281
- // ==================================================================
1282
- // Flight-Based SSR Path
1283
- // ==================================================================
1284
- // If the Flight SSR client + SSR manifest are available, render pages
1285
- // by fetching the Flight stream from upstream and using
1286
- // renderToPipeableStream for streaming HTML with proper hydration.
1287
- // Falls back to legacy renderToString if Flight SSR is unavailable.
1288
- // ==================================================================
1289
- if (useFlightSSR) {
1290
- try {
1291
- if (isDev && resolvedSSRManifestPath && fs_1.default.existsSync(resolvedSSRManifestPath)) {
1292
- try {
1293
- ssrManifest = loadSSRManifestFromDisk(resolvedSSRManifestPath);
1457
+ const currentRoute = matchRoute(req.path, serverManifest.routes);
1458
+ (0, request_context_1.setCurrentSegmentConfig)(currentRoute?.segmentConfig);
1459
+ // ==================================================================
1460
+ // Static / ISR Cache Check
1461
+ // ==================================================================
1462
+ // Before dynamic rendering, check if we have a pre-rendered page.
1463
+ // For ISR pages whose revalidate window has expired, serve the stale
1464
+ // cached version immediately and kick off background revalidation.
1465
+ // ==================================================================
1466
+ if (!isDev) {
1467
+ const cached = (0, static_cache_1.getCachedPage)(req.path);
1468
+ if (cached.page) {
1469
+ if (cached.stale) {
1470
+ // ISR: serve stale page immediately, revalidate in background
1471
+ const route = currentRoute;
1472
+ if (route && !(0, static_cache_1.isRevalidating)(req.path)) {
1473
+ const urlPath = req.path;
1474
+ // Fire-and-forget background revalidation
1475
+ (0, static_generator_1.revalidatePath)(urlPath, route, undefined, runtimeRoot, vistaDirRoot).catch((err) => {
1476
+ console.error('[vista:isr] Background revalidation error:', err);
1477
+ });
1478
+ }
1294
1479
  }
1295
- catch {
1296
- // Manifest may be mid-write during compilation; keep the last good in-memory copy.
1480
+ const pprRequestMode = (0, ppr_1.resolvePprRequestMode)({
1481
+ headerValue: req.headers['x-vista-prerender'],
1482
+ queryValue: req.query?.__vista_prerender,
1483
+ });
1484
+ const servePprShell = pprRequestMode === 'shell' && Boolean(cached.page.shellHtml);
1485
+ const responseHtml = servePprShell ? cached.page.shellHtml : cached.page.html;
1486
+ res
1487
+ .status(200)
1488
+ .type('text/html')
1489
+ .setHeader('X-Vista-Cache', cached.stale ? 'STALE' : 'HIT');
1490
+ if (cached.page.ppr?.enabled) {
1491
+ const responseMode = pprRequestMode === 'resume' ? 'RESUME' : servePprShell ? 'SHELL' : 'PPR';
1492
+ res.setHeader('X-Vista-Prerender', responseMode);
1493
+ res.setHeader('X-Vista-Prerender-Resume', cached.page.ppr.resumePath);
1494
+ res.setHeader('X-Vista-Prerender-Strategy', cached.page.ppr.strategy);
1495
+ res.setHeader('Vary', appendVaryHeader(res.getHeader('Vary'), 'x-vista-prerender'));
1297
1496
  }
1497
+ res.setHeader('X-Vista-Route-Runtime', currentRoute?.segmentConfig.runtime ?? 'nodejs');
1498
+ res.send(responseHtml);
1499
+ return;
1298
1500
  }
1299
- // Metadata extraction: still done locally so we have <head> content
1300
- const rootLayout = (0, root_resolver_1.resolveRootLayout)(cwd, isDev);
1301
- const route = matchRoute(req.path, serverManifest.routes);
1302
- let metadataHtml = '';
1303
- if (route) {
1304
- if (isDev) {
1305
- clearProjectRequireCache(cwd);
1306
- }
1307
- const PageModule = require(route.pagePath);
1308
- let metadata = { ...(rootLayout.metadata || {}) };
1309
- if (PageModule.metadata) {
1310
- metadata = { ...metadata, ...PageModule.metadata };
1311
- }
1312
- if (typeof PageModule.generateMetadata === 'function') {
1313
- const params = extractParams(req.path, route);
1314
- const searchParams = Object.fromEntries(new URLSearchParams(req.query).entries());
1501
+ }
1502
+ if (upstreamUnavailableReason) {
1503
+ res.status(503).type('text/plain').send(getUpstreamUnavailableMessage());
1504
+ return;
1505
+ }
1506
+ // ==================================================================
1507
+ // Flight-Based SSR Path
1508
+ // ==================================================================
1509
+ // If the Flight SSR client + SSR manifest are available, render pages
1510
+ // by fetching the Flight stream from upstream and using
1511
+ // renderToPipeableStream for streaming HTML with proper hydration.
1512
+ // Falls back to legacy renderToString if Flight SSR is unavailable.
1513
+ // ==================================================================
1514
+ if (useFlightSSR) {
1515
+ try {
1516
+ if (isDev && resolvedSSRManifestPath && fs_1.default.existsSync(resolvedSSRManifestPath)) {
1315
1517
  try {
1518
+ ssrManifest = loadSSRManifestFromDisk(resolvedSSRManifestPath);
1519
+ }
1520
+ catch {
1521
+ // Manifest may be mid-write during compilation; keep the last good in-memory copy.
1522
+ }
1523
+ }
1524
+ // Metadata extraction: still done locally so we have <head> content
1525
+ const rootLayout = (0, root_resolver_1.resolveRootLayout)(runtimeRoot, isDev);
1526
+ const route = currentRoute;
1527
+ let metadataHtml = '';
1528
+ if (route) {
1529
+ if (isDev) {
1530
+ clearProjectRequireCache(runtimeRoot);
1531
+ }
1532
+ const PageModule = require(route.pagePath);
1533
+ let metadata = { ...(rootLayout.metadata || {}) };
1534
+ if (PageModule.metadata) {
1535
+ metadata = { ...metadata, ...PageModule.metadata };
1536
+ }
1537
+ if (typeof PageModule.generateMetadata === 'function') {
1538
+ const params = extractParams(req.path, route);
1539
+ const searchParams = Object.fromEntries(new URLSearchParams(req.query).entries());
1316
1540
  const dynamicMeta = await PageModule.generateMetadata({ params, searchParams }, metadata);
1317
1541
  metadata = { ...metadata, ...dynamicMeta };
1318
1542
  }
1319
- catch (metaErr) {
1320
- console.error('[vista:rsc] Error in generateMetadata:', metaErr);
1321
- }
1543
+ const { generateMetadataHtml } = require('../metadata/generate');
1544
+ metadataHtml = metadata ? generateMetadataHtml(metadata) : '';
1322
1545
  }
1323
- const { generateMetadataHtml } = require('../metadata/generate');
1324
- metadataHtml = metadata ? generateMetadataHtml(metadata) : '';
1325
- }
1326
- // Render the page via Flight stream → SSR
1327
- await renderFlightToHTMLStream(upstreamOrigin, req.path, req.query ? new URLSearchParams(req.query).toString() : '', metadataHtml, findChunkFiles(cwd, isDev), rootLayout.mode, flightSSRClient, ssrManifest, res, isDev);
1328
- return;
1329
- }
1330
- catch (flightError) {
1331
- console.error('[vista:rsc] Flight SSR failed:', flightError.message);
1332
- // If headers haven't been sent yet, show the error overlay directly.
1333
- // This is much better than falling through to legacy renderToString,
1334
- // which will likely hit the same error (e.g. useState in a server component).
1335
- if (isDev && !res.headersSent) {
1336
- const errorInfo = {
1337
- type: 'runtime',
1338
- message: flightError.message || 'Flight SSR Error',
1339
- stack: flightError.stack,
1340
- };
1341
- res.status(500).send((0, dev_error_1.renderErrorHTML)([errorInfo]));
1546
+ // Render the page via Flight stream → SSR
1547
+ await renderFlightToHTMLStream(upstreamOrigin, req.path, req.query ? new URLSearchParams(req.query).toString() : '', metadataHtml, findChunkFiles(cwd, isDev), rootLayout.mode, flightSSRClient, ssrManifest, res, isDev);
1342
1548
  return;
1343
1549
  }
1344
- // If headers were already sent (stream was partially flushed),
1345
- // we can't change the status code, but we can inject an error
1346
- // overlay script at the end of the stream.
1347
- if (isDev && res.headersSent) {
1348
- try {
1349
- const errMsg = (flightError.message || 'Flight SSR Error')
1350
- .replace(/'/g, "\\'")
1351
- .replace(/\n/g, '\\n');
1352
- res.write(`<script>document.body.innerHTML='';document.body.style.background='#1a1a2e';document.body.style.color='#ff6b6b';document.body.style.fontFamily='monospace';document.body.style.padding='40px';document.body.innerHTML='<h2 style="color:#ff6b6b">\\u26a0 Server Error</h2><pre style="white-space:pre-wrap;color:#ffa07a">${errMsg}</pre>';</script>`);
1353
- res.end();
1550
+ catch (flightError) {
1551
+ if (flightError?.name === 'NotFoundError' && !res.headersSent) {
1552
+ try {
1553
+ const rootLayout = (0, root_resolver_1.resolveRootLayout)(runtimeRoot, isDev);
1554
+ const route = currentRoute;
1555
+ if (route) {
1556
+ const segmentNotFoundPath = (0, app_router_runtime_1.resolveNearestSegmentNotFoundPath)(path_1.default.join(runtimeRoot, 'app'), route.routeDir);
1557
+ if (segmentNotFoundPath) {
1558
+ const params = extractParams(req.path, route);
1559
+ const searchParams = Object.fromEntries(new URLSearchParams(req.query).entries());
1560
+ const notFoundResult = await createRouteElement({
1561
+ ...route,
1562
+ pagePath: segmentNotFoundPath,
1563
+ }, { params, searchParams, req }, isDev, rootLayout, runtimeRoot, { disableParallelSlots: true });
1564
+ const html = (0, server_1.renderToString)(notFoundResult.element);
1565
+ const { generateMetadataHtml } = require('../metadata/generate');
1566
+ const metadataHtml = notFoundResult.metadata
1567
+ ? generateMetadataHtml(notFoundResult.metadata)
1568
+ : '';
1569
+ res
1570
+ .status(404)
1571
+ .type('text/html')
1572
+ .send(createHtmlDocument(html, metadataHtml, findChunkFiles(cwd, isDev), notFoundResult.rootMode));
1573
+ return;
1574
+ }
1575
+ }
1576
+ }
1577
+ catch (notFoundError) {
1578
+ console.error('[vista:rsc] Failed to render segment not-found fallback:', notFoundError);
1579
+ }
1354
1580
  }
1355
- catch {
1356
- res.end();
1581
+ console.error('[vista:rsc] Flight SSR failed:', flightError.message);
1582
+ // If headers haven't been sent yet, show the error overlay directly.
1583
+ // This is much better than falling through to legacy renderToString,
1584
+ // which will likely hit the same error (e.g. useState in a server component).
1585
+ if (isDev && !res.headersSent) {
1586
+ const errorInfo = {
1587
+ type: 'runtime',
1588
+ message: flightError.message || 'Flight SSR Error',
1589
+ stack: flightError.stack,
1590
+ };
1591
+ res.status(500).send((0, dev_error_1.renderErrorHTML)([errorInfo]));
1592
+ return;
1593
+ }
1594
+ // If headers were already sent (stream was partially flushed),
1595
+ // we can't change the status code, but we can inject an error
1596
+ // overlay script at the end of the stream.
1597
+ if (isDev && res.headersSent) {
1598
+ try {
1599
+ const errMsg = (flightError.message || 'Flight SSR Error')
1600
+ .replace(/'/g, "\\'")
1601
+ .replace(/\n/g, '\\n');
1602
+ res.write(`<script>document.body.innerHTML='';document.body.style.background='#1a1a2e';document.body.style.color='#ff6b6b';document.body.style.fontFamily='monospace';document.body.style.padding='40px';document.body.innerHTML='<h2 style="color:#ff6b6b">\\u26a0 Server Error</h2><pre style="white-space:pre-wrap;color:#ffa07a">${errMsg}</pre>';</script>`);
1603
+ res.end();
1604
+ }
1605
+ catch {
1606
+ res.end();
1607
+ }
1608
+ return;
1357
1609
  }
1358
- return;
1359
1610
  }
1360
1611
  }
1361
- }
1362
- // ==================================================================
1363
- // Legacy Fallback: Direct renderToString
1364
- // ==================================================================
1365
- // Used when Flight SSR is unavailable or fails. This path does NOT
1366
- // go through the Flight protocol it requires page modules directly
1367
- // and renders them with renderToString (synchronous, no streaming).
1368
- // ==================================================================
1369
- try {
1370
- // Check upstream availability for the legacy path
1371
- await withTimeout(`${upstreamOrigin}/rsc/`, { headers: { Accept: 'text/x-component' } }, 3000);
1372
- }
1373
- catch (error) {
1374
- res
1375
- .status(503)
1376
- .type('text/plain')
1377
- .send(`RSC upstream unavailable (${upstreamOrigin}/rsc): ${error.message}`);
1378
- return;
1379
- }
1380
- try {
1381
- const rootLayout = (0, root_resolver_1.resolveRootLayout)(cwd, isDev);
1382
- const route = matchRoute(req.path, serverManifest.routes);
1383
- if (!route) {
1384
- const resolvedNotFound = (0, root_resolver_1.resolveNotFoundComponent)(cwd, rootLayout, isDev);
1385
- if (resolvedNotFound) {
1386
- const notFoundElement = react_1.default.createElement(resolvedNotFound.component, {
1387
- params: {},
1388
- searchParams: {},
1389
- });
1390
- const wrapped = react_1.default.createElement(rootLayout.component, { params: {}, searchParams: {} }, notFoundElement);
1391
- const html = (0, server_1.renderToString)(wrapped);
1392
- res
1393
- .status(404)
1394
- .type('text/html')
1395
- .send(createHtmlDocument(html, '', findChunkFiles(cwd, isDev), rootLayout.mode));
1396
- return;
1397
- }
1398
- res.status(404).type('text/html').send((0, not_found_page_1.getStyledNotFoundHTML)());
1612
+ // ==================================================================
1613
+ // Legacy Fallback: Direct renderToString
1614
+ // ==================================================================
1615
+ // Used when Flight SSR is unavailable or fails. This path does NOT
1616
+ // go through the Flight protocol it requires page modules directly
1617
+ // and renders them with renderToString (synchronous, no streaming).
1618
+ // ==================================================================
1619
+ try {
1620
+ // Check upstream availability for the legacy path
1621
+ await withTimeout(`${upstreamOrigin}/rsc/`, { headers: { Accept: 'text/x-component' } }, 3000);
1622
+ }
1623
+ catch (error) {
1624
+ res.status(503).type('text/plain').send(getUpstreamUnavailableMessage());
1399
1625
  return;
1400
1626
  }
1401
- const params = extractParams(req.path, route);
1402
- const searchParams = Object.fromEntries(new URLSearchParams(req.query).entries());
1403
- const { element, metadata, rootMode } = await createRouteElement(route, { params, searchParams, req }, isDev, rootLayout, cwd);
1404
- const appHtml = (0, server_1.renderToString)(element);
1405
- const { generateMetadataHtml } = require('../metadata/generate');
1406
- const metadataHtml = metadata ? generateMetadataHtml(metadata) : '';
1407
- res
1408
- .status(200)
1409
- .type('text/html')
1410
- .send(createHtmlDocument(appHtml, metadataHtml, findChunkFiles(cwd, isDev), rootMode));
1411
- }
1412
- catch (error) {
1413
- if (error?.name === 'NotFoundError') {
1414
- try {
1415
- const rootLayout = (0, root_resolver_1.resolveRootLayout)(cwd, isDev);
1416
- const resolvedNotFound = (0, root_resolver_1.resolveNotFoundComponent)(cwd, rootLayout, isDev);
1627
+ try {
1628
+ const rootLayout = (0, root_resolver_1.resolveRootLayout)(runtimeRoot, isDev);
1629
+ const route = currentRoute;
1630
+ (0, request_context_1.setCurrentSegmentConfig)(route?.segmentConfig);
1631
+ if (!route) {
1632
+ const resolvedNotFound = (0, root_resolver_1.resolveNotFoundComponent)(runtimeRoot, rootLayout, isDev);
1417
1633
  if (resolvedNotFound) {
1418
1634
  const notFoundElement = react_1.default.createElement(resolvedNotFound.component, {
1419
1635
  params: {},
@@ -1430,23 +1646,79 @@ function startRSCServer(options = {}) {
1430
1646
  res.status(404).type('text/html').send((0, not_found_page_1.getStyledNotFoundHTML)());
1431
1647
  return;
1432
1648
  }
1433
- catch (notFoundError) {
1434
- console.error('[vista:rsc] Failed to render NotFoundError fallback:', notFoundError);
1435
- }
1436
- }
1437
- console.error('[vista:rsc] Render error:', error);
1438
- if (isDev) {
1439
- const errorInfo = {
1440
- type: 'runtime',
1441
- message: error.message || 'Unknown Server Error',
1442
- stack: error.stack,
1443
- };
1444
- res.status(500).send((0, dev_error_1.renderErrorHTML)([errorInfo]));
1649
+ const params = extractParams(req.path, route);
1650
+ const searchParams = Object.fromEntries(new URLSearchParams(req.query).entries());
1651
+ const { element, metadata, rootMode } = await createRouteElement(route, { params, searchParams, req }, isDev, rootLayout, runtimeRoot);
1652
+ const appHtml = (0, server_1.renderToString)(element);
1653
+ const { generateMetadataHtml } = require('../metadata/generate');
1654
+ const metadataHtml = metadata ? generateMetadataHtml(metadata) : '';
1655
+ res
1656
+ .status(200)
1657
+ .type('text/html')
1658
+ .send(createHtmlDocument(appHtml, metadataHtml, findChunkFiles(cwd, isDev), rootMode));
1445
1659
  }
1446
- else {
1447
- res.status(500).send('<h1>Internal Server Error</h1>');
1660
+ catch (error) {
1661
+ if (error?.name === 'NotFoundError') {
1662
+ try {
1663
+ const rootLayout = (0, root_resolver_1.resolveRootLayout)(runtimeRoot, isDev);
1664
+ const route = currentRoute;
1665
+ (0, request_context_1.setCurrentSegmentConfig)(route?.segmentConfig);
1666
+ if (route) {
1667
+ const segmentNotFoundPath = (0, app_router_runtime_1.resolveNearestSegmentNotFoundPath)(path_1.default.join(runtimeRoot, 'app'), route.routeDir);
1668
+ if (segmentNotFoundPath) {
1669
+ const params = extractParams(req.path, route);
1670
+ const searchParams = Object.fromEntries(new URLSearchParams(req.query).entries());
1671
+ const notFoundResult = await createRouteElement({
1672
+ ...route,
1673
+ pagePath: segmentNotFoundPath,
1674
+ }, { params, searchParams, req }, isDev, rootLayout, runtimeRoot, { disableParallelSlots: true });
1675
+ const html = (0, server_1.renderToString)(notFoundResult.element);
1676
+ const { generateMetadataHtml } = require('../metadata/generate');
1677
+ const metadataHtml = notFoundResult.metadata
1678
+ ? generateMetadataHtml(notFoundResult.metadata)
1679
+ : '';
1680
+ res
1681
+ .status(404)
1682
+ .type('text/html')
1683
+ .send(createHtmlDocument(html, metadataHtml, findChunkFiles(cwd, isDev), notFoundResult.rootMode));
1684
+ return;
1685
+ }
1686
+ }
1687
+ const resolvedNotFound = (0, root_resolver_1.resolveNotFoundComponent)(runtimeRoot, rootLayout, isDev);
1688
+ if (resolvedNotFound) {
1689
+ const notFoundElement = react_1.default.createElement(resolvedNotFound.component, {
1690
+ params: {},
1691
+ searchParams: {},
1692
+ });
1693
+ const wrapped = react_1.default.createElement(rootLayout.component, { params: {}, searchParams: {} }, notFoundElement);
1694
+ const html = (0, server_1.renderToString)(wrapped);
1695
+ res
1696
+ .status(404)
1697
+ .type('text/html')
1698
+ .send(createHtmlDocument(html, '', findChunkFiles(cwd, isDev), rootLayout.mode));
1699
+ return;
1700
+ }
1701
+ res.status(404).type('text/html').send((0, not_found_page_1.getStyledNotFoundHTML)());
1702
+ return;
1703
+ }
1704
+ catch (notFoundError) {
1705
+ console.error('[vista:rsc] Failed to render NotFoundError fallback:', notFoundError);
1706
+ }
1707
+ }
1708
+ console.error('[vista:rsc] Render error:', error);
1709
+ if (isDev) {
1710
+ const errorInfo = {
1711
+ type: 'runtime',
1712
+ message: error.message || 'Unknown Server Error',
1713
+ stack: error.stack,
1714
+ };
1715
+ res.status(500).send((0, dev_error_1.renderErrorHTML)([errorInfo]));
1716
+ }
1717
+ else {
1718
+ res.status(500).send('<h1>Internal Server Error</h1>');
1719
+ }
1448
1720
  }
1449
- }
1721
+ });
1450
1722
  });
1451
1723
  const server = app.listen(port, () => {
1452
1724
  (0, logger_1.printServerReady)({ port, mode: 'rsc', rscFlight: useFlightSSR });