@lovable.dev/vite-tanstack-config 2.16.0 → 2.17.1

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/hmr-gate.cjs CHANGED
@@ -8,6 +8,24 @@ function requestPathname(req) {
8
8
  const queryStart = url.indexOf("?");
9
9
  return queryStart === -1 ? url : url.slice(0, queryStart);
10
10
  }
11
+ const nonNavigationPrefixes = [
12
+ "/@vite/",
13
+ "/@fs/",
14
+ "/@id/",
15
+ "/@react-refresh",
16
+ "/api/",
17
+ "/go-api/",
18
+ "/assets/",
19
+ "/src/",
20
+ "/_",
21
+ "/favicon"
22
+ ];
23
+ function isHtmlNavigationRequest(req) {
24
+ if (req.method !== "GET") return false;
25
+ const url = req.url;
26
+ if (!url || nonNavigationPrefixes.some((prefix) => url.startsWith(prefix))) return false;
27
+ return (req.headers.accept ?? "").includes("text/html");
28
+ }
11
29
  function gateStatusHeaders(req) {
12
30
  const origin = req.headers.origin;
13
31
  const headers = {
@@ -233,6 +251,7 @@ function hmrGatePlugin(options = {}) {
233
251
  lastUpdateKey = "";
234
252
  }
235
253
  let classicFailOpenTimer = null;
254
+ let errorServedSinceFlush = false;
236
255
  let transportMode = false;
237
256
  let swallowedUpdates = 0;
238
257
  let heldReload = false;
@@ -420,6 +439,12 @@ function hmrGatePlugin(options = {}) {
420
439
  });
421
440
  srv.middlewares.use((req, res, next) => {
422
441
  if (req.method !== "POST" || requestPathname(req) !== "/__hmr_flush") return next();
442
+ const flushOrigin = req.headers.origin;
443
+ if (typeof flushOrigin === "string" && !statusCorsOriginRe.test(flushOrigin)) {
444
+ res.writeHead(403, { "Content-Type": "application/json" });
445
+ res.end(JSON.stringify({ error: "origin not allowed" }));
446
+ return;
447
+ }
423
448
  const priorDrainUntil = drainUntil;
424
449
  const released = releaseParked();
425
450
  drainUntil = Date.now() + drainMs;
@@ -494,6 +519,23 @@ function hmrGatePlugin(options = {}) {
494
519
  });
495
520
  return;
496
521
  }
522
+ srv.middlewares.use((req, res, next) => {
523
+ const originalEnd = res.end.bind(res);
524
+ res.end = ((...args) => {
525
+ if (res.statusCode >= 500 && isHtmlNavigationRequest(req)) {
526
+ errorServedSinceFlush = true;
527
+ if (classicFailOpenTimer === null) {
528
+ classicFailOpenTimer = setTimeout(() => {
529
+ classicFailOpenTimer = null;
530
+ flushPending();
531
+ }, maxHoldMs);
532
+ classicFailOpenTimer.unref?.();
533
+ }
534
+ }
535
+ return originalEnd(...args);
536
+ });
537
+ next();
538
+ });
497
539
  const _emit = srv.watcher.emit.bind(srv.watcher);
498
540
  originalEmit = _emit;
499
541
  const flushPending = () => {
@@ -502,6 +544,9 @@ function hmrGatePlugin(options = {}) {
502
544
  classicFailOpenTimer = null;
503
545
  }
504
546
  if (pending.size > 0) noteUpdateFlush();
547
+ const errorServed = errorServedSinceFlush;
548
+ errorServedSinceFlush = false;
549
+ if (errorServed) srv.moduleGraph.invalidateAll();
505
550
  const entries = Array.from(pending.entries());
506
551
  pending.clear();
507
552
  for (const [f] of entries) srv.moduleGraph.onFileChange(f);
@@ -514,7 +559,11 @@ function hmrGatePlugin(options = {}) {
514
559
  }
515
560
  if (fullReload) srv.ws.send({ type: "full-reload" });
516
561
  }
517
- return entries;
562
+ if (errorServed && (entries.length === 0 || !fullReload)) srv.ws.send({ type: "full-reload" });
563
+ return {
564
+ entries,
565
+ errorRecovery: errorServed
566
+ };
518
567
  };
519
568
  srv.watcher.emit = ((event, ...args) => {
520
569
  if (typeof event === "string" && gatedEvents.has(event) && !isPassthrough(args[0])) {
@@ -523,7 +572,7 @@ function hmrGatePlugin(options = {}) {
523
572
  const hash = fileContentHash(filePath);
524
573
  if (hash !== null && hash === lastFlushedHashes.get(filePath)) {
525
574
  pending.delete(filePath);
526
- if (pending.size === 0 && classicFailOpenTimer !== null) {
575
+ if (pending.size === 0 && !errorServedSinceFlush && classicFailOpenTimer !== null) {
527
576
  clearTimeout(classicFailOpenTimer);
528
577
  classicFailOpenTimer = null;
529
578
  }
@@ -550,11 +599,18 @@ function hmrGatePlugin(options = {}) {
550
599
  });
551
600
  srv.middlewares.use((req, res, next) => {
552
601
  if (req.method !== "POST" || requestPathname(req) !== "/__hmr_flush") return next();
553
- const entries = flushPending();
602
+ const origin = req.headers.origin;
603
+ if (typeof origin === "string" && !statusCorsOriginRe.test(origin)) {
604
+ res.writeHead(403, { "Content-Type": "application/json" });
605
+ res.end(JSON.stringify({ error: "origin not allowed" }));
606
+ return;
607
+ }
608
+ const { entries, errorRecovery } = flushPending();
554
609
  const files = entries.map(([f]) => f);
555
610
  const body = JSON.stringify({
556
611
  flushed: files,
557
612
  count: entries.length,
613
+ errorRecovery,
558
614
  mode: fullReload ? "full-reload" : "granular"
559
615
  });
560
616
  res.writeHead(200, { "Content-Type": "application/json" });
@@ -567,7 +623,8 @@ function hmrGatePlugin(options = {}) {
567
623
  const body = JSON.stringify({
568
624
  enabled: true,
569
625
  pending: serialized,
570
- count: pending.size,
626
+ count: pending.size + (errorServedSinceFlush ? 1 : 0),
627
+ errorRecovery: errorServedSinceFlush,
571
628
  mode: fullReload ? "full-reload" : "granular"
572
629
  });
573
630
  res.writeHead(200, gateStatusHeaders(req));
@@ -609,6 +666,7 @@ function hmrGatePlugin(options = {}) {
609
666
  drainUntil = 0;
610
667
  pending.clear();
611
668
  lastFlushedHashes.clear();
669
+ errorServedSinceFlush = false;
612
670
  server = null;
613
671
  originalEmit = null;
614
672
  }
package/dist/hmr-gate.js CHANGED
@@ -7,6 +7,24 @@ function requestPathname(req) {
7
7
  const queryStart = url.indexOf("?");
8
8
  return queryStart === -1 ? url : url.slice(0, queryStart);
9
9
  }
10
+ const nonNavigationPrefixes = [
11
+ "/@vite/",
12
+ "/@fs/",
13
+ "/@id/",
14
+ "/@react-refresh",
15
+ "/api/",
16
+ "/go-api/",
17
+ "/assets/",
18
+ "/src/",
19
+ "/_",
20
+ "/favicon"
21
+ ];
22
+ function isHtmlNavigationRequest(req) {
23
+ if (req.method !== "GET") return false;
24
+ const url = req.url;
25
+ if (!url || nonNavigationPrefixes.some((prefix) => url.startsWith(prefix))) return false;
26
+ return (req.headers.accept ?? "").includes("text/html");
27
+ }
10
28
  function gateStatusHeaders(req) {
11
29
  const origin = req.headers.origin;
12
30
  const headers = {
@@ -232,6 +250,7 @@ function hmrGatePlugin(options = {}) {
232
250
  lastUpdateKey = "";
233
251
  }
234
252
  let classicFailOpenTimer = null;
253
+ let errorServedSinceFlush = false;
235
254
  let transportMode = false;
236
255
  let swallowedUpdates = 0;
237
256
  let heldReload = false;
@@ -419,6 +438,12 @@ function hmrGatePlugin(options = {}) {
419
438
  });
420
439
  srv.middlewares.use((req, res, next) => {
421
440
  if (req.method !== "POST" || requestPathname(req) !== "/__hmr_flush") return next();
441
+ const flushOrigin = req.headers.origin;
442
+ if (typeof flushOrigin === "string" && !statusCorsOriginRe.test(flushOrigin)) {
443
+ res.writeHead(403, { "Content-Type": "application/json" });
444
+ res.end(JSON.stringify({ error: "origin not allowed" }));
445
+ return;
446
+ }
422
447
  const priorDrainUntil = drainUntil;
423
448
  const released = releaseParked();
424
449
  drainUntil = Date.now() + drainMs;
@@ -493,6 +518,23 @@ function hmrGatePlugin(options = {}) {
493
518
  });
494
519
  return;
495
520
  }
521
+ srv.middlewares.use((req, res, next) => {
522
+ const originalEnd = res.end.bind(res);
523
+ res.end = ((...args) => {
524
+ if (res.statusCode >= 500 && isHtmlNavigationRequest(req)) {
525
+ errorServedSinceFlush = true;
526
+ if (classicFailOpenTimer === null) {
527
+ classicFailOpenTimer = setTimeout(() => {
528
+ classicFailOpenTimer = null;
529
+ flushPending();
530
+ }, maxHoldMs);
531
+ classicFailOpenTimer.unref?.();
532
+ }
533
+ }
534
+ return originalEnd(...args);
535
+ });
536
+ next();
537
+ });
496
538
  const _emit = srv.watcher.emit.bind(srv.watcher);
497
539
  originalEmit = _emit;
498
540
  const flushPending = () => {
@@ -501,6 +543,9 @@ function hmrGatePlugin(options = {}) {
501
543
  classicFailOpenTimer = null;
502
544
  }
503
545
  if (pending.size > 0) noteUpdateFlush();
546
+ const errorServed = errorServedSinceFlush;
547
+ errorServedSinceFlush = false;
548
+ if (errorServed) srv.moduleGraph.invalidateAll();
504
549
  const entries = Array.from(pending.entries());
505
550
  pending.clear();
506
551
  for (const [f] of entries) srv.moduleGraph.onFileChange(f);
@@ -513,7 +558,11 @@ function hmrGatePlugin(options = {}) {
513
558
  }
514
559
  if (fullReload) srv.ws.send({ type: "full-reload" });
515
560
  }
516
- return entries;
561
+ if (errorServed && (entries.length === 0 || !fullReload)) srv.ws.send({ type: "full-reload" });
562
+ return {
563
+ entries,
564
+ errorRecovery: errorServed
565
+ };
517
566
  };
518
567
  srv.watcher.emit = ((event, ...args) => {
519
568
  if (typeof event === "string" && gatedEvents.has(event) && !isPassthrough(args[0])) {
@@ -522,7 +571,7 @@ function hmrGatePlugin(options = {}) {
522
571
  const hash = fileContentHash(filePath);
523
572
  if (hash !== null && hash === lastFlushedHashes.get(filePath)) {
524
573
  pending.delete(filePath);
525
- if (pending.size === 0 && classicFailOpenTimer !== null) {
574
+ if (pending.size === 0 && !errorServedSinceFlush && classicFailOpenTimer !== null) {
526
575
  clearTimeout(classicFailOpenTimer);
527
576
  classicFailOpenTimer = null;
528
577
  }
@@ -549,11 +598,18 @@ function hmrGatePlugin(options = {}) {
549
598
  });
550
599
  srv.middlewares.use((req, res, next) => {
551
600
  if (req.method !== "POST" || requestPathname(req) !== "/__hmr_flush") return next();
552
- const entries = flushPending();
601
+ const origin = req.headers.origin;
602
+ if (typeof origin === "string" && !statusCorsOriginRe.test(origin)) {
603
+ res.writeHead(403, { "Content-Type": "application/json" });
604
+ res.end(JSON.stringify({ error: "origin not allowed" }));
605
+ return;
606
+ }
607
+ const { entries, errorRecovery } = flushPending();
553
608
  const files = entries.map(([f]) => f);
554
609
  const body = JSON.stringify({
555
610
  flushed: files,
556
611
  count: entries.length,
612
+ errorRecovery,
557
613
  mode: fullReload ? "full-reload" : "granular"
558
614
  });
559
615
  res.writeHead(200, { "Content-Type": "application/json" });
@@ -566,7 +622,8 @@ function hmrGatePlugin(options = {}) {
566
622
  const body = JSON.stringify({
567
623
  enabled: true,
568
624
  pending: serialized,
569
- count: pending.size,
625
+ count: pending.size + (errorServedSinceFlush ? 1 : 0),
626
+ errorRecovery: errorServedSinceFlush,
570
627
  mode: fullReload ? "full-reload" : "granular"
571
628
  });
572
629
  res.writeHead(200, gateStatusHeaders(req));
@@ -608,6 +665,7 @@ function hmrGatePlugin(options = {}) {
608
665
  drainUntil = 0;
609
666
  pending.clear();
610
667
  lastFlushedHashes.clear();
668
+ errorServedSinceFlush = false;
611
669
  server = null;
612
670
  originalEmit = null;
613
671
  }
package/dist/index.cjs CHANGED
@@ -551,6 +551,107 @@ function stripRedundantNodejsCompatFlag() {
551
551
  }
552
552
  };
553
553
  }
554
+ const NITRO_SERVER_ENTRY = "index.mjs";
555
+ const NITRO_WRANGLER_CONFIG = "wrangler.json";
556
+ const DEFAULT_SERVER_OUTPUT_DIR = "dist/server";
557
+ const DEFAULT_SERVER_ENTRY_NAME = "server";
558
+ const PRERENDER_PREVIEW_HOST = "127.0.0.1";
559
+ const TSS_PRERENDERING_ENV = "TSS_PRERENDERING";
560
+ const SHIM_MARKER = "// Generated by @lovable.dev/vite-tanstack-config for the prerender preview server.";
561
+ function prerenderShimSource(vars) {
562
+ return `${SHIM_MARKER}
563
+ import server from "./${NITRO_SERVER_ENTRY}";
564
+
565
+ const env = ${JSON.stringify(vars)};
566
+ const ctx = { waitUntil() {}, passThroughOnException() {}, props: {} };
567
+
568
+ export default {
569
+ fetch(request) {
570
+ // srvx's NodeRequest exposes \`ip\` as a getter-only accessor and nitro's
571
+ // cloudflare module handler assigns to it; shadow it with a writable one.
572
+ Object.defineProperty(request, "ip", { value: undefined, writable: true, configurable: true });
573
+ return server.fetch(request, env, ctx);
574
+ },
575
+ };
576
+ `;
577
+ }
578
+ function prerenderPreviewShim(state) {
579
+ let root = process.cwd();
580
+ let serverOutDir = DEFAULT_SERVER_OUTPUT_DIR;
581
+ let serverEntryName = DEFAULT_SERVER_ENTRY_NAME;
582
+ return {
583
+ name: "lovable-prerender-preview-shim",
584
+ apply: "build",
585
+ config(userConfig) {
586
+ const ssrBuild = userConfig.environments?.ssr?.build;
587
+ if (ssrBuild?.outDir) serverOutDir = ssrBuild.outDir;
588
+ const input = ssrBuild?.rolldownOptions?.input ?? ssrBuild?.rollupOptions?.input;
589
+ if (typeof input === "string") serverEntryName = input;
590
+ },
591
+ configResolved(config) {
592
+ root = config.root;
593
+ },
594
+ buildApp: {
595
+ order: "post",
596
+ handler: async () => {
597
+ const { access, readFile, writeFile } = await import("node:fs/promises");
598
+ const nodePath = await import("node:path");
599
+ const exists = async (candidate) => {
600
+ try {
601
+ await access(candidate);
602
+ return true;
603
+ } catch {
604
+ return false;
605
+ }
606
+ };
607
+ const outDir = nodePath.resolve(root, serverOutDir);
608
+ if (!await exists(nodePath.join(outDir, NITRO_SERVER_ENTRY))) return;
609
+ const basename = nodePath.basename(serverEntryName, nodePath.extname(serverEntryName));
610
+ if (!basename || basename === "index") {
611
+ logWarning(`Cannot prerender: the server entry "${serverEntryName}" would need a shim at ${DEFAULT_SERVER_OUTPUT_DIR}/index.js, which outranks nitro's own entry when publishing. Rename the entry to anything but \`index\`.`);
612
+ return;
613
+ }
614
+ const target = nodePath.join(outDir, `${basename}.js`);
615
+ const occupant = await readFile(target, "utf8").catch(() => void 0);
616
+ if (occupant !== void 0 && !occupant.startsWith(SHIM_MARKER)) {
617
+ logWarning(`Cannot prerender: ${basename}.js already exists in ${serverOutDir} and was not written by this plugin.`);
618
+ return;
619
+ }
620
+ let vars = {};
621
+ try {
622
+ const candidate = JSON.parse(await readFile(nodePath.join(outDir, NITRO_WRANGLER_CONFIG), "utf8"))?.vars;
623
+ if (candidate && typeof candidate === "object" && !Array.isArray(candidate)) vars = candidate;
624
+ } catch {}
625
+ await writeFile(target, prerenderShimSource(vars));
626
+ state.file = target;
627
+ }
628
+ }
629
+ };
630
+ }
631
+ function prerenderPreviewShimCleanup(state) {
632
+ return {
633
+ name: "lovable-prerender-preview-shim-cleanup",
634
+ apply: "build",
635
+ enforce: "post",
636
+ buildApp: {
637
+ order: "post",
638
+ handler: async () => {
639
+ if (!state.file) return;
640
+ const { rm } = await import("node:fs/promises");
641
+ await rm(state.file, { force: true });
642
+ state.file = void 0;
643
+ }
644
+ }
645
+ };
646
+ }
647
+ function prerenderIsEnabled(startOptions) {
648
+ const spa = startOptions.spa;
649
+ if (spa && (spa.enabled ?? true)) return true;
650
+ const prerender = startOptions.prerender;
651
+ if (prerender?.enabled === false) return false;
652
+ const pages = Array.isArray(startOptions.pages) ? startOptions.pages : [];
653
+ return !!(prerender?.enabled ?? pages.some((page) => !!page?.prerender?.enabled));
654
+ }
554
655
  function devSsrErrorLogger() {
555
656
  let lastCapture;
556
657
  const CAPTURE_TTL_MS = 5e3;
@@ -730,6 +831,7 @@ function defineConfig(configOrOptions = {}) {
730
831
  specifiers: ["server-only"]
731
832
  }
732
833
  } }, options.tanstackStart ?? {});
834
+ const prerenderEnabled = prerenderIsEnabled(tanstackStartOptions);
733
835
  if (command === "build" && isSandbox) {
734
836
  const { startOptions, skipReporter } = pinPrerenderForLovableBuild(tanstackStartOptions);
735
837
  internalPlugins.push(tanstackStart(startOptions), skipReporter);
@@ -782,6 +884,10 @@ function defineConfig(configOrOptions = {}) {
782
884
  }
783
885
  } else if (!nitroOpts.preset && !await nitroSupportsDefaultPreset()) logWarning("The installed `nitro` predates the `defaultPreset` option (3.0.260603-beta), so the Cloudflare fallback is ignored and a zero-config build may target Node instead. Upgrade nitro to >=3.0.260603-beta, or set an explicit `nitro: { preset: \"cloudflare-module\" }`.");
784
886
  internalPlugins.push(nitro(nitroOpts));
887
+ if (prerenderEnabled) {
888
+ const shimState = {};
889
+ internalPlugins.push(prerenderPreviewShim(shimState), prerenderPreviewShimCleanup(shimState));
890
+ }
785
891
  if (isSandbox) internalPlugins.push(stripRedundantNodejsCompatFlag());
786
892
  }
787
893
  }
@@ -857,6 +963,10 @@ function defineConfig(configOrOptions = {}) {
857
963
  host: "::",
858
964
  port: 8080
859
965
  } }, config);
966
+ if (env.isPreview && prerenderEnabled && process.env[TSS_PRERENDERING_ENV] === "true") config = (0, vite.mergeConfig)({ preview: {
967
+ host: PRERENDER_PREVIEW_HOST,
968
+ port: 0
969
+ } }, config);
860
970
  if (isSandbox) return config;
861
971
  return applyWatchDebounceDefaults(config);
862
972
  };
package/dist/index.js CHANGED
@@ -527,6 +527,107 @@ function stripRedundantNodejsCompatFlag() {
527
527
  }
528
528
  };
529
529
  }
530
+ const NITRO_SERVER_ENTRY = "index.mjs";
531
+ const NITRO_WRANGLER_CONFIG = "wrangler.json";
532
+ const DEFAULT_SERVER_OUTPUT_DIR = "dist/server";
533
+ const DEFAULT_SERVER_ENTRY_NAME = "server";
534
+ const PRERENDER_PREVIEW_HOST = "127.0.0.1";
535
+ const TSS_PRERENDERING_ENV = "TSS_PRERENDERING";
536
+ const SHIM_MARKER = "// Generated by @lovable.dev/vite-tanstack-config for the prerender preview server.";
537
+ function prerenderShimSource(vars) {
538
+ return `${SHIM_MARKER}
539
+ import server from "./${NITRO_SERVER_ENTRY}";
540
+
541
+ const env = ${JSON.stringify(vars)};
542
+ const ctx = { waitUntil() {}, passThroughOnException() {}, props: {} };
543
+
544
+ export default {
545
+ fetch(request) {
546
+ // srvx's NodeRequest exposes \`ip\` as a getter-only accessor and nitro's
547
+ // cloudflare module handler assigns to it; shadow it with a writable one.
548
+ Object.defineProperty(request, "ip", { value: undefined, writable: true, configurable: true });
549
+ return server.fetch(request, env, ctx);
550
+ },
551
+ };
552
+ `;
553
+ }
554
+ function prerenderPreviewShim(state) {
555
+ let root = process.cwd();
556
+ let serverOutDir = DEFAULT_SERVER_OUTPUT_DIR;
557
+ let serverEntryName = DEFAULT_SERVER_ENTRY_NAME;
558
+ return {
559
+ name: "lovable-prerender-preview-shim",
560
+ apply: "build",
561
+ config(userConfig) {
562
+ const ssrBuild = userConfig.environments?.ssr?.build;
563
+ if (ssrBuild?.outDir) serverOutDir = ssrBuild.outDir;
564
+ const input = ssrBuild?.rolldownOptions?.input ?? ssrBuild?.rollupOptions?.input;
565
+ if (typeof input === "string") serverEntryName = input;
566
+ },
567
+ configResolved(config) {
568
+ root = config.root;
569
+ },
570
+ buildApp: {
571
+ order: "post",
572
+ handler: async () => {
573
+ const { access, readFile, writeFile } = await import("node:fs/promises");
574
+ const nodePath = await import("node:path");
575
+ const exists = async (candidate) => {
576
+ try {
577
+ await access(candidate);
578
+ return true;
579
+ } catch {
580
+ return false;
581
+ }
582
+ };
583
+ const outDir = nodePath.resolve(root, serverOutDir);
584
+ if (!await exists(nodePath.join(outDir, NITRO_SERVER_ENTRY))) return;
585
+ const basename = nodePath.basename(serverEntryName, nodePath.extname(serverEntryName));
586
+ if (!basename || basename === "index") {
587
+ logWarning(`Cannot prerender: the server entry "${serverEntryName}" would need a shim at ${DEFAULT_SERVER_OUTPUT_DIR}/index.js, which outranks nitro's own entry when publishing. Rename the entry to anything but \`index\`.`);
588
+ return;
589
+ }
590
+ const target = nodePath.join(outDir, `${basename}.js`);
591
+ const occupant = await readFile(target, "utf8").catch(() => void 0);
592
+ if (occupant !== void 0 && !occupant.startsWith(SHIM_MARKER)) {
593
+ logWarning(`Cannot prerender: ${basename}.js already exists in ${serverOutDir} and was not written by this plugin.`);
594
+ return;
595
+ }
596
+ let vars = {};
597
+ try {
598
+ const candidate = JSON.parse(await readFile(nodePath.join(outDir, NITRO_WRANGLER_CONFIG), "utf8"))?.vars;
599
+ if (candidate && typeof candidate === "object" && !Array.isArray(candidate)) vars = candidate;
600
+ } catch {}
601
+ await writeFile(target, prerenderShimSource(vars));
602
+ state.file = target;
603
+ }
604
+ }
605
+ };
606
+ }
607
+ function prerenderPreviewShimCleanup(state) {
608
+ return {
609
+ name: "lovable-prerender-preview-shim-cleanup",
610
+ apply: "build",
611
+ enforce: "post",
612
+ buildApp: {
613
+ order: "post",
614
+ handler: async () => {
615
+ if (!state.file) return;
616
+ const { rm } = await import("node:fs/promises");
617
+ await rm(state.file, { force: true });
618
+ state.file = void 0;
619
+ }
620
+ }
621
+ };
622
+ }
623
+ function prerenderIsEnabled(startOptions) {
624
+ const spa = startOptions.spa;
625
+ if (spa && (spa.enabled ?? true)) return true;
626
+ const prerender = startOptions.prerender;
627
+ if (prerender?.enabled === false) return false;
628
+ const pages = Array.isArray(startOptions.pages) ? startOptions.pages : [];
629
+ return !!(prerender?.enabled ?? pages.some((page) => !!page?.prerender?.enabled));
630
+ }
530
631
  function devSsrErrorLogger() {
531
632
  let lastCapture;
532
633
  const CAPTURE_TTL_MS = 5e3;
@@ -706,6 +807,7 @@ function defineConfig(configOrOptions = {}) {
706
807
  specifiers: ["server-only"]
707
808
  }
708
809
  } }, options.tanstackStart ?? {});
810
+ const prerenderEnabled = prerenderIsEnabled(tanstackStartOptions);
709
811
  if (command === "build" && isSandbox) {
710
812
  const { startOptions, skipReporter } = pinPrerenderForLovableBuild(tanstackStartOptions);
711
813
  internalPlugins.push(tanstackStart(startOptions), skipReporter);
@@ -758,6 +860,10 @@ function defineConfig(configOrOptions = {}) {
758
860
  }
759
861
  } else if (!nitroOpts.preset && !await nitroSupportsDefaultPreset()) logWarning("The installed `nitro` predates the `defaultPreset` option (3.0.260603-beta), so the Cloudflare fallback is ignored and a zero-config build may target Node instead. Upgrade nitro to >=3.0.260603-beta, or set an explicit `nitro: { preset: \"cloudflare-module\" }`.");
760
862
  internalPlugins.push(nitro(nitroOpts));
863
+ if (prerenderEnabled) {
864
+ const shimState = {};
865
+ internalPlugins.push(prerenderPreviewShim(shimState), prerenderPreviewShimCleanup(shimState));
866
+ }
761
867
  if (isSandbox) internalPlugins.push(stripRedundantNodejsCompatFlag());
762
868
  }
763
869
  }
@@ -833,6 +939,10 @@ function defineConfig(configOrOptions = {}) {
833
939
  host: "::",
834
940
  port: 8080
835
941
  } }, config);
942
+ if (env.isPreview && prerenderEnabled && process.env[TSS_PRERENDERING_ENV] === "true") config = mergeConfig({ preview: {
943
+ host: PRERENDER_PREVIEW_HOST,
944
+ port: 0
945
+ } }, config);
836
946
  if (isSandbox) return config;
837
947
  return applyWatchDebounceDefaults(config);
838
948
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lovable.dev/vite-tanstack-config",
3
- "version": "2.16.0",
3
+ "version": "2.17.1",
4
4
  "description": "Vite config wrapper for Lovable TanStack Start projects",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.js",