@lovable.dev/vite-tanstack-config 2.22.0 → 2.23.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/README.md +4 -0
- package/dist/hmr-gate.cjs +1 -1
- package/dist/hmr-gate.js +1 -1
- package/dist/index.cjs +318 -4
- package/dist/index.js +319 -5
- package/package.json +12 -2
package/README.md
CHANGED
|
@@ -16,3 +16,7 @@ Apps can pass through Vite options and adjust the React, TanStack Start, Nitro,
|
|
|
16
16
|
## Build exit watchdog
|
|
17
17
|
|
|
18
18
|
Inside a Lovable build with prerender enabled, the build process is forced to exit if it is still alive 60 seconds after the last build hook has returned, and a `[lovable-build-exit-report]` block naming the open handle types is written to stderr. Adjust it with `defineConfig({ buildExitWatchdog: { graceMs, enabled } })`; the grace is clamped to 5 s–10 min. The `LOVABLE_PRERENDER_EXIT_GRACE_MS` and `LOVABLE_PRERENDER_EXIT_WATCHDOG=off` environment variables override the config.
|
|
19
|
+
|
|
20
|
+
## Route manifest
|
|
21
|
+
|
|
22
|
+
Inside a Lovable build, the route surface of the app is written to `dist/.lovable/routes.json` after the build: `{ version, generator, basepath?, serverEntry, requestMiddleware?, routes }`, where `routes` is the generated route tree's `fullPaths` union in TanStack path syntax (`/posts/$postId`, `/files/$`, `/docs/{-$version}`). Lovable's serving layer uses it to answer requests no route can match without starting the app. The file is enumerated with `@tanstack/router-generator` through an in-memory overlay, so the build never rewrites `routeTree.gen.ts`, and it is skipped — with a warning naming the reason — whenever the app can answer paths outside its route tree: `tanstackStart.spa`, `router.virtualRouteConfig`, `nitro.routeRules`/`handlers`, a project `nitro.config.*` / `.config/nitro.*` / `.nitrorc` file (Nitro loads it on its own, so the wrapper cannot see what it adds), a `rewrite` in the router file, a `basepath` there that `tanstackStart.router.basepath` does not declare (a computed or shorthand one included), or a router file (`router.entry`, resolved the way TanStack Start resolves it) that cannot be read to check either.
|
package/dist/hmr-gate.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
let node_crypto = require("node:crypto");
|
|
3
2
|
let node_fs = require("node:fs");
|
|
3
|
+
let node_crypto = require("node:crypto");
|
|
4
4
|
//#region src/hmr-gate/index.ts
|
|
5
5
|
const statusCorsOriginRe = /^(?:https:\/\/(?:[^/]+\.)?lovable\.dev|https?:\/\/(?:(?:[^/:]+\.)?localhost|127\.0\.0\.1|\[::1\]))(?::\d+)?$/;
|
|
6
6
|
function requestPathname(req) {
|
package/dist/hmr-gate.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
1
|
import { readFileSync } from "node:fs";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
3
|
//#region src/hmr-gate/index.ts
|
|
4
4
|
const statusCorsOriginRe = /^(?:https:\/\/(?:[^/]+\.)?lovable\.dev|https?:\/\/(?:(?:[^/:]+\.)?localhost|127\.0\.0\.1|\[::1\]))(?::\d+)?$/;
|
|
5
5
|
function requestPathname(req) {
|
package/dist/index.cjs
CHANGED
|
@@ -21,6 +21,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
21
21
|
enumerable: true
|
|
22
22
|
}) : target, mod));
|
|
23
23
|
//#endregion
|
|
24
|
+
let node_fs = require("node:fs");
|
|
24
25
|
let node_path = require("node:path");
|
|
25
26
|
let node_url = require("node:url");
|
|
26
27
|
let vite = require("vite");
|
|
@@ -153,7 +154,7 @@ function bundledDevCssUrlShim() {
|
|
|
153
154
|
//#endregion
|
|
154
155
|
//#region package.json
|
|
155
156
|
var name = "@lovable.dev/vite-tanstack-config";
|
|
156
|
-
var version = "2.
|
|
157
|
+
var version = "2.23.1";
|
|
157
158
|
//#endregion
|
|
158
159
|
//#region src/logging.ts
|
|
159
160
|
const PACKAGE_NAME = "@lovable.dev/vite-tanstack-config";
|
|
@@ -168,7 +169,7 @@ const CLIENT_OUTPUT_DIR = `${PUBLISH_OUTPUT_DIR}/client`;
|
|
|
168
169
|
//#endregion
|
|
169
170
|
//#region src/documents.ts
|
|
170
171
|
const DOCUMENT_SET_VERSION = 1;
|
|
171
|
-
const GENERATOR = `${name}@${version}`;
|
|
172
|
+
const GENERATOR$1 = `${name}@${version}`;
|
|
172
173
|
const DOCUMENT_SET_DIR = ".lovable";
|
|
173
174
|
const DOCUMENT_SET_FILE = "output.json";
|
|
174
175
|
const DOCUMENT_SET_PLUGIN_NAME = "lovable:document-set";
|
|
@@ -341,7 +342,7 @@ function createDocumentSet(routerBasePath) {
|
|
|
341
342
|
}
|
|
342
343
|
logWarning(`Not publishing prerendered route ${entry.route}: it produced no ${entry.file}.`);
|
|
343
344
|
}
|
|
344
|
-
const payload = serializeDocumentSet(present, GENERATOR);
|
|
345
|
+
const payload = serializeDocumentSet(present, GENERATOR$1);
|
|
345
346
|
if (payload === void 0) return;
|
|
346
347
|
try {
|
|
347
348
|
await mkdir(nodePath.dirname(artifact), { recursive: true });
|
|
@@ -367,12 +368,319 @@ function createDocumentSet(routerBasePath) {
|
|
|
367
368
|
};
|
|
368
369
|
}
|
|
369
370
|
//#endregion
|
|
371
|
+
//#region src/routes.ts
|
|
372
|
+
const ROUTE_MANIFEST_VERSION = 1;
|
|
373
|
+
const GENERATOR = `${name}@${version}`;
|
|
374
|
+
const ROUTE_MANIFEST_DIR = ".lovable";
|
|
375
|
+
const ROUTE_MANIFEST_FILE = "routes.json";
|
|
376
|
+
const ROUTE_MANIFEST_MAX_ROUTES = 1e3;
|
|
377
|
+
const ROUTE_MANIFEST_MAX_BYTES = 512 * 1024;
|
|
378
|
+
const ROUTE_MANIFEST_PLUGIN_NAME = "lovable:route-manifest";
|
|
379
|
+
const ROUTER_GENERATOR_PACKAGE = "@tanstack/router-generator";
|
|
380
|
+
const ENTRY_EXTENSIONS = [
|
|
381
|
+
"",
|
|
382
|
+
".ts",
|
|
383
|
+
".js",
|
|
384
|
+
".mts",
|
|
385
|
+
".mjs",
|
|
386
|
+
".tsx",
|
|
387
|
+
".jsx"
|
|
388
|
+
];
|
|
389
|
+
const NITRO_CONFIG_EXTENSIONS = [
|
|
390
|
+
".js",
|
|
391
|
+
".ts",
|
|
392
|
+
".mjs",
|
|
393
|
+
".cjs",
|
|
394
|
+
".mts",
|
|
395
|
+
".cts",
|
|
396
|
+
".json",
|
|
397
|
+
".jsonc",
|
|
398
|
+
".json5",
|
|
399
|
+
".yaml",
|
|
400
|
+
".yml",
|
|
401
|
+
".toml"
|
|
402
|
+
];
|
|
403
|
+
const NITRO_CONFIG_FILES = [
|
|
404
|
+
...NITRO_CONFIG_EXTENSIONS.map((extension) => `nitro.config${extension}`),
|
|
405
|
+
...NITRO_CONFIG_EXTENSIONS.flatMap((extension) => [`.config/nitro${extension}`, `.config/nitro.config${extension}`]),
|
|
406
|
+
".nitrorc"
|
|
407
|
+
];
|
|
408
|
+
function subOptions(options, key) {
|
|
409
|
+
const value = options[key];
|
|
410
|
+
return typeof value === "object" && value !== null ? value : {};
|
|
411
|
+
}
|
|
412
|
+
function stringOption(options, key, fallback) {
|
|
413
|
+
const value = options[key];
|
|
414
|
+
return typeof value === "string" ? value : fallback;
|
|
415
|
+
}
|
|
416
|
+
/** Why no manifest is emitted: the app can answer paths outside its route tree. */
|
|
417
|
+
function routeManifestOptOut(inputs) {
|
|
418
|
+
const { tanstackStart } = inputs;
|
|
419
|
+
if (tanstackStart.spa != null && subOptions(tanstackStart, "spa").enabled !== false) return "tanstackStart.spa serves the shell for every path";
|
|
420
|
+
if (subOptions(tanstackStart, "router").virtualRouteConfig !== void 0) return "tanstackStart.router.virtualRouteConfig is set";
|
|
421
|
+
if (typeof inputs.nitro === "object" && inputs.nitro !== null) {
|
|
422
|
+
for (const key of [
|
|
423
|
+
"routeRules",
|
|
424
|
+
"handlers",
|
|
425
|
+
"devHandlers"
|
|
426
|
+
]) if (key in inputs.nitro) return `nitro.${key} is set`;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
function routeFullPaths(routeNodes, inferFullPath) {
|
|
430
|
+
const paths = /* @__PURE__ */ new Set();
|
|
431
|
+
for (const node of routeNodes) {
|
|
432
|
+
if (node.routePath === void 0 || node.isNonPath) continue;
|
|
433
|
+
paths.add(inferFullPath(node));
|
|
434
|
+
}
|
|
435
|
+
return [...paths].sort();
|
|
436
|
+
}
|
|
437
|
+
const COMMENTS = String.raw`(?:(?://[^\n]*\n|/\*[\s\S]*?\*/)\s*)*`;
|
|
438
|
+
const BASEPATH_PROPERTY = new RegExp(String.raw`(?<=[{,]\s*${COMMENTS})\bbasepath\b\s*(?::\s*(?:(['"\x60])([^'"\x60]*)\1)?|(?=${COMMENTS}[,}]))`, "g");
|
|
439
|
+
/**
|
|
440
|
+
* Whether an object-literal `basepath` property in the router file names anything but
|
|
441
|
+
* the declared basepath; a computed value or a shorthand cannot be checked and disagrees.
|
|
442
|
+
*/
|
|
443
|
+
function routerBasepathDisagrees(routerSource, declared) {
|
|
444
|
+
for (const property of routerSource.matchAll(BASEPATH_PROPERTY)) {
|
|
445
|
+
const literal = property[2];
|
|
446
|
+
if (literal === void 0 || normalizeBasePath(literal) !== declared) return true;
|
|
447
|
+
}
|
|
448
|
+
return false;
|
|
449
|
+
}
|
|
450
|
+
/** The artifact bytes, or undefined when the manifest would exceed the worker's caps. */
|
|
451
|
+
function serializeRouteManifest(manifest) {
|
|
452
|
+
if (manifest.routes.length > 1e3) {
|
|
453
|
+
logWarning(`Not publishing the route manifest: ${manifest.routes.length} routes exceed the ${ROUTE_MANIFEST_MAX_ROUTES}-route limit.`);
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
const payload = JSON.stringify(manifest);
|
|
457
|
+
const bytes = Buffer.byteLength(payload, "utf8");
|
|
458
|
+
if (bytes > 524288) {
|
|
459
|
+
logWarning(`Not publishing the route manifest: ${bytes} bytes exceed the ${ROUTE_MANIFEST_MAX_BYTES}-byte limit.`);
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
return payload;
|
|
463
|
+
}
|
|
464
|
+
function createOverlayFs(fsp) {
|
|
465
|
+
const overlay = /* @__PURE__ */ new Map();
|
|
466
|
+
const memStat = () => ({
|
|
467
|
+
mtimeMs: BigInt(Date.now()) * 1000000n,
|
|
468
|
+
mode: 420,
|
|
469
|
+
uid: 0,
|
|
470
|
+
gid: 0
|
|
471
|
+
});
|
|
472
|
+
return {
|
|
473
|
+
async stat(filePath) {
|
|
474
|
+
if (overlay.has(filePath)) return memStat();
|
|
475
|
+
const stat = await fsp.stat(filePath, { bigint: true });
|
|
476
|
+
return {
|
|
477
|
+
mtimeMs: stat.mtimeMs,
|
|
478
|
+
mode: Number(stat.mode),
|
|
479
|
+
uid: Number(stat.uid),
|
|
480
|
+
gid: Number(stat.gid)
|
|
481
|
+
};
|
|
482
|
+
},
|
|
483
|
+
async readFile(filePath) {
|
|
484
|
+
const overlaid = overlay.get(filePath);
|
|
485
|
+
if (overlaid !== void 0) return {
|
|
486
|
+
stat: memStat(),
|
|
487
|
+
fileContent: overlaid
|
|
488
|
+
};
|
|
489
|
+
try {
|
|
490
|
+
const [stat, fileContent] = await Promise.all([fsp.stat(filePath, { bigint: true }), fsp.readFile(filePath, "utf8")]);
|
|
491
|
+
return {
|
|
492
|
+
stat: { mtimeMs: stat.mtimeMs },
|
|
493
|
+
fileContent
|
|
494
|
+
};
|
|
495
|
+
} catch (error) {
|
|
496
|
+
if (error.code === "ENOENT") return "file-not-existing";
|
|
497
|
+
throw error;
|
|
498
|
+
}
|
|
499
|
+
},
|
|
500
|
+
async writeFile(filePath, content) {
|
|
501
|
+
overlay.set(filePath, content);
|
|
502
|
+
},
|
|
503
|
+
async rename(oldPath, newPath) {
|
|
504
|
+
const content = overlay.get(oldPath);
|
|
505
|
+
if (content === void 0) return;
|
|
506
|
+
overlay.set(newPath, content);
|
|
507
|
+
overlay.delete(oldPath);
|
|
508
|
+
},
|
|
509
|
+
async chmod() {},
|
|
510
|
+
async chown() {}
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
async function loadRouterGenerator(root) {
|
|
514
|
+
const { createRequire } = await import("node:module");
|
|
515
|
+
const { pathToFileURL } = await import("node:url");
|
|
516
|
+
const nodePath = await import("node:path");
|
|
517
|
+
const { readFile } = await import("node:fs/promises");
|
|
518
|
+
let lastError;
|
|
519
|
+
for (const base of [nodePath.join(root, "package.json"), require("url").pathToFileURL(__filename).href]) try {
|
|
520
|
+
const packageJsonPath = createRequire(base).resolve(`${ROUTER_GENERATOR_PACKAGE}/package.json`);
|
|
521
|
+
const packageJson = JSON.parse(await readFile(packageJsonPath, "utf8"));
|
|
522
|
+
const entry = packageJson.exports?.["."]?.import?.default ?? packageJson.module ?? packageJson.main;
|
|
523
|
+
if (entry === void 0) throw new Error(`${ROUTER_GENERATOR_PACKAGE} declares no entry point`);
|
|
524
|
+
return await import(pathToFileURL(nodePath.join(nodePath.dirname(packageJsonPath), entry)).href);
|
|
525
|
+
} catch (error) {
|
|
526
|
+
lastError = error;
|
|
527
|
+
}
|
|
528
|
+
throw lastError;
|
|
529
|
+
}
|
|
530
|
+
/** The first of `names` (relative to `dir`) that exists, as given. */
|
|
531
|
+
async function findExisting(dir, names) {
|
|
532
|
+
const { access } = await import("node:fs/promises");
|
|
533
|
+
const nodePath = await import("node:path");
|
|
534
|
+
for (const name of names) if (await access(nodePath.join(dir, name)).then(() => true, () => false)) return name;
|
|
535
|
+
}
|
|
536
|
+
async function resolveEntryFile(srcDirectory, entry) {
|
|
537
|
+
const { stat } = await import("node:fs/promises");
|
|
538
|
+
const base = (await import("node:path")).resolve(srcDirectory, entry.startsWith("./") ? entry : `./${entry}`);
|
|
539
|
+
for (const extension of ENTRY_EXTENSIONS) {
|
|
540
|
+
const file = `${base}${extension}`;
|
|
541
|
+
if (await stat(file).then((stats) => stats.isFile(), () => false)) return file;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
async function readSource(file) {
|
|
545
|
+
if (file === void 0) return void 0;
|
|
546
|
+
const { readFile } = await import("node:fs/promises");
|
|
547
|
+
return readFile(file, "utf8").catch(() => void 0);
|
|
548
|
+
}
|
|
549
|
+
async function enumerateRoutes(options) {
|
|
550
|
+
const fsp = await import("node:fs/promises");
|
|
551
|
+
const nodePath = await import("node:path");
|
|
552
|
+
const os = await import("node:os");
|
|
553
|
+
const generator = await loadRouterGenerator(options.root);
|
|
554
|
+
const { root, srcDirectory } = options;
|
|
555
|
+
const routesDirectory = nodePath.resolve(root, srcDirectory, stringOption(options.router, "routesDirectory", "routes"));
|
|
556
|
+
const generatedRouteTree = nodePath.resolve(root, srcDirectory, stringOption(options.router, "generatedRouteTree", "routeTree.gen.ts"));
|
|
557
|
+
const tmpDir = await fsp.mkdtemp(nodePath.join(os.tmpdir(), "lovable-route-manifest-"));
|
|
558
|
+
try {
|
|
559
|
+
const config = generator.getConfig({
|
|
560
|
+
...options.router,
|
|
561
|
+
routesDirectory,
|
|
562
|
+
generatedRouteTree,
|
|
563
|
+
tmpDir,
|
|
564
|
+
disableLogging: true,
|
|
565
|
+
enableRouteTreeFormatting: false
|
|
566
|
+
}, root);
|
|
567
|
+
const crawler = new generator.Generator({
|
|
568
|
+
config,
|
|
569
|
+
root,
|
|
570
|
+
fs: createOverlayFs(fsp)
|
|
571
|
+
});
|
|
572
|
+
await crawler.run();
|
|
573
|
+
const crawl = await crawler.getCrawlingResult();
|
|
574
|
+
if (crawl === void 0) throw new Error("the route generator produced no crawling result");
|
|
575
|
+
return routeFullPaths(crawl.acc.routeNodes, generator.inferFullPath);
|
|
576
|
+
} finally {
|
|
577
|
+
await fsp.rm(tmpDir, {
|
|
578
|
+
recursive: true,
|
|
579
|
+
force: true
|
|
580
|
+
}).catch(() => {});
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
function createRouteManifestPlugin(inputs) {
|
|
584
|
+
let root = process.cwd();
|
|
585
|
+
return {
|
|
586
|
+
name: ROUTE_MANIFEST_PLUGIN_NAME,
|
|
587
|
+
apply: "build",
|
|
588
|
+
enforce: "post",
|
|
589
|
+
configResolved(config) {
|
|
590
|
+
root = config.root;
|
|
591
|
+
},
|
|
592
|
+
buildApp: {
|
|
593
|
+
order: "post",
|
|
594
|
+
async handler() {
|
|
595
|
+
const { mkdir, rm, writeFile } = await import("node:fs/promises");
|
|
596
|
+
const nodePath = await import("node:path");
|
|
597
|
+
const artifact = nodePath.join(root, PUBLISH_OUTPUT_DIR, ROUTE_MANIFEST_DIR, ROUTE_MANIFEST_FILE);
|
|
598
|
+
await rm(artifact, { force: true }).catch((error) => {
|
|
599
|
+
logWarning(`Could not remove a previous build's route manifest: ${String(error)}`);
|
|
600
|
+
});
|
|
601
|
+
const optOut = routeManifestOptOut(inputs);
|
|
602
|
+
if (optOut !== void 0) {
|
|
603
|
+
logWarning(`Not publishing a route manifest: ${optOut}.`);
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
const nitroConfig = await findExisting(root, NITRO_CONFIG_FILES);
|
|
607
|
+
if (nitroConfig !== void 0) {
|
|
608
|
+
logWarning(`Not publishing a route manifest: nitro loads ${nitroConfig} on its own, and it may add routes.`);
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
const { tanstackStart } = inputs;
|
|
612
|
+
const srcDirectory = stringOption(tanstackStart, "srcDirectory", "src");
|
|
613
|
+
const router = subOptions(tanstackStart, "router");
|
|
614
|
+
const sourceDir = nodePath.resolve(root, srcDirectory);
|
|
615
|
+
const routerEntry = stringOption(router, "entry", "router");
|
|
616
|
+
const routerSource = await readSource(await resolveEntryFile(sourceDir, routerEntry));
|
|
617
|
+
if (routerSource === void 0) {
|
|
618
|
+
logWarning(`Not publishing a route manifest: could not read the router file (${routerEntry} under ${srcDirectory}) to check its settings.`);
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
if (/\brewrite\s*:/.test(routerSource)) {
|
|
622
|
+
logWarning("Not publishing a route manifest: the router file sets `rewrite`.");
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
const basepath = normalizeBasePath(router.basepath);
|
|
626
|
+
if (routerBasepathDisagrees(routerSource, basepath)) {
|
|
627
|
+
logWarning("Not publishing a route manifest: the router file sets a `basepath` that tanstackStart.router.basepath does not declare.");
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
let routes;
|
|
631
|
+
try {
|
|
632
|
+
routes = await enumerateRoutes({
|
|
633
|
+
root,
|
|
634
|
+
srcDirectory,
|
|
635
|
+
router
|
|
636
|
+
});
|
|
637
|
+
} catch (error) {
|
|
638
|
+
logWarning(`Not publishing a route manifest; could not enumerate the route tree: ${String(error)}`);
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
if (routes.length === 0) {
|
|
642
|
+
logWarning("Not publishing a route manifest: the route tree has no routes.");
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
const startSource = await readSource(await resolveEntryFile(sourceDir, stringOption(subOptions(tanstackStart, "start"), "entry", "start")));
|
|
646
|
+
const payload = serializeRouteManifest({
|
|
647
|
+
version: ROUTE_MANIFEST_VERSION,
|
|
648
|
+
generator: GENERATOR,
|
|
649
|
+
...basepath === "" ? {} : { basepath },
|
|
650
|
+
serverEntry: typeof subOptions(tanstackStart, "server").entry === "string" ? "custom" : "default",
|
|
651
|
+
...startSource === void 0 ? {} : { requestMiddleware: /\brequestMiddleware\b/.test(startSource) },
|
|
652
|
+
routes
|
|
653
|
+
});
|
|
654
|
+
if (payload === void 0) return;
|
|
655
|
+
try {
|
|
656
|
+
await mkdir(nodePath.dirname(artifact), { recursive: true });
|
|
657
|
+
await writeFile(artifact, payload);
|
|
658
|
+
} catch (error) {
|
|
659
|
+
logWarning(`Could not write the route manifest; unknown paths fall back to SSR: ${String(error)}`);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
//#endregion
|
|
370
666
|
//#region src/index.ts
|
|
371
667
|
const SANDBOX_ENV_VAR = "DEV_SERVER__PROJECT_PATH";
|
|
372
668
|
const LOVABLE_SANDBOX_ENV_VAR = "LOVABLE_SANDBOX";
|
|
373
669
|
function isSandboxEnvironment() {
|
|
374
670
|
return process.env[LOVABLE_SANDBOX_ENV_VAR] === "1" || !!process.env[SANDBOX_ENV_VAR];
|
|
375
671
|
}
|
|
672
|
+
function sandboxFsAllow(root) {
|
|
673
|
+
let nodeModules;
|
|
674
|
+
let realRoot;
|
|
675
|
+
try {
|
|
676
|
+
nodeModules = node_fs.realpathSync.native((0, node_path.join)(root, "node_modules"));
|
|
677
|
+
realRoot = node_fs.realpathSync.native(root);
|
|
678
|
+
} catch {
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
if (nodeModules.startsWith(realRoot + node_path.sep)) return void 0;
|
|
682
|
+
return [(0, vite.searchForWorkspaceRoot)(root), nodeModules];
|
|
683
|
+
}
|
|
376
684
|
const TOOL_MANAGED_WATCH_IGNORES = [
|
|
377
685
|
"**/.workspace/**",
|
|
378
686
|
"**/.agents/**",
|
|
@@ -1222,6 +1530,10 @@ function defineConfig(configOrOptions = {}) {
|
|
|
1222
1530
|
if (command === "build" && isSandbox) {
|
|
1223
1531
|
const { startOptions, skipReporter, documentSet } = pinPrerenderForLovableBuild(tanstackStartOptions);
|
|
1224
1532
|
internalPlugins.push(tanstackStart(startOptions), skipReporter, documentSet);
|
|
1533
|
+
internalPlugins.push(createRouteManifestPlugin({
|
|
1534
|
+
tanstackStart: tanstackStartOptions,
|
|
1535
|
+
nitro: options.nitro
|
|
1536
|
+
}));
|
|
1225
1537
|
} else internalPlugins.push(tanstackStart(tanstackStartOptions));
|
|
1226
1538
|
const explicitNitro = options.nitro === true || typeof options.nitro === "object" && options.nitro !== null;
|
|
1227
1539
|
if (options.nitro !== false && command === "build") {
|
|
@@ -1344,12 +1656,14 @@ function defineConfig(configOrOptions = {}) {
|
|
|
1344
1656
|
}
|
|
1345
1657
|
if (isSandbox) {
|
|
1346
1658
|
validateConfig(config);
|
|
1659
|
+
const fsAllow = sandboxFsAllow(process.cwd());
|
|
1347
1660
|
config = cleanServerConfig((0, vite.mergeConfig)(config, { server: {
|
|
1348
1661
|
host: "::",
|
|
1349
1662
|
port: 8080,
|
|
1350
1663
|
strictPort: true,
|
|
1351
1664
|
watch: { ignored: [...TOOL_MANAGED_WATCH_IGNORES] },
|
|
1352
|
-
hmr: { overlay: false }
|
|
1665
|
+
hmr: { overlay: false },
|
|
1666
|
+
...fsAllow ? { fs: { allow: fsAllow } } : {}
|
|
1353
1667
|
} }));
|
|
1354
1668
|
} else config = (0, vite.mergeConfig)({ server: {
|
|
1355
1669
|
host: "::",
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { realpathSync } from "node:fs";
|
|
1
2
|
import { basename, extname, join, relative, resolve, sep } from "node:path";
|
|
2
3
|
import { fileURLToPath } from "node:url";
|
|
3
|
-
import { loadEnv, mergeConfig } from "vite";
|
|
4
|
+
import { loadEnv, mergeConfig, searchForWorkspaceRoot } from "vite";
|
|
4
5
|
import https from "node:https";
|
|
5
6
|
//#region src/assetsProxyPlugin.ts
|
|
6
7
|
const ASSET_RE = /^\/__l5e\/assets-v1\//;
|
|
@@ -129,7 +130,7 @@ function bundledDevCssUrlShim() {
|
|
|
129
130
|
//#endregion
|
|
130
131
|
//#region package.json
|
|
131
132
|
var name = "@lovable.dev/vite-tanstack-config";
|
|
132
|
-
var version = "2.
|
|
133
|
+
var version = "2.23.1";
|
|
133
134
|
//#endregion
|
|
134
135
|
//#region src/logging.ts
|
|
135
136
|
const PACKAGE_NAME = "@lovable.dev/vite-tanstack-config";
|
|
@@ -144,7 +145,7 @@ const CLIENT_OUTPUT_DIR = `${PUBLISH_OUTPUT_DIR}/client`;
|
|
|
144
145
|
//#endregion
|
|
145
146
|
//#region src/documents.ts
|
|
146
147
|
const DOCUMENT_SET_VERSION = 1;
|
|
147
|
-
const GENERATOR = `${name}@${version}`;
|
|
148
|
+
const GENERATOR$1 = `${name}@${version}`;
|
|
148
149
|
const DOCUMENT_SET_DIR = ".lovable";
|
|
149
150
|
const DOCUMENT_SET_FILE = "output.json";
|
|
150
151
|
const DOCUMENT_SET_PLUGIN_NAME = "lovable:document-set";
|
|
@@ -317,7 +318,7 @@ function createDocumentSet(routerBasePath) {
|
|
|
317
318
|
}
|
|
318
319
|
logWarning(`Not publishing prerendered route ${entry.route}: it produced no ${entry.file}.`);
|
|
319
320
|
}
|
|
320
|
-
const payload = serializeDocumentSet(present, GENERATOR);
|
|
321
|
+
const payload = serializeDocumentSet(present, GENERATOR$1);
|
|
321
322
|
if (payload === void 0) return;
|
|
322
323
|
try {
|
|
323
324
|
await mkdir(nodePath.dirname(artifact), { recursive: true });
|
|
@@ -343,12 +344,319 @@ function createDocumentSet(routerBasePath) {
|
|
|
343
344
|
};
|
|
344
345
|
}
|
|
345
346
|
//#endregion
|
|
347
|
+
//#region src/routes.ts
|
|
348
|
+
const ROUTE_MANIFEST_VERSION = 1;
|
|
349
|
+
const GENERATOR = `${name}@${version}`;
|
|
350
|
+
const ROUTE_MANIFEST_DIR = ".lovable";
|
|
351
|
+
const ROUTE_MANIFEST_FILE = "routes.json";
|
|
352
|
+
const ROUTE_MANIFEST_MAX_ROUTES = 1e3;
|
|
353
|
+
const ROUTE_MANIFEST_MAX_BYTES = 512 * 1024;
|
|
354
|
+
const ROUTE_MANIFEST_PLUGIN_NAME = "lovable:route-manifest";
|
|
355
|
+
const ROUTER_GENERATOR_PACKAGE = "@tanstack/router-generator";
|
|
356
|
+
const ENTRY_EXTENSIONS = [
|
|
357
|
+
"",
|
|
358
|
+
".ts",
|
|
359
|
+
".js",
|
|
360
|
+
".mts",
|
|
361
|
+
".mjs",
|
|
362
|
+
".tsx",
|
|
363
|
+
".jsx"
|
|
364
|
+
];
|
|
365
|
+
const NITRO_CONFIG_EXTENSIONS = [
|
|
366
|
+
".js",
|
|
367
|
+
".ts",
|
|
368
|
+
".mjs",
|
|
369
|
+
".cjs",
|
|
370
|
+
".mts",
|
|
371
|
+
".cts",
|
|
372
|
+
".json",
|
|
373
|
+
".jsonc",
|
|
374
|
+
".json5",
|
|
375
|
+
".yaml",
|
|
376
|
+
".yml",
|
|
377
|
+
".toml"
|
|
378
|
+
];
|
|
379
|
+
const NITRO_CONFIG_FILES = [
|
|
380
|
+
...NITRO_CONFIG_EXTENSIONS.map((extension) => `nitro.config${extension}`),
|
|
381
|
+
...NITRO_CONFIG_EXTENSIONS.flatMap((extension) => [`.config/nitro${extension}`, `.config/nitro.config${extension}`]),
|
|
382
|
+
".nitrorc"
|
|
383
|
+
];
|
|
384
|
+
function subOptions(options, key) {
|
|
385
|
+
const value = options[key];
|
|
386
|
+
return typeof value === "object" && value !== null ? value : {};
|
|
387
|
+
}
|
|
388
|
+
function stringOption(options, key, fallback) {
|
|
389
|
+
const value = options[key];
|
|
390
|
+
return typeof value === "string" ? value : fallback;
|
|
391
|
+
}
|
|
392
|
+
/** Why no manifest is emitted: the app can answer paths outside its route tree. */
|
|
393
|
+
function routeManifestOptOut(inputs) {
|
|
394
|
+
const { tanstackStart } = inputs;
|
|
395
|
+
if (tanstackStart.spa != null && subOptions(tanstackStart, "spa").enabled !== false) return "tanstackStart.spa serves the shell for every path";
|
|
396
|
+
if (subOptions(tanstackStart, "router").virtualRouteConfig !== void 0) return "tanstackStart.router.virtualRouteConfig is set";
|
|
397
|
+
if (typeof inputs.nitro === "object" && inputs.nitro !== null) {
|
|
398
|
+
for (const key of [
|
|
399
|
+
"routeRules",
|
|
400
|
+
"handlers",
|
|
401
|
+
"devHandlers"
|
|
402
|
+
]) if (key in inputs.nitro) return `nitro.${key} is set`;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
function routeFullPaths(routeNodes, inferFullPath) {
|
|
406
|
+
const paths = /* @__PURE__ */ new Set();
|
|
407
|
+
for (const node of routeNodes) {
|
|
408
|
+
if (node.routePath === void 0 || node.isNonPath) continue;
|
|
409
|
+
paths.add(inferFullPath(node));
|
|
410
|
+
}
|
|
411
|
+
return [...paths].sort();
|
|
412
|
+
}
|
|
413
|
+
const COMMENTS = String.raw`(?:(?://[^\n]*\n|/\*[\s\S]*?\*/)\s*)*`;
|
|
414
|
+
const BASEPATH_PROPERTY = new RegExp(String.raw`(?<=[{,]\s*${COMMENTS})\bbasepath\b\s*(?::\s*(?:(['"\x60])([^'"\x60]*)\1)?|(?=${COMMENTS}[,}]))`, "g");
|
|
415
|
+
/**
|
|
416
|
+
* Whether an object-literal `basepath` property in the router file names anything but
|
|
417
|
+
* the declared basepath; a computed value or a shorthand cannot be checked and disagrees.
|
|
418
|
+
*/
|
|
419
|
+
function routerBasepathDisagrees(routerSource, declared) {
|
|
420
|
+
for (const property of routerSource.matchAll(BASEPATH_PROPERTY)) {
|
|
421
|
+
const literal = property[2];
|
|
422
|
+
if (literal === void 0 || normalizeBasePath(literal) !== declared) return true;
|
|
423
|
+
}
|
|
424
|
+
return false;
|
|
425
|
+
}
|
|
426
|
+
/** The artifact bytes, or undefined when the manifest would exceed the worker's caps. */
|
|
427
|
+
function serializeRouteManifest(manifest) {
|
|
428
|
+
if (manifest.routes.length > 1e3) {
|
|
429
|
+
logWarning(`Not publishing the route manifest: ${manifest.routes.length} routes exceed the ${ROUTE_MANIFEST_MAX_ROUTES}-route limit.`);
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
const payload = JSON.stringify(manifest);
|
|
433
|
+
const bytes = Buffer.byteLength(payload, "utf8");
|
|
434
|
+
if (bytes > 524288) {
|
|
435
|
+
logWarning(`Not publishing the route manifest: ${bytes} bytes exceed the ${ROUTE_MANIFEST_MAX_BYTES}-byte limit.`);
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
return payload;
|
|
439
|
+
}
|
|
440
|
+
function createOverlayFs(fsp) {
|
|
441
|
+
const overlay = /* @__PURE__ */ new Map();
|
|
442
|
+
const memStat = () => ({
|
|
443
|
+
mtimeMs: BigInt(Date.now()) * 1000000n,
|
|
444
|
+
mode: 420,
|
|
445
|
+
uid: 0,
|
|
446
|
+
gid: 0
|
|
447
|
+
});
|
|
448
|
+
return {
|
|
449
|
+
async stat(filePath) {
|
|
450
|
+
if (overlay.has(filePath)) return memStat();
|
|
451
|
+
const stat = await fsp.stat(filePath, { bigint: true });
|
|
452
|
+
return {
|
|
453
|
+
mtimeMs: stat.mtimeMs,
|
|
454
|
+
mode: Number(stat.mode),
|
|
455
|
+
uid: Number(stat.uid),
|
|
456
|
+
gid: Number(stat.gid)
|
|
457
|
+
};
|
|
458
|
+
},
|
|
459
|
+
async readFile(filePath) {
|
|
460
|
+
const overlaid = overlay.get(filePath);
|
|
461
|
+
if (overlaid !== void 0) return {
|
|
462
|
+
stat: memStat(),
|
|
463
|
+
fileContent: overlaid
|
|
464
|
+
};
|
|
465
|
+
try {
|
|
466
|
+
const [stat, fileContent] = await Promise.all([fsp.stat(filePath, { bigint: true }), fsp.readFile(filePath, "utf8")]);
|
|
467
|
+
return {
|
|
468
|
+
stat: { mtimeMs: stat.mtimeMs },
|
|
469
|
+
fileContent
|
|
470
|
+
};
|
|
471
|
+
} catch (error) {
|
|
472
|
+
if (error.code === "ENOENT") return "file-not-existing";
|
|
473
|
+
throw error;
|
|
474
|
+
}
|
|
475
|
+
},
|
|
476
|
+
async writeFile(filePath, content) {
|
|
477
|
+
overlay.set(filePath, content);
|
|
478
|
+
},
|
|
479
|
+
async rename(oldPath, newPath) {
|
|
480
|
+
const content = overlay.get(oldPath);
|
|
481
|
+
if (content === void 0) return;
|
|
482
|
+
overlay.set(newPath, content);
|
|
483
|
+
overlay.delete(oldPath);
|
|
484
|
+
},
|
|
485
|
+
async chmod() {},
|
|
486
|
+
async chown() {}
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
async function loadRouterGenerator(root) {
|
|
490
|
+
const { createRequire } = await import("node:module");
|
|
491
|
+
const { pathToFileURL } = await import("node:url");
|
|
492
|
+
const nodePath = await import("node:path");
|
|
493
|
+
const { readFile } = await import("node:fs/promises");
|
|
494
|
+
let lastError;
|
|
495
|
+
for (const base of [nodePath.join(root, "package.json"), import.meta.url]) try {
|
|
496
|
+
const packageJsonPath = createRequire(base).resolve(`${ROUTER_GENERATOR_PACKAGE}/package.json`);
|
|
497
|
+
const packageJson = JSON.parse(await readFile(packageJsonPath, "utf8"));
|
|
498
|
+
const entry = packageJson.exports?.["."]?.import?.default ?? packageJson.module ?? packageJson.main;
|
|
499
|
+
if (entry === void 0) throw new Error(`${ROUTER_GENERATOR_PACKAGE} declares no entry point`);
|
|
500
|
+
return await import(pathToFileURL(nodePath.join(nodePath.dirname(packageJsonPath), entry)).href);
|
|
501
|
+
} catch (error) {
|
|
502
|
+
lastError = error;
|
|
503
|
+
}
|
|
504
|
+
throw lastError;
|
|
505
|
+
}
|
|
506
|
+
/** The first of `names` (relative to `dir`) that exists, as given. */
|
|
507
|
+
async function findExisting(dir, names) {
|
|
508
|
+
const { access } = await import("node:fs/promises");
|
|
509
|
+
const nodePath = await import("node:path");
|
|
510
|
+
for (const name of names) if (await access(nodePath.join(dir, name)).then(() => true, () => false)) return name;
|
|
511
|
+
}
|
|
512
|
+
async function resolveEntryFile(srcDirectory, entry) {
|
|
513
|
+
const { stat } = await import("node:fs/promises");
|
|
514
|
+
const base = (await import("node:path")).resolve(srcDirectory, entry.startsWith("./") ? entry : `./${entry}`);
|
|
515
|
+
for (const extension of ENTRY_EXTENSIONS) {
|
|
516
|
+
const file = `${base}${extension}`;
|
|
517
|
+
if (await stat(file).then((stats) => stats.isFile(), () => false)) return file;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
async function readSource(file) {
|
|
521
|
+
if (file === void 0) return void 0;
|
|
522
|
+
const { readFile } = await import("node:fs/promises");
|
|
523
|
+
return readFile(file, "utf8").catch(() => void 0);
|
|
524
|
+
}
|
|
525
|
+
async function enumerateRoutes(options) {
|
|
526
|
+
const fsp = await import("node:fs/promises");
|
|
527
|
+
const nodePath = await import("node:path");
|
|
528
|
+
const os = await import("node:os");
|
|
529
|
+
const generator = await loadRouterGenerator(options.root);
|
|
530
|
+
const { root, srcDirectory } = options;
|
|
531
|
+
const routesDirectory = nodePath.resolve(root, srcDirectory, stringOption(options.router, "routesDirectory", "routes"));
|
|
532
|
+
const generatedRouteTree = nodePath.resolve(root, srcDirectory, stringOption(options.router, "generatedRouteTree", "routeTree.gen.ts"));
|
|
533
|
+
const tmpDir = await fsp.mkdtemp(nodePath.join(os.tmpdir(), "lovable-route-manifest-"));
|
|
534
|
+
try {
|
|
535
|
+
const config = generator.getConfig({
|
|
536
|
+
...options.router,
|
|
537
|
+
routesDirectory,
|
|
538
|
+
generatedRouteTree,
|
|
539
|
+
tmpDir,
|
|
540
|
+
disableLogging: true,
|
|
541
|
+
enableRouteTreeFormatting: false
|
|
542
|
+
}, root);
|
|
543
|
+
const crawler = new generator.Generator({
|
|
544
|
+
config,
|
|
545
|
+
root,
|
|
546
|
+
fs: createOverlayFs(fsp)
|
|
547
|
+
});
|
|
548
|
+
await crawler.run();
|
|
549
|
+
const crawl = await crawler.getCrawlingResult();
|
|
550
|
+
if (crawl === void 0) throw new Error("the route generator produced no crawling result");
|
|
551
|
+
return routeFullPaths(crawl.acc.routeNodes, generator.inferFullPath);
|
|
552
|
+
} finally {
|
|
553
|
+
await fsp.rm(tmpDir, {
|
|
554
|
+
recursive: true,
|
|
555
|
+
force: true
|
|
556
|
+
}).catch(() => {});
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
function createRouteManifestPlugin(inputs) {
|
|
560
|
+
let root = process.cwd();
|
|
561
|
+
return {
|
|
562
|
+
name: ROUTE_MANIFEST_PLUGIN_NAME,
|
|
563
|
+
apply: "build",
|
|
564
|
+
enforce: "post",
|
|
565
|
+
configResolved(config) {
|
|
566
|
+
root = config.root;
|
|
567
|
+
},
|
|
568
|
+
buildApp: {
|
|
569
|
+
order: "post",
|
|
570
|
+
async handler() {
|
|
571
|
+
const { mkdir, rm, writeFile } = await import("node:fs/promises");
|
|
572
|
+
const nodePath = await import("node:path");
|
|
573
|
+
const artifact = nodePath.join(root, PUBLISH_OUTPUT_DIR, ROUTE_MANIFEST_DIR, ROUTE_MANIFEST_FILE);
|
|
574
|
+
await rm(artifact, { force: true }).catch((error) => {
|
|
575
|
+
logWarning(`Could not remove a previous build's route manifest: ${String(error)}`);
|
|
576
|
+
});
|
|
577
|
+
const optOut = routeManifestOptOut(inputs);
|
|
578
|
+
if (optOut !== void 0) {
|
|
579
|
+
logWarning(`Not publishing a route manifest: ${optOut}.`);
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
const nitroConfig = await findExisting(root, NITRO_CONFIG_FILES);
|
|
583
|
+
if (nitroConfig !== void 0) {
|
|
584
|
+
logWarning(`Not publishing a route manifest: nitro loads ${nitroConfig} on its own, and it may add routes.`);
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
const { tanstackStart } = inputs;
|
|
588
|
+
const srcDirectory = stringOption(tanstackStart, "srcDirectory", "src");
|
|
589
|
+
const router = subOptions(tanstackStart, "router");
|
|
590
|
+
const sourceDir = nodePath.resolve(root, srcDirectory);
|
|
591
|
+
const routerEntry = stringOption(router, "entry", "router");
|
|
592
|
+
const routerSource = await readSource(await resolveEntryFile(sourceDir, routerEntry));
|
|
593
|
+
if (routerSource === void 0) {
|
|
594
|
+
logWarning(`Not publishing a route manifest: could not read the router file (${routerEntry} under ${srcDirectory}) to check its settings.`);
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
if (/\brewrite\s*:/.test(routerSource)) {
|
|
598
|
+
logWarning("Not publishing a route manifest: the router file sets `rewrite`.");
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
const basepath = normalizeBasePath(router.basepath);
|
|
602
|
+
if (routerBasepathDisagrees(routerSource, basepath)) {
|
|
603
|
+
logWarning("Not publishing a route manifest: the router file sets a `basepath` that tanstackStart.router.basepath does not declare.");
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
let routes;
|
|
607
|
+
try {
|
|
608
|
+
routes = await enumerateRoutes({
|
|
609
|
+
root,
|
|
610
|
+
srcDirectory,
|
|
611
|
+
router
|
|
612
|
+
});
|
|
613
|
+
} catch (error) {
|
|
614
|
+
logWarning(`Not publishing a route manifest; could not enumerate the route tree: ${String(error)}`);
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
if (routes.length === 0) {
|
|
618
|
+
logWarning("Not publishing a route manifest: the route tree has no routes.");
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
const startSource = await readSource(await resolveEntryFile(sourceDir, stringOption(subOptions(tanstackStart, "start"), "entry", "start")));
|
|
622
|
+
const payload = serializeRouteManifest({
|
|
623
|
+
version: ROUTE_MANIFEST_VERSION,
|
|
624
|
+
generator: GENERATOR,
|
|
625
|
+
...basepath === "" ? {} : { basepath },
|
|
626
|
+
serverEntry: typeof subOptions(tanstackStart, "server").entry === "string" ? "custom" : "default",
|
|
627
|
+
...startSource === void 0 ? {} : { requestMiddleware: /\brequestMiddleware\b/.test(startSource) },
|
|
628
|
+
routes
|
|
629
|
+
});
|
|
630
|
+
if (payload === void 0) return;
|
|
631
|
+
try {
|
|
632
|
+
await mkdir(nodePath.dirname(artifact), { recursive: true });
|
|
633
|
+
await writeFile(artifact, payload);
|
|
634
|
+
} catch (error) {
|
|
635
|
+
logWarning(`Could not write the route manifest; unknown paths fall back to SSR: ${String(error)}`);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
//#endregion
|
|
346
642
|
//#region src/index.ts
|
|
347
643
|
const SANDBOX_ENV_VAR = "DEV_SERVER__PROJECT_PATH";
|
|
348
644
|
const LOVABLE_SANDBOX_ENV_VAR = "LOVABLE_SANDBOX";
|
|
349
645
|
function isSandboxEnvironment() {
|
|
350
646
|
return process.env[LOVABLE_SANDBOX_ENV_VAR] === "1" || !!process.env[SANDBOX_ENV_VAR];
|
|
351
647
|
}
|
|
648
|
+
function sandboxFsAllow(root) {
|
|
649
|
+
let nodeModules;
|
|
650
|
+
let realRoot;
|
|
651
|
+
try {
|
|
652
|
+
nodeModules = realpathSync.native(join(root, "node_modules"));
|
|
653
|
+
realRoot = realpathSync.native(root);
|
|
654
|
+
} catch {
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
if (nodeModules.startsWith(realRoot + sep)) return void 0;
|
|
658
|
+
return [searchForWorkspaceRoot(root), nodeModules];
|
|
659
|
+
}
|
|
352
660
|
const TOOL_MANAGED_WATCH_IGNORES = [
|
|
353
661
|
"**/.workspace/**",
|
|
354
662
|
"**/.agents/**",
|
|
@@ -1198,6 +1506,10 @@ function defineConfig(configOrOptions = {}) {
|
|
|
1198
1506
|
if (command === "build" && isSandbox) {
|
|
1199
1507
|
const { startOptions, skipReporter, documentSet } = pinPrerenderForLovableBuild(tanstackStartOptions);
|
|
1200
1508
|
internalPlugins.push(tanstackStart(startOptions), skipReporter, documentSet);
|
|
1509
|
+
internalPlugins.push(createRouteManifestPlugin({
|
|
1510
|
+
tanstackStart: tanstackStartOptions,
|
|
1511
|
+
nitro: options.nitro
|
|
1512
|
+
}));
|
|
1201
1513
|
} else internalPlugins.push(tanstackStart(tanstackStartOptions));
|
|
1202
1514
|
const explicitNitro = options.nitro === true || typeof options.nitro === "object" && options.nitro !== null;
|
|
1203
1515
|
if (options.nitro !== false && command === "build") {
|
|
@@ -1320,12 +1632,14 @@ function defineConfig(configOrOptions = {}) {
|
|
|
1320
1632
|
}
|
|
1321
1633
|
if (isSandbox) {
|
|
1322
1634
|
validateConfig(config);
|
|
1635
|
+
const fsAllow = sandboxFsAllow(process.cwd());
|
|
1323
1636
|
config = cleanServerConfig(mergeConfig(config, { server: {
|
|
1324
1637
|
host: "::",
|
|
1325
1638
|
port: 8080,
|
|
1326
1639
|
strictPort: true,
|
|
1327
1640
|
watch: { ignored: [...TOOL_MANAGED_WATCH_IGNORES] },
|
|
1328
|
-
hmr: { overlay: false }
|
|
1641
|
+
hmr: { overlay: false },
|
|
1642
|
+
...fsAllow ? { fs: { allow: fsAllow } } : {}
|
|
1329
1643
|
} }));
|
|
1330
1644
|
} else config = mergeConfig({ server: {
|
|
1331
1645
|
host: "::",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lovable.dev/vite-tanstack-config",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.23.1",
|
|
4
4
|
"description": "Vite config wrapper for Lovable TanStack Start projects",
|
|
5
5
|
"main": "./dist/index.cjs",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -69,19 +69,28 @@
|
|
|
69
69
|
"vite-tsconfig-paths": ">=6.0.0",
|
|
70
70
|
"nitro": ">=3.0.260603-beta",
|
|
71
71
|
"@tanstack/react-start": ">=1.100.0",
|
|
72
|
+
"@tanstack/router-generator": ">=1.121.0 <1.166.45 || >=1.166.49",
|
|
72
73
|
"@vitejs/plugin-react": ">=4.0.0"
|
|
73
74
|
},
|
|
74
75
|
"peerDependenciesMeta": {
|
|
75
76
|
"nitro": {
|
|
76
77
|
"optional": true
|
|
78
|
+
},
|
|
79
|
+
"@tanstack/router-generator": {
|
|
80
|
+
"optional": true
|
|
77
81
|
}
|
|
78
82
|
},
|
|
79
83
|
"devDependencies": {
|
|
80
84
|
"@tailwindcss/vite": "^4.3.0",
|
|
85
|
+
"@tanstack/react-query": "^5.102.8",
|
|
86
|
+
"@tanstack/react-router": "1.170.30",
|
|
81
87
|
"@tanstack/react-start": "^1.167.50",
|
|
88
|
+
"@tanstack/router-generator": "^1.167.31",
|
|
82
89
|
"@types/node": "22.13.13",
|
|
83
90
|
"@vitejs/plugin-react": "^5.2.0",
|
|
84
91
|
"nitro": "3.0.260603-beta",
|
|
92
|
+
"react": "19.3.0",
|
|
93
|
+
"react-dom": "19.3.0",
|
|
85
94
|
"rimraf": "^5.0.0",
|
|
86
95
|
"tsdown": "^0.22.5",
|
|
87
96
|
"typescript": "7.0.2",
|
|
@@ -100,7 +109,8 @@
|
|
|
100
109
|
"typecheck": "tsgo --noEmit",
|
|
101
110
|
"dev": "tsdown --watch",
|
|
102
111
|
"test": "vitest run",
|
|
103
|
-
"test:
|
|
112
|
+
"test:unit": "vitest run --project unit",
|
|
113
|
+
"test:watch": "vitest --project unit",
|
|
104
114
|
"clean": "rimraf dist",
|
|
105
115
|
"coverage": "vitest run --coverage"
|
|
106
116
|
}
|