@vistagenic/vista 0.3.2 → 0.3.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.
Files changed (98) hide show
  1. package/bin/vista.js +79 -109
  2. package/dist/ai/embeddings.d.ts +11 -11
  3. package/dist/ai/embeddings.js +71 -71
  4. package/dist/ai/index.d.ts +10 -10
  5. package/dist/ai/index.js +26 -26
  6. package/dist/ai/providers/base.js +85 -85
  7. package/dist/ai/react/react-server.d.ts +1 -1
  8. package/dist/ai/react/react-server.js +17 -17
  9. package/dist/ai/react/use-agent.js +138 -138
  10. package/dist/auth/core.d.ts +140 -140
  11. package/dist/auth/core.js +165 -165
  12. package/dist/auth/index.d.ts +20 -20
  13. package/dist/auth/index.js +369 -369
  14. package/dist/auth/react-server.d.ts +1 -1
  15. package/dist/auth/react-server.js +10 -10
  16. package/dist/auth/react.d.ts +18 -18
  17. package/dist/auth/react.js +71 -71
  18. package/dist/bin/build-rsc-flashpack.js +6 -0
  19. package/dist/bin/build-rsc.js +537 -492
  20. package/dist/bin/build.js +355 -353
  21. package/dist/bin/generate.js +513 -510
  22. package/dist/bin/webpack.config.js +4 -3
  23. package/dist/build/manifest.d.ts +169 -169
  24. package/dist/build/manifest.js +423 -423
  25. package/dist/build/rsc/client-manifest.d.ts +62 -62
  26. package/dist/build/rsc/client-manifest.js +295 -295
  27. package/dist/build/rsc/compiler.js +5 -4
  28. package/dist/build/rsc/native-scanner.d.ts +135 -135
  29. package/dist/build/rsc/native-scanner.js +203 -203
  30. package/dist/build/rsc/react-client-reference-manifest.d.ts +28 -28
  31. package/dist/build/rsc/react-client-reference-manifest.js +240 -240
  32. package/dist/build/standalone.js +260 -254
  33. package/dist/client/dynamic.react-server.d.ts +2 -2
  34. package/dist/client/dynamic.react-server.js +23 -23
  35. package/dist/client/link.react-server.d.ts +2 -2
  36. package/dist/client/link.react-server.js +11 -11
  37. package/dist/client/navigation.react-server.d.ts +1 -1
  38. package/dist/client/navigation.react-server.js +17 -17
  39. package/dist/client/router.react-server.d.ts +1 -1
  40. package/dist/client/router.react-server.js +17 -17
  41. package/dist/client/rsc-router.react-server.d.ts +1 -1
  42. package/dist/client/rsc-router.react-server.js +17 -17
  43. package/dist/client/script.react-server.d.ts +2 -2
  44. package/dist/client/script.react-server.js +23 -23
  45. package/dist/config.d.ts +1 -1
  46. package/dist/config.js +1 -7
  47. package/dist/deploy/adapters/cloudflare.js +43 -19
  48. package/dist/deploy/adapters/docker.d.ts +1 -0
  49. package/dist/deploy/adapters/docker.js +5 -4
  50. package/dist/deploy/adapters/netlify.js +62 -17
  51. package/dist/deploy/adapters/vercel.js +80 -50
  52. package/dist/deploy/preflight.js +4 -3
  53. package/dist/deploy/runtime-pack.d.ts +11 -0
  54. package/dist/deploy/runtime-pack.js +300 -0
  55. package/dist/deploy/utils.d.ts +1 -1
  56. package/dist/deploy/utils.js +31 -2
  57. package/dist/flashpack/command.js +1 -0
  58. package/dist/flashpack/runtime.js +2 -1
  59. package/dist/index.d.ts +23 -23
  60. package/dist/index.js +61 -61
  61. package/dist/server/app-dir.d.ts +22 -0
  62. package/dist/server/app-dir.js +77 -0
  63. package/dist/server/cookie-parse.d.ts +4 -4
  64. package/dist/server/cookie-parse.js +14 -14
  65. package/dist/server/engine.js +5 -5
  66. package/dist/server/image-optimizer.js +2 -1
  67. package/dist/server/index.d.ts +109 -109
  68. package/dist/server/index.js +315 -315
  69. package/dist/server/middleware-runner.d.ts +125 -125
  70. package/dist/server/middleware-runner.js +607 -615
  71. package/dist/server/middleware-security.d.ts +36 -36
  72. package/dist/server/middleware-security.js +183 -183
  73. package/dist/server/module-compile-hook.js +695 -695
  74. package/dist/server/root-resolver.js +6 -5
  75. package/dist/server/rsc-engine-flashpack.d.ts +3 -1
  76. package/dist/server/rsc-engine-flashpack.js +12 -1
  77. package/dist/server/rsc-engine.d.ts +6 -1
  78. package/dist/server/rsc-engine.js +203 -219
  79. package/dist/server/rsc-upstream.js +893 -892
  80. package/dist/server/static-generator.js +4 -3
  81. package/dist/server/structure-validator.js +2 -1
  82. package/dist/server/structure-watch.js +2 -0
  83. package/dist/server/typed-api-runtime.js +639 -635
  84. package/dist/server/vista-import-map.js +134 -134
  85. package/dist/stack/index.d.ts +34 -34
  86. package/dist/stack/index.js +49 -49
  87. package/dist/stack/server/caller.d.ts +13 -13
  88. package/dist/stack/server/caller.js +42 -42
  89. package/dist/stack/server/executor.d.ts +40 -40
  90. package/dist/stack/server/executor.js +222 -222
  91. package/dist/stack/server/index.d.ts +11 -11
  92. package/dist/stack/server/index.js +24 -24
  93. package/dist/stack/server/procedure.d.ts +20 -20
  94. package/dist/stack/server/procedure.js +66 -66
  95. package/dist/stack/server/types.d.ts +113 -113
  96. package/dist/theme/react-server.d.ts +10 -10
  97. package/dist/theme/react-server.js +16 -16
  98. package/package.json +1 -1
@@ -12,6 +12,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
12
12
  return (mod && mod.__esModule) ? mod : { "default": mod };
13
13
  };
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.createRSCApp = createRSCApp;
16
+ exports.getRSCRequestListener = getRSCRequestListener;
15
17
  exports.startRSCServer = startRSCServer;
16
18
  exports.default = startRSCServer;
17
19
  const path_1 = __importDefault(require("path"));
@@ -81,6 +83,7 @@ const app_router_runtime_1 = require("./app-router-runtime");
81
83
  const fetch_policy_1 = require("./fetch-policy");
82
84
  const vista_import_map_1 = require("./vista-import-map");
83
85
  const project_alias_resolver_1 = require("./project-alias-resolver");
86
+ const app_dir_1 = require("./app-dir");
84
87
  // Support CSS imports on server runtime
85
88
  // - Regular .css: ignored (handled by PostCSS)
86
89
  // - .module.css: return empty class mapping (webpack build handles real mappings)
@@ -432,6 +435,46 @@ function loadSSRManifestFromDisk(absolutePath) {
432
435
  const manifest = (0, react_client_reference_manifest_1.normalizeReactServerConsumerManifest)(JSON.parse(fs_1.default.readFileSync(absolutePath, 'utf-8')));
433
436
  return normalizeSSRManifest(manifest);
434
437
  }
438
+ /** Stub `{}` manifests written before the first webpack emit are not usable for Flight SSR. */
439
+ function isSSRManifestReady(manifest) {
440
+ if (!manifest || !manifest.moduleMap)
441
+ return false;
442
+ return Object.keys(manifest.moduleMap).length > 0;
443
+ }
444
+ /**
445
+ * Tee an upstream Flight body: one copy for createFromNodeStream (SSR), one
446
+ * buffer for inline hydration bootstrap in the HTML response.
447
+ */
448
+ function teeFlightReadable(source) {
449
+ const decodeStream = new stream_1.PassThrough();
450
+ const chunks = [];
451
+ let resolveBuffer;
452
+ let rejectBuffer;
453
+ const bufferPromise = new Promise((resolve, reject) => {
454
+ resolveBuffer = resolve;
455
+ rejectBuffer = reject;
456
+ });
457
+ source.on('data', (chunk) => {
458
+ const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
459
+ chunks.push(buf);
460
+ if (!decodeStream.write(buf)) {
461
+ source.pause();
462
+ decodeStream.once('drain', () => source.resume());
463
+ }
464
+ });
465
+ source.on('end', () => {
466
+ decodeStream.end();
467
+ resolveBuffer(Buffer.concat(chunks));
468
+ });
469
+ source.on('error', (error) => {
470
+ decodeStream.destroy(error);
471
+ rejectBuffer(error);
472
+ });
473
+ return { decodeStream, bufferPromise };
474
+ }
475
+ function buildInlineFlightBootstrapScript(flightText) {
476
+ return `<script>window.${constants_1.RSC_DATA_FLAG}=${JSON.stringify(flightText)};</script>`;
477
+ }
435
478
  function matchPattern(pathname, pattern) {
436
479
  const patternParts = pattern.split('/').filter(Boolean);
437
480
  const pathParts = pathname.split('/').filter(Boolean);
@@ -563,7 +606,7 @@ function applySegmentBoundaries(dir, element) {
563
606
  return wrappedElement;
564
607
  }
565
608
  async function renderAppSubtreeElement(input) {
566
- const appDir = path_1.default.join(input.cwd, 'app');
609
+ const appDir = (0, app_dir_1.resolveAppDir)(input.cwd);
567
610
  let element = await createRenderableRouteModuleElement(input.entryFilePath, {
568
611
  params: input.params,
569
612
  searchParams: input.searchParams,
@@ -630,7 +673,7 @@ async function createRouteElement(route, context, isDev, rootLayout, runtimeRoot
630
673
  metadata = { ...metadata, ...dynamicMeta };
631
674
  }
632
675
  const element = await renderAppSubtreeElement({
633
- subtreeRootDir: path_1.default.join(runtimeRoot, 'app'),
676
+ subtreeRootDir: (0, app_dir_1.resolveAppDir)(runtimeRoot),
634
677
  entryFilePath: route.pagePath,
635
678
  pathname: req.path,
636
679
  params,
@@ -748,8 +791,8 @@ function spawnUpstream(cwd, runtimeRoot, upstreamPort) {
748
791
  // ---------------------------------------------------------------------------
749
792
  /**
750
793
  * Fetch the Flight stream from the upstream RSC process, decode it with
751
- * createFromNodeStream, and render the resulting React tree to an HTML stream
752
- * via renderToPipeableStream.
794
+ * createFromNodeStream, render HTML via renderToPipeableStream, and inline the
795
+ * raw Flight payload so the browser can hydrate without a second /rsc fetch.
753
796
  */
754
797
  async function renderFlightToHTMLStream(upstreamOrigin, pathname, search, metadataHtml, chunkFiles, rootMode, flightSSRClient, ssrManifest, res, isDev) {
755
798
  const flightUrl = `${upstreamOrigin}/rsc${pathname}${search ? `?${search}` : ''}`;
@@ -763,30 +806,20 @@ async function renderFlightToHTMLStream(upstreamOrigin, pathname, search, metada
763
806
  if (!upstream.body) {
764
807
  throw new Error('Upstream returned empty body');
765
808
  }
766
- // 2. Convert Web ReadableStream to Node.js Readable
809
+ // 2. Tee: decode stream for SSR + buffer for inline hydration bootstrap
767
810
  const nodeStream = stream_1.Readable.fromWeb(upstream.body);
768
- // 3. Decode Flight stream into a React tree
769
- const flightResponse = flightSSRClient.createFromNodeStream(nodeStream, ssrManifest);
770
- let element;
771
- // In dev, decode the flight payload before React.use() render path.
772
- // This keeps manifest mismatch errors in the request try/catch boundary
773
- // instead of crashing the whole process with an uncaught exception.
774
- if (isDev) {
775
- const resolvedTree = await flightResponse;
776
- element = react_1.default.createElement(react_1.default.Fragment, null, resolvedTree);
777
- }
778
- else {
779
- // 4. Wrap in a component that consumes the Flight response
780
- function FlightRoot() {
781
- return react_1.default.use(flightResponse);
782
- }
783
- element = react_1.default.createElement(FlightRoot);
784
- }
785
- // 5. Build script tags for client chunks
811
+ const { decodeStream, bufferPromise } = teeFlightReadable(nodeStream);
812
+ // 3. Decode Flight stream into a React tree (stream in both dev and prod)
813
+ const flightResponse = flightSSRClient.createFromNodeStream(decodeStream, ssrManifest);
814
+ function FlightRoot() {
815
+ return react_1.default.use(flightResponse);
816
+ }
817
+ const element = react_1.default.createElement(FlightRoot);
818
+ // 4. Build script tags for client chunks
786
819
  const scripts = chunkFiles
787
820
  .map((chunk) => `<script defer src="${constants_1.STATIC_CHUNKS_PATH}${chunk}"></script>`)
788
821
  .join('\n ');
789
- // 6. Render to a pipeable HTML stream
822
+ // 5. Render to a pipeable HTML stream; inject inline Flight before client scripts
790
823
  return new Promise((resolve, reject) => {
791
824
  let shellSent = false;
792
825
  const { pipe } = (0, server_1.renderToPipeableStream)(element, {
@@ -795,11 +828,10 @@ async function renderFlightToHTMLStream(upstreamOrigin, pathname, search, metada
795
828
  res.status(upstream.status === 404 ? 404 : 200);
796
829
  res.setHeader('Content-Type', 'text/html; charset=utf-8');
797
830
  res.setHeader('Transfer-Encoding', 'chunked');
798
- // Create a transform stream to inject head/body content
831
+ let heldTail = '';
799
832
  const transform = new stream_1.Transform({
800
833
  transform(chunk, _encoding, callback) {
801
834
  let html = chunk.toString();
802
- // Inject into <head> if present
803
835
  if (html.includes('</head>')) {
804
836
  const fontHtml = (0, registry_1.getAllFontHTML)();
805
837
  const headInjection = `
@@ -810,15 +842,29 @@ async function renderFlightToHTMLStream(upstreamOrigin, pathname, search, metada
810
842
  ${getCSSLinks()}`;
811
843
  html = html.replace('</head>', `${headInjection}\n</head>`);
812
844
  }
813
- // Inject scripts before </body>
814
- if (html.includes('</body>')) {
815
- const bodyInjection = `
816
- <script>window.${constants_1.HYDRATE_DOCUMENT_FLAG} = ${rootMode === 'document'};</script>
817
- ${scripts}`;
818
- html = html.replace('</body>', `${bodyInjection}\n</body>`);
845
+ // Hold </body>… so flush can inject Flight + scripts before it.
846
+ const bodyCloseIdx = html.indexOf('</body>');
847
+ if (bodyCloseIdx !== -1) {
848
+ const beforeBodyClose = html.slice(0, bodyCloseIdx);
849
+ heldTail = html.slice(bodyCloseIdx);
850
+ const hydrateFlag = `<script>window.${constants_1.HYDRATE_DOCUMENT_FLAG} = ${rootMode === 'document'};</script>`;
851
+ callback(null, `${beforeBodyClose}\n ${hydrateFlag}\n`);
852
+ return;
819
853
  }
820
854
  callback(null, html);
821
855
  },
856
+ flush(callback) {
857
+ bufferPromise
858
+ .then((buf) => {
859
+ const flightScript = buildInlineFlightBootstrapScript(buf.toString('utf8'));
860
+ // Flight first, then deferred client chunks, then </body></html>
861
+ this.push(`${flightScript}\n ${scripts}\n${heldTail || '</body></html>'}`);
862
+ callback();
863
+ })
864
+ .catch((error) => {
865
+ callback(error instanceof Error ? error : new Error(String(error)));
866
+ });
867
+ },
822
868
  });
823
869
  pipe(transform);
824
870
  transform.pipe(res);
@@ -927,6 +973,23 @@ function renderCompilePendingHTML() {
927
973
  </body>
928
974
  </html>`;
929
975
  }
976
+ function failRSCStartup(message, listen) {
977
+ console.error(message);
978
+ if (!listen) {
979
+ throw new Error(message);
980
+ }
981
+ process.exit(1);
982
+ }
983
+ let cachedRequestListener = null;
984
+ function createRSCApp(options = {}) {
985
+ return startRSCServer({ ...options, listen: false });
986
+ }
987
+ function getRSCRequestListener(options = {}) {
988
+ if (!cachedRequestListener) {
989
+ cachedRequestListener = createRSCApp(options);
990
+ }
991
+ return cachedRequestListener;
992
+ }
930
993
  function startRSCServer(options = {}) {
931
994
  const app = (0, express_1.default)();
932
995
  const cwd = path_1.default.resolve(options.projectRoot || process.env.VISTA_ARTIFACT_ROOT || process.cwd());
@@ -943,7 +1006,8 @@ function startRSCServer(options = {}) {
943
1006
  cleanHotUpdateFiles(cwd);
944
1007
  // Request logger — logs GET/POST with timing
945
1008
  app.use((0, logger_1.requestLogger)());
946
- const port = resolvePort(String(options.port || vistaConfig.server?.port || 3003), 3003);
1009
+ const shouldListen = options.listen !== false;
1010
+ const port = resolvePort(String(options.port || vistaConfig.server?.port || process.env.PORT || 3003), 3003);
947
1011
  const upstreamPort = resolvePort(String(process.env.RSC_UPSTREAM_PORT || port + 1), port + 1);
948
1012
  const upstreamOrigin = `http://127.0.0.1:${upstreamPort}`;
949
1013
  installSingleReactResolution(runtimeRoot);
@@ -956,8 +1020,7 @@ function startRSCServer(options = {}) {
956
1020
  installSSRWebpackShim();
957
1021
  const serverManifestPath = path_1.default.join(cwd, constants_1.BUILD_DIR, 'server', 'server-manifest.json');
958
1022
  if (!fs_1.default.existsSync(serverManifestPath)) {
959
- console.error(`[vista:rsc] Missing server manifest at ${serverManifestPath}. Run "vista build --rsc" first.`);
960
- process.exit(1);
1023
+ failRSCStartup(`[vista:rsc] Missing server manifest at ${serverManifestPath}. Run "vista build --rsc" first.`, shouldListen);
961
1024
  }
962
1025
  try {
963
1026
  if (!isDev || !options.compiler) {
@@ -965,8 +1028,7 @@ function startRSCServer(options = {}) {
965
1028
  }
966
1029
  }
967
1030
  catch (error) {
968
- console.error(error.message);
969
- process.exit(1);
1031
+ failRSCStartup(error.message, shouldListen);
970
1032
  }
971
1033
  // ========================================================================
972
1034
  // Flight SSR Client + SSR Manifest
@@ -978,7 +1040,8 @@ function startRSCServer(options = {}) {
978
1040
  flightSSRClient = require(flightClientPath);
979
1041
  }
980
1042
  catch (err) {
981
- // Flight SSR not availablefallback to renderToString
1043
+ // Flight SSR client missingrequests will fail closed (no renderToString).
1044
+ (0, logger_1.logError)(`[vista:rsc] react-server-dom-webpack/client.node unavailable: ${err?.message || String(err)}`);
982
1045
  }
983
1046
  const ssrManifestPath = path_1.default.join(cwd, constants_1.BUILD_DIR, 'react-server-manifest.json');
984
1047
  const ssrManifestLegacyPath = path_1.default.join(cwd, constants_1.BUILD_DIR, 'react-ssr-manifest.json');
@@ -988,13 +1051,21 @@ function startRSCServer(options = {}) {
988
1051
  ? ssrManifestLegacyPath
989
1052
  : null;
990
1053
  if (resolvedSSRManifestPath) {
991
- ssrManifest = loadSSRManifestFromDisk(resolvedSSRManifestPath);
1054
+ try {
1055
+ ssrManifest = loadSSRManifestFromDisk(resolvedSSRManifestPath);
1056
+ }
1057
+ catch {
1058
+ ssrManifest = null;
1059
+ }
1060
+ }
1061
+ if (ssrManifest && !isSSRManifestReady(ssrManifest)) {
1062
+ // Stub manifests written before the first webpack emit must not drive SSR.
1063
+ ssrManifest = null;
992
1064
  }
993
- else if (flightSSRClient) {
994
- // Can't use Flight SSR without the manifest
1065
+ if (!resolvedSSRManifestPath && flightSSRClient) {
995
1066
  flightSSRClient = null;
996
1067
  }
997
- const useFlightSSR = !!flightSSRClient && !!ssrManifest;
1068
+ let useFlightSSR = !!flightSSRClient && isSSRManifestReady(ssrManifest);
998
1069
  // ========================================================================
999
1070
  // Structure Validation (dev + strict-block)
1000
1071
  // ========================================================================
@@ -1098,11 +1169,15 @@ function startRSCServer(options = {}) {
1098
1169
  httpServer.close();
1099
1170
  }
1100
1171
  // 6. Force exit after brief grace period (Windows Ctrl+C fix)
1101
- setTimeout(() => process.exit(0), 500);
1172
+ if (shouldListen) {
1173
+ setTimeout(() => process.exit(0), 500);
1174
+ }
1102
1175
  };
1103
- process.on('SIGINT', shutdown);
1104
- process.on('SIGTERM', shutdown);
1105
- process.on('exit', shutdown);
1176
+ if (shouldListen) {
1177
+ process.on('SIGINT', shutdown);
1178
+ process.on('SIGTERM', shutdown);
1179
+ process.on('exit', shutdown);
1180
+ }
1106
1181
  // ========================================================================
1107
1182
  // Live-Reload SSE for RSC dev mode
1108
1183
  // - Pushes reload events when server components change (fs.watch)
@@ -1134,6 +1209,7 @@ function startRSCServer(options = {}) {
1134
1209
  'lib',
1135
1210
  'ctx',
1136
1211
  'data',
1212
+ 'src',
1137
1213
  'middleware.ts',
1138
1214
  'vista.config.ts',
1139
1215
  'content-collections.ts',
@@ -1228,9 +1304,18 @@ function startRSCServer(options = {}) {
1228
1304
  if (fs_1.default.existsSync(serverManifestPath)) {
1229
1305
  serverManifest = JSON.parse(fs_1.default.readFileSync(serverManifestPath, 'utf-8'));
1230
1306
  }
1231
- // Reload SSR manifest on rebuild too
1307
+ // Reload SSR manifest on rebuild; ignore stub `{}` until webpack writes a real map.
1232
1308
  if (resolvedSSRManifestPath && fs_1.default.existsSync(resolvedSSRManifestPath)) {
1233
- ssrManifest = loadSSRManifestFromDisk(resolvedSSRManifestPath);
1309
+ try {
1310
+ const nextManifest = loadSSRManifestFromDisk(resolvedSSRManifestPath);
1311
+ if (isSSRManifestReady(nextManifest)) {
1312
+ ssrManifest = nextManifest;
1313
+ useFlightSSR = !!flightSSRClient;
1314
+ }
1315
+ }
1316
+ catch {
1317
+ // Manifest may be mid-write during compilation.
1318
+ }
1234
1319
  }
1235
1320
  });
1236
1321
  }
@@ -1503,28 +1588,50 @@ function startRSCServer(options = {}) {
1503
1588
  return;
1504
1589
  }
1505
1590
  // ==================================================================
1506
- // Flight-Based SSR Path
1591
+ // Flight-Based SSR Path (fail-closed — no renderToString page HTML)
1507
1592
  // ==================================================================
1508
- // If the Flight SSR client + SSR manifest are available, render pages
1509
- // by fetching the Flight stream from upstream and using
1510
- // renderToPipeableStream for streaming HTML with proper hydration.
1511
- // Falls back to legacy renderToString if Flight SSR is unavailable.
1512
- // ==================================================================
1513
- if (useFlightSSR) {
1593
+ if (isDev && resolvedSSRManifestPath && fs_1.default.existsSync(resolvedSSRManifestPath)) {
1514
1594
  try {
1515
- if (isDev && resolvedSSRManifestPath && fs_1.default.existsSync(resolvedSSRManifestPath)) {
1516
- try {
1517
- ssrManifest = loadSSRManifestFromDisk(resolvedSSRManifestPath);
1518
- }
1519
- catch {
1520
- // Manifest may be mid-write during compilation; keep the last good in-memory copy.
1521
- }
1595
+ const nextManifest = loadSSRManifestFromDisk(resolvedSSRManifestPath);
1596
+ if (isSSRManifestReady(nextManifest)) {
1597
+ ssrManifest = nextManifest;
1598
+ useFlightSSR = !!flightSSRClient;
1522
1599
  }
1523
- // Metadata extraction: still done locally so we have <head> content
1524
- const rootLayout = (0, root_resolver_1.resolveRootLayout)(runtimeRoot, isDev);
1525
- const route = currentRoute;
1526
- let metadataHtml = '';
1527
- if (route) {
1600
+ }
1601
+ catch {
1602
+ // Manifest may be mid-write during compilation; keep the last good in-memory copy.
1603
+ }
1604
+ }
1605
+ if (!useFlightSSR || !flightSSRClient || !isSSRManifestReady(ssrManifest)) {
1606
+ if (isDev && options.compiler) {
1607
+ // Wait for a non-empty SSR manifest after the first webpack emit.
1608
+ res.status(503).type('text/html').send(renderCompilePendingHTML());
1609
+ return;
1610
+ }
1611
+ const message = 'Flight SSR is unavailable. A usable react-server-manifest.json is required; Vista no longer falls back to renderToString.';
1612
+ if (isDev) {
1613
+ res.status(500).send((0, dev_error_1.renderErrorHTML)([
1614
+ {
1615
+ type: 'build',
1616
+ source: 'server',
1617
+ message,
1618
+ },
1619
+ ]));
1620
+ }
1621
+ else {
1622
+ res.status(500).type('text/plain').send(message);
1623
+ }
1624
+ return;
1625
+ }
1626
+ try {
1627
+ // Metadata extraction: still done locally so we have <head> content.
1628
+ // notFound() here must not skip Flight — upstream renders the nearest
1629
+ // segment not-found as a 404 Flight payload.
1630
+ const rootLayout = (0, root_resolver_1.resolveRootLayout)(runtimeRoot, isDev);
1631
+ const route = currentRoute;
1632
+ let metadataHtml = '';
1633
+ if (route) {
1634
+ try {
1528
1635
  if (isDev) {
1529
1636
  clearProjectRequireCache(runtimeRoot);
1530
1637
  }
@@ -1542,173 +1649,49 @@ function startRSCServer(options = {}) {
1542
1649
  const { generateMetadataHtml } = require('../metadata/generate');
1543
1650
  metadataHtml = metadata ? generateMetadataHtml(metadata) : '';
1544
1651
  }
1545
- // Render the page via Flight stream → SSR
1546
- await renderFlightToHTMLStream(upstreamOrigin, req.path, req.query ? new URLSearchParams(req.query).toString() : '', metadataHtml, findChunkFiles(cwd, isDev), rootLayout.mode, flightSSRClient, ssrManifest, res, isDev);
1547
- return;
1548
- }
1549
- catch (flightError) {
1550
- if (flightError?.name === 'NotFoundError' && !res.headersSent) {
1551
- try {
1552
- const rootLayout = (0, root_resolver_1.resolveRootLayout)(runtimeRoot, isDev);
1553
- const route = currentRoute;
1554
- if (route) {
1555
- const segmentNotFoundPath = (0, app_router_runtime_1.resolveNearestSegmentNotFoundPath)(path_1.default.join(runtimeRoot, 'app'), route.routeDir);
1556
- if (segmentNotFoundPath) {
1557
- const params = extractParams(req.path, route);
1558
- const searchParams = Object.fromEntries(new URLSearchParams(req.query).entries());
1559
- const notFoundResult = await createRouteElement({
1560
- ...route,
1561
- pagePath: segmentNotFoundPath,
1562
- }, { params, searchParams, req }, isDev, rootLayout, runtimeRoot, { disableParallelSlots: true });
1563
- const html = (0, server_1.renderToString)(notFoundResult.element);
1564
- const { generateMetadataHtml } = require('../metadata/generate');
1565
- const metadataHtml = notFoundResult.metadata
1566
- ? generateMetadataHtml(notFoundResult.metadata)
1567
- : '';
1568
- res
1569
- .status(404)
1570
- .type('text/html')
1571
- .send(createHtmlDocument(html, metadataHtml, findChunkFiles(cwd, isDev), notFoundResult.rootMode));
1572
- return;
1573
- }
1574
- }
1575
- }
1576
- catch (notFoundError) {
1577
- console.error('[vista:rsc] Failed to render segment not-found fallback:', notFoundError);
1578
- }
1579
- }
1580
- console.error('[vista:rsc] Flight SSR failed:', flightError.message);
1581
- // If headers haven't been sent yet, show the error overlay directly.
1582
- // This is much better than falling through to legacy renderToString,
1583
- // which will likely hit the same error (e.g. useState in a server component).
1584
- if (isDev && !res.headersSent) {
1585
- res.status(500).send((0, dev_error_1.renderErrorHTML)([(0, dev_error_1.fromCaughtError)(flightError, { source: 'server' })]));
1586
- return;
1587
- }
1588
- // If headers were already sent (stream was partially flushed),
1589
- // we can't change the status code, but we can inject an error
1590
- // overlay script at the end of the stream.
1591
- if (isDev && res.headersSent) {
1592
- try {
1593
- const errMsg = (flightError.message || 'Flight SSR Error')
1594
- .replace(/'/g, "\\'")
1595
- .replace(/\n/g, '\\n');
1596
- 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>`);
1597
- res.end();
1652
+ catch (metadataError) {
1653
+ if (metadataError?.name !== 'NotFoundError') {
1654
+ throw metadataError;
1598
1655
  }
1599
- catch {
1600
- res.end();
1601
- }
1602
- return;
1603
1656
  }
1604
1657
  }
1605
- }
1606
- // ==================================================================
1607
- // Legacy Fallback: Direct renderToString
1608
- // ==================================================================
1609
- // Used when Flight SSR is unavailable or fails. This path does NOT
1610
- // go through the Flight protocol — it requires page modules directly
1611
- // and renders them with renderToString (synchronous, no streaming).
1612
- // ==================================================================
1613
- try {
1614
- // Check upstream availability for the legacy path
1615
- await withTimeout(`${upstreamOrigin}/rsc/`, { headers: { Accept: 'text/x-component' } }, 3000);
1616
- }
1617
- catch (error) {
1618
- res.status(503).type('text/plain').send(getUpstreamUnavailableMessage());
1658
+ await renderFlightToHTMLStream(upstreamOrigin, req.path, req.query ? new URLSearchParams(req.query).toString() : '', metadataHtml, findChunkFiles(cwd, isDev), rootLayout.mode, flightSSRClient, ssrManifest, res, isDev);
1619
1659
  return;
1620
1660
  }
1621
- try {
1622
- const rootLayout = (0, root_resolver_1.resolveRootLayout)(runtimeRoot, isDev);
1623
- const route = currentRoute;
1624
- (0, request_context_1.setCurrentSegmentConfig)(route?.segmentConfig);
1625
- if (!route) {
1626
- const resolvedNotFound = (0, root_resolver_1.resolveNotFoundComponent)(runtimeRoot, rootLayout, isDev);
1627
- if (resolvedNotFound) {
1628
- const notFoundElement = react_1.default.createElement(resolvedNotFound.component, {
1629
- params: {},
1630
- searchParams: {},
1631
- });
1632
- const wrapped = react_1.default.createElement(rootLayout.component, { params: {}, searchParams: {} }, notFoundElement);
1633
- const html = (0, server_1.renderToString)(wrapped);
1634
- res
1635
- .status(404)
1636
- .type('text/html')
1637
- .send(createHtmlDocument(html, '', findChunkFiles(cwd, isDev), rootLayout.mode));
1638
- return;
1639
- }
1661
+ catch (flightError) {
1662
+ if (flightError?.name === 'NotFoundError' && !res.headersSent) {
1640
1663
  res.status(404).type('text/html').send((0, not_found_page_1.getStyledNotFoundHTML)());
1641
1664
  return;
1642
1665
  }
1643
- const params = extractParams(req.path, route);
1644
- const searchParams = Object.fromEntries(new URLSearchParams(req.query).entries());
1645
- const { element, metadata, rootMode } = await createRouteElement(route, { params, searchParams, req }, isDev, rootLayout, runtimeRoot);
1646
- const appHtml = (0, server_1.renderToString)(element);
1647
- const { generateMetadataHtml } = require('../metadata/generate');
1648
- const metadataHtml = metadata ? generateMetadataHtml(metadata) : '';
1649
- res
1650
- .status(200)
1651
- .type('text/html')
1652
- .send(createHtmlDocument(appHtml, metadataHtml, findChunkFiles(cwd, isDev), rootMode));
1653
- }
1654
- catch (error) {
1655
- if (error?.name === 'NotFoundError') {
1656
- try {
1657
- const rootLayout = (0, root_resolver_1.resolveRootLayout)(runtimeRoot, isDev);
1658
- const route = currentRoute;
1659
- (0, request_context_1.setCurrentSegmentConfig)(route?.segmentConfig);
1660
- if (route) {
1661
- const segmentNotFoundPath = (0, app_router_runtime_1.resolveNearestSegmentNotFoundPath)(path_1.default.join(runtimeRoot, 'app'), route.routeDir);
1662
- if (segmentNotFoundPath) {
1663
- const params = extractParams(req.path, route);
1664
- const searchParams = Object.fromEntries(new URLSearchParams(req.query).entries());
1665
- const notFoundResult = await createRouteElement({
1666
- ...route,
1667
- pagePath: segmentNotFoundPath,
1668
- }, { params, searchParams, req }, isDev, rootLayout, runtimeRoot, { disableParallelSlots: true });
1669
- const html = (0, server_1.renderToString)(notFoundResult.element);
1670
- const { generateMetadataHtml } = require('../metadata/generate');
1671
- const metadataHtml = notFoundResult.metadata
1672
- ? generateMetadataHtml(notFoundResult.metadata)
1673
- : '';
1674
- res
1675
- .status(404)
1676
- .type('text/html')
1677
- .send(createHtmlDocument(html, metadataHtml, findChunkFiles(cwd, isDev), notFoundResult.rootMode));
1678
- return;
1679
- }
1680
- }
1681
- const resolvedNotFound = (0, root_resolver_1.resolveNotFoundComponent)(runtimeRoot, rootLayout, isDev);
1682
- if (resolvedNotFound) {
1683
- const notFoundElement = react_1.default.createElement(resolvedNotFound.component, {
1684
- params: {},
1685
- searchParams: {},
1686
- });
1687
- const wrapped = react_1.default.createElement(rootLayout.component, { params: {}, searchParams: {} }, notFoundElement);
1688
- const html = (0, server_1.renderToString)(wrapped);
1689
- res
1690
- .status(404)
1691
- .type('text/html')
1692
- .send(createHtmlDocument(html, '', findChunkFiles(cwd, isDev), rootLayout.mode));
1693
- return;
1694
- }
1695
- res.status(404).type('text/html').send((0, not_found_page_1.getStyledNotFoundHTML)());
1696
- return;
1666
+ console.error('[vista:rsc] Flight SSR failed:', flightError.message);
1667
+ if (!res.headersSent) {
1668
+ if (isDev) {
1669
+ res.status(500).send((0, dev_error_1.renderErrorHTML)([(0, dev_error_1.fromCaughtError)(flightError, { source: 'server' })]));
1697
1670
  }
1698
- catch (notFoundError) {
1699
- console.error('[vista:rsc] Failed to render NotFoundError fallback:', notFoundError);
1671
+ else {
1672
+ res.status(500).send('<h1>Internal Server Error</h1>');
1700
1673
  }
1674
+ return;
1701
1675
  }
1702
- console.error('[vista:rsc] Render error:', error);
1703
- if (isDev) {
1704
- res.status(500).send((0, dev_error_1.renderErrorHTML)([(0, dev_error_1.fromCaughtError)(error, { source: 'server' })]));
1705
- }
1706
- else {
1707
- res.status(500).send('<h1>Internal Server Error</h1>');
1676
+ if (isDev && res.headersSent) {
1677
+ try {
1678
+ const errMsg = (flightError.message || 'Flight SSR Error')
1679
+ .replace(/'/g, "\\'")
1680
+ .replace(/\n/g, '\\n');
1681
+ 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>`);
1682
+ res.end();
1683
+ }
1684
+ catch {
1685
+ res.end();
1686
+ }
1708
1687
  }
1688
+ return;
1709
1689
  }
1710
1690
  });
1711
1691
  });
1692
+ if (!shouldListen) {
1693
+ return app;
1694
+ }
1712
1695
  const server = app.listen(port, () => {
1713
1696
  (0, logger_1.printServerReady)({ port, mode: 'rsc', rscFlight: useFlightSSR });
1714
1697
  });
@@ -1722,4 +1705,5 @@ function startRSCServer(options = {}) {
1722
1705
  (0, logger_1.logError)(`RSC startup failed: ${error?.message || String(error)}`);
1723
1706
  process.exit(1);
1724
1707
  });
1708
+ return app;
1725
1709
  }