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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -150,6 +150,186 @@ function bundledDevCssUrlShim() {
150
150
  };
151
151
  }
152
152
  //#endregion
153
+ //#region package.json
154
+ var name = "@lovable.dev/vite-tanstack-config";
155
+ var version = "2.18.0";
156
+ //#endregion
157
+ //#region src/logging.ts
158
+ const PACKAGE_NAME = "@lovable.dev/vite-tanstack-config";
159
+ function logWarning(message) {
160
+ console.warn(`[${PACKAGE_NAME}] ${message}`);
161
+ }
162
+ //#endregion
163
+ //#region src/outputPaths.ts
164
+ const PUBLISH_OUTPUT_DIR = "dist";
165
+ const SERVER_OUTPUT_DIR = `${PUBLISH_OUTPUT_DIR}/server`;
166
+ const CLIENT_OUTPUT_DIR = `${PUBLISH_OUTPUT_DIR}/client`;
167
+ //#endregion
168
+ //#region src/documents.ts
169
+ const DOCUMENT_SET_VERSION = 1;
170
+ const GENERATOR = `${name}@${version}`;
171
+ const DOCUMENT_SET_DIR = ".lovable";
172
+ const DOCUMENT_SET_FILE = "output.json";
173
+ const DOCUMENT_SET_MAX_ENTRIES = 1e4;
174
+ const DOCUMENT_SET_MAX_BYTES = 4e6;
175
+ const DOCUMENT_SET_PLUGIN_NAME = "lovable:document-set";
176
+ const TANSTACK_POST_BUILD_PLUGIN = "tanstack-start-core:post-build";
177
+ const DOT_SEGMENT = /(^|\/)\.(\/|$)/;
178
+ function hasControlChars(value) {
179
+ for (let i = 0; i < value.length; i++) {
180
+ const code = value.charCodeAt(i);
181
+ if (code < 32 || code === 127) return true;
182
+ }
183
+ return false;
184
+ }
185
+ function normalizeRoute(raw) {
186
+ if (typeof raw !== "string") return void 0;
187
+ const pathname = raw.split(/[?#]/)[0] ?? "";
188
+ if (!pathname.startsWith("/")) return void 0;
189
+ const route = pathname.replace(/\/+$/, "") || "/";
190
+ if (route.includes("//") || route.includes("..") || route.includes("\\") || DOT_SEGMENT.test(route) || hasControlChars(route)) return;
191
+ return route;
192
+ }
193
+ /** Upstream's `joinURL("/", router.basepath)`: leading slash, no trailing slash, "" when unset. */
194
+ function normalizeBasePath(raw) {
195
+ if (typeof raw !== "string") return "";
196
+ const trimmed = raw.trim().replace(/\/+$/, "");
197
+ if (trimmed === "") return "";
198
+ return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
199
+ }
200
+ function withoutBasePath(route, basePath) {
201
+ if (route === basePath) return "/";
202
+ if (basePath === "") return route;
203
+ return route.startsWith(`${basePath}/`) ? route.slice(basePath.length) : route;
204
+ }
205
+ function documentEntryForPage(page, basePath = "") {
206
+ if (typeof page.prerender?.outputPath === "string") return void 0;
207
+ const pagePath = normalizeRoute(page.path);
208
+ if (pagePath === void 0) return void 0;
209
+ const inner = withoutBasePath(pagePath, basePath);
210
+ const route = normalizeRoute(`${basePath}${inner}`);
211
+ if (route === void 0) return void 0;
212
+ const file = inner === "/" ? "index.html" : `${inner.slice(1)}/index.html`;
213
+ if (file !== file.trim()) return void 0;
214
+ return {
215
+ route,
216
+ file
217
+ };
218
+ }
219
+ function compareRoutes(a, b) {
220
+ if (a.route < b.route) return -1;
221
+ if (a.route > b.route) return 1;
222
+ return 0;
223
+ }
224
+ /** The artifact bytes, or undefined when there is nothing to publish. */
225
+ function serializeDocumentSet(entries, generator) {
226
+ const sorted = [...entries].sort(compareRoutes);
227
+ const selected = [];
228
+ let cappedBy;
229
+ let length = JSON.stringify({
230
+ version: DOCUMENT_SET_VERSION,
231
+ generator,
232
+ documents: []
233
+ }).length;
234
+ for (const entry of sorted) {
235
+ if (selected.length === DOCUMENT_SET_MAX_ENTRIES) {
236
+ cappedBy = "entry limit";
237
+ break;
238
+ }
239
+ const added = JSON.stringify(entry).length + 1;
240
+ if (length + added > DOCUMENT_SET_MAX_BYTES) {
241
+ cappedBy = "byte limit";
242
+ break;
243
+ }
244
+ length += added;
245
+ selected.push(entry);
246
+ }
247
+ if (cappedBy !== void 0) logWarning(`Publishing only ${selected.length} of ${sorted.length} publishable routes; the document set hit its ${cappedBy}.`);
248
+ if (selected.length === 0) return void 0;
249
+ return JSON.stringify({
250
+ version: DOCUMENT_SET_VERSION,
251
+ generator,
252
+ documents: selected
253
+ });
254
+ }
255
+ const EXISTENCE_BATCH_SIZE = 64;
256
+ async function whichExist(paths, access) {
257
+ const exists = [];
258
+ for (let i = 0; i < paths.length; i += EXISTENCE_BATCH_SIZE) {
259
+ const batch = paths.slice(i, i + EXISTENCE_BATCH_SIZE).map((path) => access(path).then(() => true, () => false));
260
+ exists.push(...await Promise.all(batch));
261
+ }
262
+ return exists;
263
+ }
264
+ function createDocumentSet(routerBasePath) {
265
+ const basePath = normalizeBasePath(routerBasePath);
266
+ const basePathServable = basePath === "" || normalizeRoute(basePath) === basePath;
267
+ if (!basePathServable) logWarning(`Not publishing any prerendered documents: router.basepath "${basePath}" is not a servable route.`);
268
+ const documents = /* @__PURE__ */ new Map();
269
+ let root = process.cwd();
270
+ let clientOutputDir = CLIENT_OUTPUT_DIR;
271
+ const plugin = {
272
+ name: DOCUMENT_SET_PLUGIN_NAME,
273
+ apply: "build",
274
+ enforce: "post",
275
+ configResolved(config) {
276
+ root = config.root;
277
+ const names = config.plugins?.map((candidate) => candidate.name) ?? [];
278
+ const theirs = names.indexOf(TANSTACK_POST_BUILD_PLUGIN);
279
+ const ours = names.indexOf(DOCUMENT_SET_PLUGIN_NAME);
280
+ if (theirs !== -1 && ours !== -1 && ours < theirs) logWarning(`Prerendered routes will fall back to SSR: ${DOCUMENT_SET_PLUGIN_NAME} is registered before ${TANSTACK_POST_BUILD_PLUGIN}, so the document set is written before the prerender pass runs.`);
281
+ clientOutputDir = config.environments?.client?.build?.outDir ?? CLIENT_OUTPUT_DIR;
282
+ },
283
+ buildApp: {
284
+ order: "post",
285
+ async handler() {
286
+ const { access, mkdir, rm, writeFile } = await import("node:fs/promises");
287
+ const nodePath = await import("node:path");
288
+ const artifact = nodePath.join(root, PUBLISH_OUTPUT_DIR, DOCUMENT_SET_DIR, DOCUMENT_SET_FILE);
289
+ await rm(artifact, { force: true }).catch((error) => {
290
+ logWarning(`Could not remove a previous build's document set: ${String(error)}`);
291
+ });
292
+ if (documents.size === 0) return;
293
+ const clientDir = nodePath.resolve(root, clientOutputDir);
294
+ const entries = [...documents].map(([route, file]) => ({
295
+ route,
296
+ file
297
+ }));
298
+ const exists = await whichExist(entries.map(({ file }) => nodePath.resolve(clientDir, file)), access);
299
+ const present = [];
300
+ for (const [index, entry] of entries.entries()) {
301
+ if (exists[index]) {
302
+ present.push(entry);
303
+ continue;
304
+ }
305
+ logWarning(`Not publishing prerendered route ${entry.route}: it produced no ${entry.file}.`);
306
+ }
307
+ const payload = serializeDocumentSet(present, GENERATOR);
308
+ if (payload === void 0) return;
309
+ try {
310
+ await mkdir(nodePath.dirname(artifact), { recursive: true });
311
+ await writeFile(artifact, payload);
312
+ } catch (error) {
313
+ logWarning(`Could not write the document set; prerendered routes fall back to SSR: ${String(error)}`);
314
+ }
315
+ }
316
+ }
317
+ };
318
+ const record = (page) => {
319
+ if (!basePathServable) return;
320
+ const entry = documentEntryForPage(page, basePath);
321
+ if (entry === void 0) {
322
+ if (typeof page.prerender?.outputPath !== "string") logWarning(`Not publishing prerendered route ${String(page.path)}: its path cannot be a document key (dot segments, "//", "..", backslashes, control characters, and leading/trailing whitespace are rejected).`);
323
+ return;
324
+ }
325
+ documents.set(entry.route, entry.file);
326
+ };
327
+ return {
328
+ record,
329
+ plugin
330
+ };
331
+ }
332
+ //#endregion
153
333
  //#region src/index.ts
154
334
  const SANDBOX_ENV_VAR = "DEV_SERVER__PROJECT_PATH";
155
335
  const LOVABLE_SANDBOX_ENV_VAR = "LOVABLE_SANDBOX";
@@ -163,9 +343,6 @@ const TOOL_MANAGED_WATCH_IGNORES = [
163
343
  "**/.lovable/**",
164
344
  "**/.tanstack/tmp/**"
165
345
  ];
166
- function logWarning(message) {
167
- console.warn(`[@lovable.dev/vite-tanstack-config] ${message}`);
168
- }
169
346
  const NITRO_DEFAULT_PRESET_MIN = [
170
347
  3,
171
348
  0,
@@ -214,6 +391,7 @@ function pinPrerenderOptions(source, pins, label) {
214
391
  function pinPrerenderForLovableBuild(userOptions) {
215
392
  const attempted = /* @__PURE__ */ new Set();
216
393
  const rendered = /* @__PURE__ */ new Set();
394
+ const collector = createDocumentSet(userOptions.router?.basepath);
217
395
  const userPrerender = userOptions.prerender;
218
396
  const prerender = pinPrerenderOptions(userPrerender, ROOT_PRERENDER_PINS, "prerender");
219
397
  const userFilter = userPrerender?.filter;
@@ -224,6 +402,7 @@ function pinPrerenderForLovableBuild(userOptions) {
224
402
  };
225
403
  const withRenderTracking = (userOnSuccess) => (ctx) => {
226
404
  rendered.add(ctx.page.path);
405
+ collector.record(ctx.page);
227
406
  return typeof userOnSuccess === "function" ? userOnSuccess(ctx) : void 0;
228
407
  };
229
408
  prerender.onSuccess = withRenderTracking(userPrerender?.onSuccess);
@@ -256,7 +435,8 @@ function pinPrerenderForLovableBuild(userOptions) {
256
435
  logWarning(`Prerender skipped ${skipped.length} route(s) that failed to render; they fall back to SSR: ${skipped.join(", ")}`);
257
436
  }
258
437
  }
259
- }
438
+ },
439
+ documentSet: collector.plugin
260
440
  };
261
441
  }
262
442
  function lovableFetchBundlePreset() {
@@ -266,7 +446,7 @@ function lovableFetchBundlePreset() {
266
446
  noExternals: true,
267
447
  inlineDynamicImports: true,
268
448
  output: {
269
- dir: "dist",
449
+ dir: PUBLISH_OUTPUT_DIR,
270
450
  serverDir: "{{ output.dir }}/server",
271
451
  publicDir: "{{ output.dir }}/client"
272
452
  }
@@ -516,7 +696,7 @@ function lovableBuildErrorDiagnostics() {
516
696
  };
517
697
  }
518
698
  const NODEJS_COMPAT_DEFAULT_ON_DATE = "2026-08-04";
519
- const SANDBOX_WRANGLER_CONFIG_PATH = "dist/server/wrangler.json";
699
+ const SANDBOX_WRANGLER_CONFIG_PATH = `${SERVER_OUTPUT_DIR}/wrangler.json`;
520
700
  function stripRedundantNodejsCompatFlag() {
521
701
  let root = process.cwd();
522
702
  return {
@@ -553,7 +733,6 @@ function stripRedundantNodejsCompatFlag() {
553
733
  }
554
734
  const NITRO_SERVER_ENTRY = "index.mjs";
555
735
  const NITRO_WRANGLER_CONFIG = "wrangler.json";
556
- const DEFAULT_SERVER_OUTPUT_DIR = "dist/server";
557
736
  const DEFAULT_SERVER_ENTRY_NAME = "server";
558
737
  const PRERENDER_PREVIEW_HOST = "127.0.0.1";
559
738
  const TSS_PRERENDERING_ENV = "TSS_PRERENDERING";
@@ -577,7 +756,7 @@ export default {
577
756
  }
578
757
  function prerenderPreviewShim(state) {
579
758
  let root = process.cwd();
580
- let serverOutDir = DEFAULT_SERVER_OUTPUT_DIR;
759
+ let serverOutDir = SERVER_OUTPUT_DIR;
581
760
  let serverEntryName = DEFAULT_SERVER_ENTRY_NAME;
582
761
  return {
583
762
  name: "lovable-prerender-preview-shim",
@@ -608,7 +787,7 @@ function prerenderPreviewShim(state) {
608
787
  if (!await exists(nodePath.join(outDir, NITRO_SERVER_ENTRY))) return;
609
788
  const basename = nodePath.basename(serverEntryName, nodePath.extname(serverEntryName));
610
789
  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\`.`);
790
+ logWarning(`Cannot prerender: the server entry "${serverEntryName}" would need a shim at ${SERVER_OUTPUT_DIR}/index.js, which outranks nitro's own entry when publishing. Rename the entry to anything but \`index\`.`);
612
791
  return;
613
792
  }
614
793
  const target = nodePath.join(outDir, `${basename}.js`);
@@ -833,8 +1012,8 @@ function defineConfig(configOrOptions = {}) {
833
1012
  } }, options.tanstackStart ?? {});
834
1013
  const prerenderEnabled = prerenderIsEnabled(tanstackStartOptions);
835
1014
  if (command === "build" && isSandbox) {
836
- const { startOptions, skipReporter } = pinPrerenderForLovableBuild(tanstackStartOptions);
837
- internalPlugins.push(tanstackStart(startOptions), skipReporter);
1015
+ const { startOptions, skipReporter, documentSet } = pinPrerenderForLovableBuild(tanstackStartOptions);
1016
+ internalPlugins.push(tanstackStart(startOptions), skipReporter, documentSet);
838
1017
  } else internalPlugins.push(tanstackStart(tanstackStartOptions));
839
1018
  const explicitNitro = options.nitro === true || typeof options.nitro === "object" && options.nitro !== null;
840
1019
  if (options.nitro !== false && command === "build") {
@@ -872,9 +1051,9 @@ function defineConfig(configOrOptions = {}) {
872
1051
  delete nitroOpts.defaultPreset;
873
1052
  nitroOpts.preset = "cloudflare-module";
874
1053
  nitroOpts.output = {
875
- dir: "dist",
876
- serverDir: "dist/server",
877
- publicDir: "dist/client"
1054
+ dir: PUBLISH_OUTPUT_DIR,
1055
+ serverDir: SERVER_OUTPUT_DIR,
1056
+ publicDir: CLIENT_OUTPUT_DIR
878
1057
  };
879
1058
  nitroOpts.cloudflare = {
880
1059
  nodeCompat: true,
package/dist/index.d.cts CHANGED
@@ -11,11 +11,13 @@ import { ConfigEnv, Plugin, PluginOption, UserConfig, UserConfigFn, UserConfigFn
11
11
  */
12
12
  declare function lovableAssetsProxyPlugin(): Plugin;
13
13
  //#endregion
14
- //#region src/index.d.ts
14
+ //#region src/documents.d.ts
15
15
  type PrerenderPage = {
16
16
  path: string;
17
17
  prerender?: Record<string, unknown>;
18
18
  };
19
+ //#endregion
20
+ //#region src/index.d.ts
19
21
  declare function stripRedundantNodejsCompatFlag(): Plugin;
20
22
  interface LovableViteTanstackOptions {
21
23
  plugins?: PluginOption[];
@@ -72,4 +74,4 @@ declare function defineConfig(config: UserConfigFnPromise): (env: ConfigEnv) =>
72
74
  declare function defineConfig(config: UserConfigFn): (env: ConfigEnv) => Promise<UserConfig>;
73
75
  declare function defineConfig(options?: LovableViteTanstackOptions): (env: ConfigEnv) => Promise<UserConfig>;
74
76
  //#endregion
75
- export { LovableViteTanstackOptions, PrerenderPage, defineConfig, lovableAssetsProxyPlugin, stripRedundantNodejsCompatFlag };
77
+ export { LovableViteTanstackOptions, type PrerenderPage, defineConfig, lovableAssetsProxyPlugin, stripRedundantNodejsCompatFlag };
package/dist/index.d.ts CHANGED
@@ -11,11 +11,13 @@ import { ConfigEnv, Plugin, PluginOption, UserConfig, UserConfigFn, UserConfigFn
11
11
  */
12
12
  declare function lovableAssetsProxyPlugin(): Plugin;
13
13
  //#endregion
14
- //#region src/index.d.ts
14
+ //#region src/documents.d.ts
15
15
  type PrerenderPage = {
16
16
  path: string;
17
17
  prerender?: Record<string, unknown>;
18
18
  };
19
+ //#endregion
20
+ //#region src/index.d.ts
19
21
  declare function stripRedundantNodejsCompatFlag(): Plugin;
20
22
  interface LovableViteTanstackOptions {
21
23
  plugins?: PluginOption[];
@@ -72,4 +74,4 @@ declare function defineConfig(config: UserConfigFnPromise): (env: ConfigEnv) =>
72
74
  declare function defineConfig(config: UserConfigFn): (env: ConfigEnv) => Promise<UserConfig>;
73
75
  declare function defineConfig(options?: LovableViteTanstackOptions): (env: ConfigEnv) => Promise<UserConfig>;
74
76
  //#endregion
75
- export { LovableViteTanstackOptions, PrerenderPage, defineConfig, lovableAssetsProxyPlugin, stripRedundantNodejsCompatFlag };
77
+ export { LovableViteTanstackOptions, type PrerenderPage, defineConfig, lovableAssetsProxyPlugin, stripRedundantNodejsCompatFlag };
package/dist/index.js CHANGED
@@ -126,6 +126,186 @@ function bundledDevCssUrlShim() {
126
126
  };
127
127
  }
128
128
  //#endregion
129
+ //#region package.json
130
+ var name = "@lovable.dev/vite-tanstack-config";
131
+ var version = "2.18.0";
132
+ //#endregion
133
+ //#region src/logging.ts
134
+ const PACKAGE_NAME = "@lovable.dev/vite-tanstack-config";
135
+ function logWarning(message) {
136
+ console.warn(`[${PACKAGE_NAME}] ${message}`);
137
+ }
138
+ //#endregion
139
+ //#region src/outputPaths.ts
140
+ const PUBLISH_OUTPUT_DIR = "dist";
141
+ const SERVER_OUTPUT_DIR = `${PUBLISH_OUTPUT_DIR}/server`;
142
+ const CLIENT_OUTPUT_DIR = `${PUBLISH_OUTPUT_DIR}/client`;
143
+ //#endregion
144
+ //#region src/documents.ts
145
+ const DOCUMENT_SET_VERSION = 1;
146
+ const GENERATOR = `${name}@${version}`;
147
+ const DOCUMENT_SET_DIR = ".lovable";
148
+ const DOCUMENT_SET_FILE = "output.json";
149
+ const DOCUMENT_SET_MAX_ENTRIES = 1e4;
150
+ const DOCUMENT_SET_MAX_BYTES = 4e6;
151
+ const DOCUMENT_SET_PLUGIN_NAME = "lovable:document-set";
152
+ const TANSTACK_POST_BUILD_PLUGIN = "tanstack-start-core:post-build";
153
+ const DOT_SEGMENT = /(^|\/)\.(\/|$)/;
154
+ function hasControlChars(value) {
155
+ for (let i = 0; i < value.length; i++) {
156
+ const code = value.charCodeAt(i);
157
+ if (code < 32 || code === 127) return true;
158
+ }
159
+ return false;
160
+ }
161
+ function normalizeRoute(raw) {
162
+ if (typeof raw !== "string") return void 0;
163
+ const pathname = raw.split(/[?#]/)[0] ?? "";
164
+ if (!pathname.startsWith("/")) return void 0;
165
+ const route = pathname.replace(/\/+$/, "") || "/";
166
+ if (route.includes("//") || route.includes("..") || route.includes("\\") || DOT_SEGMENT.test(route) || hasControlChars(route)) return;
167
+ return route;
168
+ }
169
+ /** Upstream's `joinURL("/", router.basepath)`: leading slash, no trailing slash, "" when unset. */
170
+ function normalizeBasePath(raw) {
171
+ if (typeof raw !== "string") return "";
172
+ const trimmed = raw.trim().replace(/\/+$/, "");
173
+ if (trimmed === "") return "";
174
+ return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
175
+ }
176
+ function withoutBasePath(route, basePath) {
177
+ if (route === basePath) return "/";
178
+ if (basePath === "") return route;
179
+ return route.startsWith(`${basePath}/`) ? route.slice(basePath.length) : route;
180
+ }
181
+ function documentEntryForPage(page, basePath = "") {
182
+ if (typeof page.prerender?.outputPath === "string") return void 0;
183
+ const pagePath = normalizeRoute(page.path);
184
+ if (pagePath === void 0) return void 0;
185
+ const inner = withoutBasePath(pagePath, basePath);
186
+ const route = normalizeRoute(`${basePath}${inner}`);
187
+ if (route === void 0) return void 0;
188
+ const file = inner === "/" ? "index.html" : `${inner.slice(1)}/index.html`;
189
+ if (file !== file.trim()) return void 0;
190
+ return {
191
+ route,
192
+ file
193
+ };
194
+ }
195
+ function compareRoutes(a, b) {
196
+ if (a.route < b.route) return -1;
197
+ if (a.route > b.route) return 1;
198
+ return 0;
199
+ }
200
+ /** The artifact bytes, or undefined when there is nothing to publish. */
201
+ function serializeDocumentSet(entries, generator) {
202
+ const sorted = [...entries].sort(compareRoutes);
203
+ const selected = [];
204
+ let cappedBy;
205
+ let length = JSON.stringify({
206
+ version: DOCUMENT_SET_VERSION,
207
+ generator,
208
+ documents: []
209
+ }).length;
210
+ for (const entry of sorted) {
211
+ if (selected.length === DOCUMENT_SET_MAX_ENTRIES) {
212
+ cappedBy = "entry limit";
213
+ break;
214
+ }
215
+ const added = JSON.stringify(entry).length + 1;
216
+ if (length + added > DOCUMENT_SET_MAX_BYTES) {
217
+ cappedBy = "byte limit";
218
+ break;
219
+ }
220
+ length += added;
221
+ selected.push(entry);
222
+ }
223
+ if (cappedBy !== void 0) logWarning(`Publishing only ${selected.length} of ${sorted.length} publishable routes; the document set hit its ${cappedBy}.`);
224
+ if (selected.length === 0) return void 0;
225
+ return JSON.stringify({
226
+ version: DOCUMENT_SET_VERSION,
227
+ generator,
228
+ documents: selected
229
+ });
230
+ }
231
+ const EXISTENCE_BATCH_SIZE = 64;
232
+ async function whichExist(paths, access) {
233
+ const exists = [];
234
+ for (let i = 0; i < paths.length; i += EXISTENCE_BATCH_SIZE) {
235
+ const batch = paths.slice(i, i + EXISTENCE_BATCH_SIZE).map((path) => access(path).then(() => true, () => false));
236
+ exists.push(...await Promise.all(batch));
237
+ }
238
+ return exists;
239
+ }
240
+ function createDocumentSet(routerBasePath) {
241
+ const basePath = normalizeBasePath(routerBasePath);
242
+ const basePathServable = basePath === "" || normalizeRoute(basePath) === basePath;
243
+ if (!basePathServable) logWarning(`Not publishing any prerendered documents: router.basepath "${basePath}" is not a servable route.`);
244
+ const documents = /* @__PURE__ */ new Map();
245
+ let root = process.cwd();
246
+ let clientOutputDir = CLIENT_OUTPUT_DIR;
247
+ const plugin = {
248
+ name: DOCUMENT_SET_PLUGIN_NAME,
249
+ apply: "build",
250
+ enforce: "post",
251
+ configResolved(config) {
252
+ root = config.root;
253
+ const names = config.plugins?.map((candidate) => candidate.name) ?? [];
254
+ const theirs = names.indexOf(TANSTACK_POST_BUILD_PLUGIN);
255
+ const ours = names.indexOf(DOCUMENT_SET_PLUGIN_NAME);
256
+ if (theirs !== -1 && ours !== -1 && ours < theirs) logWarning(`Prerendered routes will fall back to SSR: ${DOCUMENT_SET_PLUGIN_NAME} is registered before ${TANSTACK_POST_BUILD_PLUGIN}, so the document set is written before the prerender pass runs.`);
257
+ clientOutputDir = config.environments?.client?.build?.outDir ?? CLIENT_OUTPUT_DIR;
258
+ },
259
+ buildApp: {
260
+ order: "post",
261
+ async handler() {
262
+ const { access, mkdir, rm, writeFile } = await import("node:fs/promises");
263
+ const nodePath = await import("node:path");
264
+ const artifact = nodePath.join(root, PUBLISH_OUTPUT_DIR, DOCUMENT_SET_DIR, DOCUMENT_SET_FILE);
265
+ await rm(artifact, { force: true }).catch((error) => {
266
+ logWarning(`Could not remove a previous build's document set: ${String(error)}`);
267
+ });
268
+ if (documents.size === 0) return;
269
+ const clientDir = nodePath.resolve(root, clientOutputDir);
270
+ const entries = [...documents].map(([route, file]) => ({
271
+ route,
272
+ file
273
+ }));
274
+ const exists = await whichExist(entries.map(({ file }) => nodePath.resolve(clientDir, file)), access);
275
+ const present = [];
276
+ for (const [index, entry] of entries.entries()) {
277
+ if (exists[index]) {
278
+ present.push(entry);
279
+ continue;
280
+ }
281
+ logWarning(`Not publishing prerendered route ${entry.route}: it produced no ${entry.file}.`);
282
+ }
283
+ const payload = serializeDocumentSet(present, GENERATOR);
284
+ if (payload === void 0) return;
285
+ try {
286
+ await mkdir(nodePath.dirname(artifact), { recursive: true });
287
+ await writeFile(artifact, payload);
288
+ } catch (error) {
289
+ logWarning(`Could not write the document set; prerendered routes fall back to SSR: ${String(error)}`);
290
+ }
291
+ }
292
+ }
293
+ };
294
+ const record = (page) => {
295
+ if (!basePathServable) return;
296
+ const entry = documentEntryForPage(page, basePath);
297
+ if (entry === void 0) {
298
+ if (typeof page.prerender?.outputPath !== "string") logWarning(`Not publishing prerendered route ${String(page.path)}: its path cannot be a document key (dot segments, "//", "..", backslashes, control characters, and leading/trailing whitespace are rejected).`);
299
+ return;
300
+ }
301
+ documents.set(entry.route, entry.file);
302
+ };
303
+ return {
304
+ record,
305
+ plugin
306
+ };
307
+ }
308
+ //#endregion
129
309
  //#region src/index.ts
130
310
  const SANDBOX_ENV_VAR = "DEV_SERVER__PROJECT_PATH";
131
311
  const LOVABLE_SANDBOX_ENV_VAR = "LOVABLE_SANDBOX";
@@ -139,9 +319,6 @@ const TOOL_MANAGED_WATCH_IGNORES = [
139
319
  "**/.lovable/**",
140
320
  "**/.tanstack/tmp/**"
141
321
  ];
142
- function logWarning(message) {
143
- console.warn(`[@lovable.dev/vite-tanstack-config] ${message}`);
144
- }
145
322
  const NITRO_DEFAULT_PRESET_MIN = [
146
323
  3,
147
324
  0,
@@ -190,6 +367,7 @@ function pinPrerenderOptions(source, pins, label) {
190
367
  function pinPrerenderForLovableBuild(userOptions) {
191
368
  const attempted = /* @__PURE__ */ new Set();
192
369
  const rendered = /* @__PURE__ */ new Set();
370
+ const collector = createDocumentSet(userOptions.router?.basepath);
193
371
  const userPrerender = userOptions.prerender;
194
372
  const prerender = pinPrerenderOptions(userPrerender, ROOT_PRERENDER_PINS, "prerender");
195
373
  const userFilter = userPrerender?.filter;
@@ -200,6 +378,7 @@ function pinPrerenderForLovableBuild(userOptions) {
200
378
  };
201
379
  const withRenderTracking = (userOnSuccess) => (ctx) => {
202
380
  rendered.add(ctx.page.path);
381
+ collector.record(ctx.page);
203
382
  return typeof userOnSuccess === "function" ? userOnSuccess(ctx) : void 0;
204
383
  };
205
384
  prerender.onSuccess = withRenderTracking(userPrerender?.onSuccess);
@@ -232,7 +411,8 @@ function pinPrerenderForLovableBuild(userOptions) {
232
411
  logWarning(`Prerender skipped ${skipped.length} route(s) that failed to render; they fall back to SSR: ${skipped.join(", ")}`);
233
412
  }
234
413
  }
235
- }
414
+ },
415
+ documentSet: collector.plugin
236
416
  };
237
417
  }
238
418
  function lovableFetchBundlePreset() {
@@ -242,7 +422,7 @@ function lovableFetchBundlePreset() {
242
422
  noExternals: true,
243
423
  inlineDynamicImports: true,
244
424
  output: {
245
- dir: "dist",
425
+ dir: PUBLISH_OUTPUT_DIR,
246
426
  serverDir: "{{ output.dir }}/server",
247
427
  publicDir: "{{ output.dir }}/client"
248
428
  }
@@ -492,7 +672,7 @@ function lovableBuildErrorDiagnostics() {
492
672
  };
493
673
  }
494
674
  const NODEJS_COMPAT_DEFAULT_ON_DATE = "2026-08-04";
495
- const SANDBOX_WRANGLER_CONFIG_PATH = "dist/server/wrangler.json";
675
+ const SANDBOX_WRANGLER_CONFIG_PATH = `${SERVER_OUTPUT_DIR}/wrangler.json`;
496
676
  function stripRedundantNodejsCompatFlag() {
497
677
  let root = process.cwd();
498
678
  return {
@@ -529,7 +709,6 @@ function stripRedundantNodejsCompatFlag() {
529
709
  }
530
710
  const NITRO_SERVER_ENTRY = "index.mjs";
531
711
  const NITRO_WRANGLER_CONFIG = "wrangler.json";
532
- const DEFAULT_SERVER_OUTPUT_DIR = "dist/server";
533
712
  const DEFAULT_SERVER_ENTRY_NAME = "server";
534
713
  const PRERENDER_PREVIEW_HOST = "127.0.0.1";
535
714
  const TSS_PRERENDERING_ENV = "TSS_PRERENDERING";
@@ -553,7 +732,7 @@ export default {
553
732
  }
554
733
  function prerenderPreviewShim(state) {
555
734
  let root = process.cwd();
556
- let serverOutDir = DEFAULT_SERVER_OUTPUT_DIR;
735
+ let serverOutDir = SERVER_OUTPUT_DIR;
557
736
  let serverEntryName = DEFAULT_SERVER_ENTRY_NAME;
558
737
  return {
559
738
  name: "lovable-prerender-preview-shim",
@@ -584,7 +763,7 @@ function prerenderPreviewShim(state) {
584
763
  if (!await exists(nodePath.join(outDir, NITRO_SERVER_ENTRY))) return;
585
764
  const basename = nodePath.basename(serverEntryName, nodePath.extname(serverEntryName));
586
765
  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\`.`);
766
+ logWarning(`Cannot prerender: the server entry "${serverEntryName}" would need a shim at ${SERVER_OUTPUT_DIR}/index.js, which outranks nitro's own entry when publishing. Rename the entry to anything but \`index\`.`);
588
767
  return;
589
768
  }
590
769
  const target = nodePath.join(outDir, `${basename}.js`);
@@ -809,8 +988,8 @@ function defineConfig(configOrOptions = {}) {
809
988
  } }, options.tanstackStart ?? {});
810
989
  const prerenderEnabled = prerenderIsEnabled(tanstackStartOptions);
811
990
  if (command === "build" && isSandbox) {
812
- const { startOptions, skipReporter } = pinPrerenderForLovableBuild(tanstackStartOptions);
813
- internalPlugins.push(tanstackStart(startOptions), skipReporter);
991
+ const { startOptions, skipReporter, documentSet } = pinPrerenderForLovableBuild(tanstackStartOptions);
992
+ internalPlugins.push(tanstackStart(startOptions), skipReporter, documentSet);
814
993
  } else internalPlugins.push(tanstackStart(tanstackStartOptions));
815
994
  const explicitNitro = options.nitro === true || typeof options.nitro === "object" && options.nitro !== null;
816
995
  if (options.nitro !== false && command === "build") {
@@ -848,9 +1027,9 @@ function defineConfig(configOrOptions = {}) {
848
1027
  delete nitroOpts.defaultPreset;
849
1028
  nitroOpts.preset = "cloudflare-module";
850
1029
  nitroOpts.output = {
851
- dir: "dist",
852
- serverDir: "dist/server",
853
- publicDir: "dist/client"
1030
+ dir: PUBLISH_OUTPUT_DIR,
1031
+ serverDir: SERVER_OUTPUT_DIR,
1032
+ publicDir: CLIENT_OUTPUT_DIR
854
1033
  };
855
1034
  nitroOpts.cloudflare = {
856
1035
  nodeCompat: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lovable.dev/vite-tanstack-config",
3
- "version": "2.17.1",
3
+ "version": "2.18.0",
4
4
  "description": "Vite config wrapper for Lovable TanStack Start projects",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.js",