@lovable.dev/vite-tanstack-config 2.23.2 → 2.25.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/README.md +3 -1
- package/dist/index.cjs +287 -61
- package/dist/index.js +287 -61
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,4 +19,6 @@ Inside a Lovable build with prerender enabled, the build process is forced to ex
|
|
|
19
19
|
|
|
20
20
|
## Route manifest
|
|
21
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.
|
|
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?, urlInspection, 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.
|
|
23
|
+
|
|
24
|
+
`urlInspection` tells the serving layer whether the code that runs ahead of route matching could answer a request from its URL; only `none` lets it enforce the manifest, every other value keeps it advisory. The wrapper reads the server entry (`server.entry`, or `src/server.ts` by convention — the same rule sets `serverEntry`) and the start entry (`start.entry` or `src/start.ts`, whose `requestMiddleware` runs before the router) together with every module they import through relative or `@/` paths, with comments and string contents removed, and reports `server_entry`, `request_middleware` or `both` when any of it names the URL, one of its parts (`pathname`, `searchParams`, `path`), a redirect (`redirect`, `Location`, a `30x` status) or a not-found answer (`404`, `notFound`). It reports `unknown` instead of guessing when a module cannot be resolved or read, when a module is loaded by a computed specifier or `import.meta.glob`, when the closure imports a package other than `node:*`/`@tanstack/*` (a mounted framework can route without naming the URL), or when it outgrows 32 files / 256 KiB. The template's `server.ts` and `start.ts` read as `none`; keep them free of those names, or new projects stop asserting it.
|
package/dist/index.cjs
CHANGED
|
@@ -103,58 +103,9 @@ function lovableAssetsProxyPlugin() {
|
|
|
103
103
|
};
|
|
104
104
|
}
|
|
105
105
|
//#endregion
|
|
106
|
-
//#region src/bundledDev.ts
|
|
107
|
-
const BUNDLED_DEV_ENV_VAR = "LOVABLE_FEATURE_BUNDLED_DEV";
|
|
108
|
-
function bundledDevRequested() {
|
|
109
|
-
return process.env[BUNDLED_DEV_ENV_VAR] === "true";
|
|
110
|
-
}
|
|
111
|
-
function viteVersionSupportsBundledDev(version) {
|
|
112
|
-
if (!version || version.includes("-")) return false;
|
|
113
|
-
const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(version);
|
|
114
|
-
if (!match) return false;
|
|
115
|
-
const [major, minor] = [Number(match[1]), Number(match[2])];
|
|
116
|
-
return major === 8 && minor === 1;
|
|
117
|
-
}
|
|
118
|
-
async function resolveInstalledViteVersion() {
|
|
119
|
-
try {
|
|
120
|
-
const { createRequire } = await import("node:module");
|
|
121
|
-
const { version } = createRequire(`${process.cwd()}/package.json`)("vite/package.json");
|
|
122
|
-
return typeof version === "string" ? version : void 0;
|
|
123
|
-
} catch {
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
//#endregion
|
|
128
|
-
//#region src/bundledDevCssUrlShim.ts
|
|
129
|
-
const VIRTUAL_PREFIX = "\0lovable-bundled-dev-css-url:";
|
|
130
|
-
const VIRTUAL_SUFFIX = ".shim.js";
|
|
131
|
-
function bundledDevCssUrlShim() {
|
|
132
|
-
return {
|
|
133
|
-
name: "lovable-bundled-dev-css-url-shim",
|
|
134
|
-
apply: "serve",
|
|
135
|
-
enforce: "pre",
|
|
136
|
-
async resolveId(id, importer) {
|
|
137
|
-
if (!id.endsWith(".css?url")) return null;
|
|
138
|
-
const resolved = await this.resolve(id.slice(0, -4), importer, { skipSelf: true });
|
|
139
|
-
if (!resolved) return null;
|
|
140
|
-
return VIRTUAL_PREFIX + resolved.id + VIRTUAL_SUFFIX;
|
|
141
|
-
},
|
|
142
|
-
load(id) {
|
|
143
|
-
if (!id.startsWith(VIRTUAL_PREFIX) || !id.endsWith(VIRTUAL_SUFFIX)) return null;
|
|
144
|
-
const file = id.slice(29, -8);
|
|
145
|
-
const inlineSpecifier = file.includes("?") ? `${file}&inline` : `${file}?inline`;
|
|
146
|
-
return [
|
|
147
|
-
`import css from ${JSON.stringify(inlineSpecifier)};`,
|
|
148
|
-
`const b64 = typeof Buffer !== "undefined" ? Buffer.from(css, "utf8").toString("base64") : btoa(unescape(encodeURIComponent(css)));`,
|
|
149
|
-
`export default "data:text/css;base64," + b64;`
|
|
150
|
-
].join("\n");
|
|
151
|
-
}
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
//#endregion
|
|
155
106
|
//#region package.json
|
|
156
107
|
var name = "@lovable.dev/vite-tanstack-config";
|
|
157
|
-
var version = "2.
|
|
108
|
+
var version = "2.25.0";
|
|
158
109
|
//#endregion
|
|
159
110
|
//#region src/logging.ts
|
|
160
111
|
const PACKAGE_NAME = "@lovable.dev/vite-tanstack-config";
|
|
@@ -388,6 +339,285 @@ function linkedDependenciesPlugin() {
|
|
|
388
339
|
};
|
|
389
340
|
}
|
|
390
341
|
//#endregion
|
|
342
|
+
//#region src/urlInspection.ts
|
|
343
|
+
const URL_TOKENS = /\b(?:url|URL|URLPattern|URLSearchParams|pathname|searchParams|path|404|30\d|notFound|isNotFound|redirect|isRedirect|Location|location)\b/;
|
|
344
|
+
const REQUEST_MIDDLEWARE = /\brequestMiddleware\b/;
|
|
345
|
+
const STATIC_IMPORT = /\b(?:import|export)\b(?<type>\s+type\b)?[^'"`;]*?(?<!\.)\bfrom\s*(['"])(?<from>[^'"\n]+)\2|\bimport\s*(['"])(?<side>[^'"\n]+)\4/g;
|
|
346
|
+
const DYNAMIC_IMPORT = /\b(?:import|require)\s*\(\s*(['"`])(?<spec>[^'"`\n]+)\1\s*(?:,[^)]*)?\)/g;
|
|
347
|
+
const DYNAMIC_LOAD = /\b(?:import|require)\s*(?:\.\s*meta\s*\.\s*glob\s*)?\(/g;
|
|
348
|
+
const READABLE_LOAD = /\b(?:import|require)\s*\(\s*(['"`])\1\s*(?:,[^)]*)?\)/y;
|
|
349
|
+
const IMPORT_EXTENSIONS = [
|
|
350
|
+
".ts",
|
|
351
|
+
".js",
|
|
352
|
+
".mts",
|
|
353
|
+
".mjs",
|
|
354
|
+
".tsx",
|
|
355
|
+
".jsx"
|
|
356
|
+
];
|
|
357
|
+
const JS_TO_TS = {
|
|
358
|
+
".js": [".ts", ".tsx"],
|
|
359
|
+
".jsx": [".tsx"],
|
|
360
|
+
".mjs": [".mts"]
|
|
361
|
+
};
|
|
362
|
+
const CODE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
363
|
+
".ts",
|
|
364
|
+
".tsx",
|
|
365
|
+
".js",
|
|
366
|
+
".jsx",
|
|
367
|
+
".mts",
|
|
368
|
+
".mjs",
|
|
369
|
+
".cts",
|
|
370
|
+
".cjs"
|
|
371
|
+
]);
|
|
372
|
+
const TRUSTED_SPECIFIERS = ["node:", "@tanstack/"];
|
|
373
|
+
const REGEX_AFTER_KEYWORDS = /* @__PURE__ */ new Set([
|
|
374
|
+
"return",
|
|
375
|
+
"typeof",
|
|
376
|
+
"instanceof",
|
|
377
|
+
"in",
|
|
378
|
+
"of",
|
|
379
|
+
"new",
|
|
380
|
+
"delete",
|
|
381
|
+
"void",
|
|
382
|
+
"throw",
|
|
383
|
+
"case",
|
|
384
|
+
"do",
|
|
385
|
+
"else",
|
|
386
|
+
"yield",
|
|
387
|
+
"await"
|
|
388
|
+
]);
|
|
389
|
+
/**
|
|
390
|
+
* Splits comments, string and regex literals from code with a small state machine, so a
|
|
391
|
+
* `//` inside a URL literal or a quote inside a regex cannot swallow the code after it.
|
|
392
|
+
*/
|
|
393
|
+
function lexSource(source) {
|
|
394
|
+
const code = [];
|
|
395
|
+
const text = [];
|
|
396
|
+
let i = 0;
|
|
397
|
+
let word = "";
|
|
398
|
+
let slashDivides = false;
|
|
399
|
+
let prev = "";
|
|
400
|
+
let closedIncrement = false;
|
|
401
|
+
const resetOperand = (divides) => {
|
|
402
|
+
word = "";
|
|
403
|
+
slashDivides = divides;
|
|
404
|
+
prev = "";
|
|
405
|
+
closedIncrement = false;
|
|
406
|
+
};
|
|
407
|
+
const regexAllowed = () => word === "" ? !slashDivides : REGEX_AFTER_KEYWORDS.has(word);
|
|
408
|
+
const readQuoted = (quote) => {
|
|
409
|
+
const start = i++;
|
|
410
|
+
while (i < source.length && source[i] !== quote && source[i] !== "\n") {
|
|
411
|
+
if (source[i] === "\\") i++;
|
|
412
|
+
i++;
|
|
413
|
+
}
|
|
414
|
+
if (source[i] === quote) i++;
|
|
415
|
+
text.push(source.slice(start, i));
|
|
416
|
+
code.push(quote + quote);
|
|
417
|
+
resetOperand(true);
|
|
418
|
+
};
|
|
419
|
+
const readRegex = () => {
|
|
420
|
+
const start = i;
|
|
421
|
+
let inClass = false;
|
|
422
|
+
for (i++; i < source.length && source[i] !== "\n"; i++) {
|
|
423
|
+
const char = source[i];
|
|
424
|
+
if (char === "\\") i++;
|
|
425
|
+
else if (char === "[") inClass = true;
|
|
426
|
+
else if (char === "]") inClass = false;
|
|
427
|
+
else if (char === "/" && !inClass) {
|
|
428
|
+
i++;
|
|
429
|
+
while (i < source.length && /[a-z]/.test(source[i])) i++;
|
|
430
|
+
text.push(source.slice(start, i));
|
|
431
|
+
code.push("//");
|
|
432
|
+
resetOperand(true);
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
i = start + 1;
|
|
437
|
+
text.push("/");
|
|
438
|
+
code.push("/");
|
|
439
|
+
resetOperand(false);
|
|
440
|
+
};
|
|
441
|
+
const readTemplate = () => {
|
|
442
|
+
i++;
|
|
443
|
+
text.push("`");
|
|
444
|
+
code.push("`");
|
|
445
|
+
while (i < source.length) {
|
|
446
|
+
const char = source[i];
|
|
447
|
+
if (char === "\\") {
|
|
448
|
+
text.push(source.slice(i, i + 2));
|
|
449
|
+
i += 2;
|
|
450
|
+
} else if (char === "`") {
|
|
451
|
+
text.push("`");
|
|
452
|
+
code.push("`");
|
|
453
|
+
i++;
|
|
454
|
+
resetOperand(true);
|
|
455
|
+
return;
|
|
456
|
+
} else if (char === "$" && source[i + 1] === "{") {
|
|
457
|
+
text.push("${");
|
|
458
|
+
code.push("${");
|
|
459
|
+
i += 2;
|
|
460
|
+
resetOperand(false);
|
|
461
|
+
readCode(true);
|
|
462
|
+
text.push("}");
|
|
463
|
+
code.push("}");
|
|
464
|
+
} else {
|
|
465
|
+
text.push(char);
|
|
466
|
+
i++;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
};
|
|
470
|
+
const readCode = (untilBrace) => {
|
|
471
|
+
let depth = 0;
|
|
472
|
+
while (i < source.length) {
|
|
473
|
+
const char = source[i];
|
|
474
|
+
const next = source[i + 1];
|
|
475
|
+
if (char === "/" && next === "/") {
|
|
476
|
+
const end = source.indexOf("\n", i);
|
|
477
|
+
i = end === -1 ? source.length : end;
|
|
478
|
+
continue;
|
|
479
|
+
}
|
|
480
|
+
if (char === "/" && next === "*") {
|
|
481
|
+
const end = source.indexOf("*/", i + 2);
|
|
482
|
+
i = end === -1 ? source.length : end + 2;
|
|
483
|
+
text.push(" ");
|
|
484
|
+
code.push(" ");
|
|
485
|
+
prev = " ";
|
|
486
|
+
continue;
|
|
487
|
+
}
|
|
488
|
+
if (char === "'" || char === "\"") {
|
|
489
|
+
readQuoted(char);
|
|
490
|
+
continue;
|
|
491
|
+
}
|
|
492
|
+
if (char === "`") {
|
|
493
|
+
readTemplate();
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
496
|
+
if (char === "/" && regexAllowed()) {
|
|
497
|
+
readRegex();
|
|
498
|
+
continue;
|
|
499
|
+
}
|
|
500
|
+
if (untilBrace && char === "{") depth++;
|
|
501
|
+
if (untilBrace && char === "}") {
|
|
502
|
+
if (depth === 0) {
|
|
503
|
+
i++;
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
depth--;
|
|
507
|
+
}
|
|
508
|
+
text.push(char);
|
|
509
|
+
code.push(char);
|
|
510
|
+
i++;
|
|
511
|
+
if (/[\w$]/.test(char)) word += char;
|
|
512
|
+
else if (!/\s/.test(char)) {
|
|
513
|
+
const closesIncrement = (char === "+" || char === "-") && prev === char && !closedIncrement;
|
|
514
|
+
closedIncrement = closesIncrement;
|
|
515
|
+
word = "";
|
|
516
|
+
slashDivides = closesIncrement || /[)\]}<]/.test(char);
|
|
517
|
+
}
|
|
518
|
+
prev = char;
|
|
519
|
+
}
|
|
520
|
+
};
|
|
521
|
+
readCode(false);
|
|
522
|
+
return {
|
|
523
|
+
code: code.join(""),
|
|
524
|
+
text: text.join("")
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
/** Every module specifier `text` (comment-free source) imports at runtime, in source order. */
|
|
528
|
+
function importSpecifiers(text) {
|
|
529
|
+
const specifiers = [];
|
|
530
|
+
for (const match of text.matchAll(STATIC_IMPORT)) {
|
|
531
|
+
if (match.groups?.type !== void 0) continue;
|
|
532
|
+
const specifier = match.groups?.from ?? match.groups?.side;
|
|
533
|
+
if (specifier !== void 0) specifiers.push(specifier);
|
|
534
|
+
}
|
|
535
|
+
for (const match of text.matchAll(DYNAMIC_IMPORT)) if (match.groups?.spec !== void 0) specifiers.push(match.groups.spec);
|
|
536
|
+
return specifiers;
|
|
537
|
+
}
|
|
538
|
+
/** Whether `code` loads a module importSpecifiers cannot name: a computed specifier, `import.meta.glob`, an argument list it does not read. */
|
|
539
|
+
function hasUnreadImports(code) {
|
|
540
|
+
for (const match of code.matchAll(DYNAMIC_LOAD)) {
|
|
541
|
+
READABLE_LOAD.lastIndex = match.index;
|
|
542
|
+
if (!READABLE_LOAD.test(code)) return true;
|
|
543
|
+
}
|
|
544
|
+
return false;
|
|
545
|
+
}
|
|
546
|
+
function inspectsUrl(code) {
|
|
547
|
+
return URL_TOKENS.test(code);
|
|
548
|
+
}
|
|
549
|
+
function classifySpecifier(specifier, fromDir, aliasDir, nodePath) {
|
|
550
|
+
if (specifier.includes("${")) return "opaque";
|
|
551
|
+
const file = specifier.replace(/\?.*$/, "");
|
|
552
|
+
if (file.startsWith("./") || file.startsWith("../")) return { file: nodePath.resolve(fromDir, file) };
|
|
553
|
+
if (file.startsWith("@/")) return { file: nodePath.resolve(aliasDir, file.slice(2)) };
|
|
554
|
+
return TRUSTED_SPECIFIERS.some((prefix) => specifier.startsWith(prefix)) ? "trusted" : "opaque";
|
|
555
|
+
}
|
|
556
|
+
async function isFile(file) {
|
|
557
|
+
const { stat } = await import("node:fs/promises");
|
|
558
|
+
return stat(file).then((stats) => stats.isFile(), () => false);
|
|
559
|
+
}
|
|
560
|
+
async function resolveLocalImport(base, nodePath) {
|
|
561
|
+
const candidates = [base, ...IMPORT_EXTENSIONS.map((extension) => `${base}${extension}`)];
|
|
562
|
+
const extension = nodePath.extname(base);
|
|
563
|
+
for (const swapped of JS_TO_TS[extension] ?? []) candidates.push(`${base.slice(0, -extension.length)}${swapped}`);
|
|
564
|
+
candidates.push(...IMPORT_EXTENSIONS.map((suffix) => nodePath.join(base, `index${suffix}`)));
|
|
565
|
+
for (const candidate of candidates) if (await isFile(candidate)) return candidate;
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* Scans `entryFile` and every local module it imports, transitively; undefined without
|
|
569
|
+
* an entry. Anything it cannot read or vouch for leaves the scan incomplete, not guessed.
|
|
570
|
+
*/
|
|
571
|
+
async function scanEntry(entryFile, options) {
|
|
572
|
+
if (entryFile === void 0) return void 0;
|
|
573
|
+
const nodePath = await import("node:path");
|
|
574
|
+
const { readFile } = await import("node:fs/promises");
|
|
575
|
+
const scan = {
|
|
576
|
+
complete: true,
|
|
577
|
+
inspectsUrl: false,
|
|
578
|
+
requestMiddleware: false
|
|
579
|
+
};
|
|
580
|
+
const incomplete = () => ({
|
|
581
|
+
...scan,
|
|
582
|
+
complete: false
|
|
583
|
+
});
|
|
584
|
+
const seen = /* @__PURE__ */ new Set();
|
|
585
|
+
const queue = [entryFile];
|
|
586
|
+
let bytes = 0;
|
|
587
|
+
while (queue.length > 0) {
|
|
588
|
+
const file = queue.shift();
|
|
589
|
+
if (seen.has(file)) continue;
|
|
590
|
+
seen.add(file);
|
|
591
|
+
if (seen.size > 32 || file.split(nodePath.sep).includes("node_modules")) return incomplete();
|
|
592
|
+
const source = await readFile(file, "utf8").catch(() => void 0);
|
|
593
|
+
if (source === void 0) return incomplete();
|
|
594
|
+
bytes += Buffer.byteLength(source, "utf8");
|
|
595
|
+
if (bytes > 262144) return incomplete();
|
|
596
|
+
const { code, text } = lexSource(source);
|
|
597
|
+
scan.inspectsUrl ||= inspectsUrl(code);
|
|
598
|
+
scan.requestMiddleware ||= REQUEST_MIDDLEWARE.test(code);
|
|
599
|
+
if (hasUnreadImports(code)) return incomplete();
|
|
600
|
+
for (const specifier of importSpecifiers(text)) {
|
|
601
|
+
const resolution = classifySpecifier(specifier, nodePath.dirname(file), options.aliasDir, nodePath);
|
|
602
|
+
if (resolution === "trusted") continue;
|
|
603
|
+
const resolved = resolution === "opaque" ? void 0 : await resolveLocalImport(resolution.file, nodePath);
|
|
604
|
+
if (resolved === void 0) return incomplete();
|
|
605
|
+
if (CODE_EXTENSIONS.has(nodePath.extname(resolved))) queue.push(resolved);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
return scan;
|
|
609
|
+
}
|
|
610
|
+
/** The manifest's verdict over the server entry and the start entry (request middleware). */
|
|
611
|
+
function urlInspectionOf(server, start) {
|
|
612
|
+
const serverInspects = server?.inspectsUrl === true;
|
|
613
|
+
const middlewareInspects = start?.inspectsUrl === true;
|
|
614
|
+
if (serverInspects && middlewareInspects) return "both";
|
|
615
|
+
if (serverInspects) return "server_entry";
|
|
616
|
+
if (middlewareInspects) return "request_middleware";
|
|
617
|
+
if (server?.complete === false || start?.complete === false) return "unknown";
|
|
618
|
+
return "none";
|
|
619
|
+
}
|
|
620
|
+
//#endregion
|
|
391
621
|
//#region src/routes.ts
|
|
392
622
|
const ROUTE_MANIFEST_VERSION = 1;
|
|
393
623
|
const GENERATOR = `${name}@${version}`;
|
|
@@ -662,13 +892,17 @@ function createRouteManifestPlugin(inputs) {
|
|
|
662
892
|
logWarning("Not publishing a route manifest: the route tree has no routes.");
|
|
663
893
|
return;
|
|
664
894
|
}
|
|
665
|
-
const
|
|
895
|
+
const serverFile = await resolveEntryFile(sourceDir, stringOption(subOptions(tanstackStart, "server"), "entry", "server"));
|
|
896
|
+
const startFile = await resolveEntryFile(sourceDir, stringOption(subOptions(tanstackStart, "start"), "entry", "start"));
|
|
897
|
+
const aliasDir = nodePath.resolve(process.cwd(), "src");
|
|
898
|
+
const [server, start] = await Promise.all([scanEntry(serverFile, { aliasDir }), scanEntry(startFile, { aliasDir })]);
|
|
666
899
|
const payload = serializeRouteManifest({
|
|
667
900
|
version: ROUTE_MANIFEST_VERSION,
|
|
668
901
|
generator: GENERATOR,
|
|
669
902
|
...basepath === "" ? {} : { basepath },
|
|
670
|
-
serverEntry:
|
|
671
|
-
...
|
|
903
|
+
serverEntry: serverFile === void 0 ? "default" : "custom",
|
|
904
|
+
...start === void 0 ? {} : { requestMiddleware: start.requestMiddleware },
|
|
905
|
+
urlInspection: urlInspectionOf(server, start),
|
|
672
906
|
routes
|
|
673
907
|
});
|
|
674
908
|
if (payload === void 0) return;
|
|
@@ -1655,14 +1889,6 @@ function defineConfig(configOrOptions = {}) {
|
|
|
1655
1889
|
plugins: [...internalPlugins, ...options.plugins ?? []]
|
|
1656
1890
|
};
|
|
1657
1891
|
if (options.vite) config = (0, vite.mergeConfig)(config, options.vite);
|
|
1658
|
-
if (command === "serve" && isSandbox && bundledDevRequested()) {
|
|
1659
|
-
const viteVersion = await resolveInstalledViteVersion();
|
|
1660
|
-
if (viteVersionSupportsBundledDev(viteVersion)) config = (0, vite.mergeConfig)(config, {
|
|
1661
|
-
experimental: { bundledDev: true },
|
|
1662
|
-
plugins: [bundledDevCssUrlShim()]
|
|
1663
|
-
});
|
|
1664
|
-
else logWarning(`Bundled dev requested (${BUNDLED_DEV_ENV_VAR}) but the installed vite (${viteVersion ?? "unresolved"}) is outside the supported 8.1.x range — starting classic dev.`);
|
|
1665
|
-
}
|
|
1666
1892
|
if (isSandbox) {
|
|
1667
1893
|
validateConfig(config);
|
|
1668
1894
|
config = cleanServerConfig((0, vite.mergeConfig)(config, { server: {
|
package/dist/index.js
CHANGED
|
@@ -79,58 +79,9 @@ function lovableAssetsProxyPlugin() {
|
|
|
79
79
|
};
|
|
80
80
|
}
|
|
81
81
|
//#endregion
|
|
82
|
-
//#region src/bundledDev.ts
|
|
83
|
-
const BUNDLED_DEV_ENV_VAR = "LOVABLE_FEATURE_BUNDLED_DEV";
|
|
84
|
-
function bundledDevRequested() {
|
|
85
|
-
return process.env[BUNDLED_DEV_ENV_VAR] === "true";
|
|
86
|
-
}
|
|
87
|
-
function viteVersionSupportsBundledDev(version) {
|
|
88
|
-
if (!version || version.includes("-")) return false;
|
|
89
|
-
const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(version);
|
|
90
|
-
if (!match) return false;
|
|
91
|
-
const [major, minor] = [Number(match[1]), Number(match[2])];
|
|
92
|
-
return major === 8 && minor === 1;
|
|
93
|
-
}
|
|
94
|
-
async function resolveInstalledViteVersion() {
|
|
95
|
-
try {
|
|
96
|
-
const { createRequire } = await import("node:module");
|
|
97
|
-
const { version } = createRequire(`${process.cwd()}/package.json`)("vite/package.json");
|
|
98
|
-
return typeof version === "string" ? version : void 0;
|
|
99
|
-
} catch {
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
//#endregion
|
|
104
|
-
//#region src/bundledDevCssUrlShim.ts
|
|
105
|
-
const VIRTUAL_PREFIX = "\0lovable-bundled-dev-css-url:";
|
|
106
|
-
const VIRTUAL_SUFFIX = ".shim.js";
|
|
107
|
-
function bundledDevCssUrlShim() {
|
|
108
|
-
return {
|
|
109
|
-
name: "lovable-bundled-dev-css-url-shim",
|
|
110
|
-
apply: "serve",
|
|
111
|
-
enforce: "pre",
|
|
112
|
-
async resolveId(id, importer) {
|
|
113
|
-
if (!id.endsWith(".css?url")) return null;
|
|
114
|
-
const resolved = await this.resolve(id.slice(0, -4), importer, { skipSelf: true });
|
|
115
|
-
if (!resolved) return null;
|
|
116
|
-
return VIRTUAL_PREFIX + resolved.id + VIRTUAL_SUFFIX;
|
|
117
|
-
},
|
|
118
|
-
load(id) {
|
|
119
|
-
if (!id.startsWith(VIRTUAL_PREFIX) || !id.endsWith(VIRTUAL_SUFFIX)) return null;
|
|
120
|
-
const file = id.slice(29, -8);
|
|
121
|
-
const inlineSpecifier = file.includes("?") ? `${file}&inline` : `${file}?inline`;
|
|
122
|
-
return [
|
|
123
|
-
`import css from ${JSON.stringify(inlineSpecifier)};`,
|
|
124
|
-
`const b64 = typeof Buffer !== "undefined" ? Buffer.from(css, "utf8").toString("base64") : btoa(unescape(encodeURIComponent(css)));`,
|
|
125
|
-
`export default "data:text/css;base64," + b64;`
|
|
126
|
-
].join("\n");
|
|
127
|
-
}
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
//#endregion
|
|
131
82
|
//#region package.json
|
|
132
83
|
var name = "@lovable.dev/vite-tanstack-config";
|
|
133
|
-
var version = "2.
|
|
84
|
+
var version = "2.25.0";
|
|
134
85
|
//#endregion
|
|
135
86
|
//#region src/logging.ts
|
|
136
87
|
const PACKAGE_NAME = "@lovable.dev/vite-tanstack-config";
|
|
@@ -364,6 +315,285 @@ function linkedDependenciesPlugin() {
|
|
|
364
315
|
};
|
|
365
316
|
}
|
|
366
317
|
//#endregion
|
|
318
|
+
//#region src/urlInspection.ts
|
|
319
|
+
const URL_TOKENS = /\b(?:url|URL|URLPattern|URLSearchParams|pathname|searchParams|path|404|30\d|notFound|isNotFound|redirect|isRedirect|Location|location)\b/;
|
|
320
|
+
const REQUEST_MIDDLEWARE = /\brequestMiddleware\b/;
|
|
321
|
+
const STATIC_IMPORT = /\b(?:import|export)\b(?<type>\s+type\b)?[^'"`;]*?(?<!\.)\bfrom\s*(['"])(?<from>[^'"\n]+)\2|\bimport\s*(['"])(?<side>[^'"\n]+)\4/g;
|
|
322
|
+
const DYNAMIC_IMPORT = /\b(?:import|require)\s*\(\s*(['"`])(?<spec>[^'"`\n]+)\1\s*(?:,[^)]*)?\)/g;
|
|
323
|
+
const DYNAMIC_LOAD = /\b(?:import|require)\s*(?:\.\s*meta\s*\.\s*glob\s*)?\(/g;
|
|
324
|
+
const READABLE_LOAD = /\b(?:import|require)\s*\(\s*(['"`])\1\s*(?:,[^)]*)?\)/y;
|
|
325
|
+
const IMPORT_EXTENSIONS = [
|
|
326
|
+
".ts",
|
|
327
|
+
".js",
|
|
328
|
+
".mts",
|
|
329
|
+
".mjs",
|
|
330
|
+
".tsx",
|
|
331
|
+
".jsx"
|
|
332
|
+
];
|
|
333
|
+
const JS_TO_TS = {
|
|
334
|
+
".js": [".ts", ".tsx"],
|
|
335
|
+
".jsx": [".tsx"],
|
|
336
|
+
".mjs": [".mts"]
|
|
337
|
+
};
|
|
338
|
+
const CODE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
339
|
+
".ts",
|
|
340
|
+
".tsx",
|
|
341
|
+
".js",
|
|
342
|
+
".jsx",
|
|
343
|
+
".mts",
|
|
344
|
+
".mjs",
|
|
345
|
+
".cts",
|
|
346
|
+
".cjs"
|
|
347
|
+
]);
|
|
348
|
+
const TRUSTED_SPECIFIERS = ["node:", "@tanstack/"];
|
|
349
|
+
const REGEX_AFTER_KEYWORDS = /* @__PURE__ */ new Set([
|
|
350
|
+
"return",
|
|
351
|
+
"typeof",
|
|
352
|
+
"instanceof",
|
|
353
|
+
"in",
|
|
354
|
+
"of",
|
|
355
|
+
"new",
|
|
356
|
+
"delete",
|
|
357
|
+
"void",
|
|
358
|
+
"throw",
|
|
359
|
+
"case",
|
|
360
|
+
"do",
|
|
361
|
+
"else",
|
|
362
|
+
"yield",
|
|
363
|
+
"await"
|
|
364
|
+
]);
|
|
365
|
+
/**
|
|
366
|
+
* Splits comments, string and regex literals from code with a small state machine, so a
|
|
367
|
+
* `//` inside a URL literal or a quote inside a regex cannot swallow the code after it.
|
|
368
|
+
*/
|
|
369
|
+
function lexSource(source) {
|
|
370
|
+
const code = [];
|
|
371
|
+
const text = [];
|
|
372
|
+
let i = 0;
|
|
373
|
+
let word = "";
|
|
374
|
+
let slashDivides = false;
|
|
375
|
+
let prev = "";
|
|
376
|
+
let closedIncrement = false;
|
|
377
|
+
const resetOperand = (divides) => {
|
|
378
|
+
word = "";
|
|
379
|
+
slashDivides = divides;
|
|
380
|
+
prev = "";
|
|
381
|
+
closedIncrement = false;
|
|
382
|
+
};
|
|
383
|
+
const regexAllowed = () => word === "" ? !slashDivides : REGEX_AFTER_KEYWORDS.has(word);
|
|
384
|
+
const readQuoted = (quote) => {
|
|
385
|
+
const start = i++;
|
|
386
|
+
while (i < source.length && source[i] !== quote && source[i] !== "\n") {
|
|
387
|
+
if (source[i] === "\\") i++;
|
|
388
|
+
i++;
|
|
389
|
+
}
|
|
390
|
+
if (source[i] === quote) i++;
|
|
391
|
+
text.push(source.slice(start, i));
|
|
392
|
+
code.push(quote + quote);
|
|
393
|
+
resetOperand(true);
|
|
394
|
+
};
|
|
395
|
+
const readRegex = () => {
|
|
396
|
+
const start = i;
|
|
397
|
+
let inClass = false;
|
|
398
|
+
for (i++; i < source.length && source[i] !== "\n"; i++) {
|
|
399
|
+
const char = source[i];
|
|
400
|
+
if (char === "\\") i++;
|
|
401
|
+
else if (char === "[") inClass = true;
|
|
402
|
+
else if (char === "]") inClass = false;
|
|
403
|
+
else if (char === "/" && !inClass) {
|
|
404
|
+
i++;
|
|
405
|
+
while (i < source.length && /[a-z]/.test(source[i])) i++;
|
|
406
|
+
text.push(source.slice(start, i));
|
|
407
|
+
code.push("//");
|
|
408
|
+
resetOperand(true);
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
i = start + 1;
|
|
413
|
+
text.push("/");
|
|
414
|
+
code.push("/");
|
|
415
|
+
resetOperand(false);
|
|
416
|
+
};
|
|
417
|
+
const readTemplate = () => {
|
|
418
|
+
i++;
|
|
419
|
+
text.push("`");
|
|
420
|
+
code.push("`");
|
|
421
|
+
while (i < source.length) {
|
|
422
|
+
const char = source[i];
|
|
423
|
+
if (char === "\\") {
|
|
424
|
+
text.push(source.slice(i, i + 2));
|
|
425
|
+
i += 2;
|
|
426
|
+
} else if (char === "`") {
|
|
427
|
+
text.push("`");
|
|
428
|
+
code.push("`");
|
|
429
|
+
i++;
|
|
430
|
+
resetOperand(true);
|
|
431
|
+
return;
|
|
432
|
+
} else if (char === "$" && source[i + 1] === "{") {
|
|
433
|
+
text.push("${");
|
|
434
|
+
code.push("${");
|
|
435
|
+
i += 2;
|
|
436
|
+
resetOperand(false);
|
|
437
|
+
readCode(true);
|
|
438
|
+
text.push("}");
|
|
439
|
+
code.push("}");
|
|
440
|
+
} else {
|
|
441
|
+
text.push(char);
|
|
442
|
+
i++;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
const readCode = (untilBrace) => {
|
|
447
|
+
let depth = 0;
|
|
448
|
+
while (i < source.length) {
|
|
449
|
+
const char = source[i];
|
|
450
|
+
const next = source[i + 1];
|
|
451
|
+
if (char === "/" && next === "/") {
|
|
452
|
+
const end = source.indexOf("\n", i);
|
|
453
|
+
i = end === -1 ? source.length : end;
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
if (char === "/" && next === "*") {
|
|
457
|
+
const end = source.indexOf("*/", i + 2);
|
|
458
|
+
i = end === -1 ? source.length : end + 2;
|
|
459
|
+
text.push(" ");
|
|
460
|
+
code.push(" ");
|
|
461
|
+
prev = " ";
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
if (char === "'" || char === "\"") {
|
|
465
|
+
readQuoted(char);
|
|
466
|
+
continue;
|
|
467
|
+
}
|
|
468
|
+
if (char === "`") {
|
|
469
|
+
readTemplate();
|
|
470
|
+
continue;
|
|
471
|
+
}
|
|
472
|
+
if (char === "/" && regexAllowed()) {
|
|
473
|
+
readRegex();
|
|
474
|
+
continue;
|
|
475
|
+
}
|
|
476
|
+
if (untilBrace && char === "{") depth++;
|
|
477
|
+
if (untilBrace && char === "}") {
|
|
478
|
+
if (depth === 0) {
|
|
479
|
+
i++;
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
depth--;
|
|
483
|
+
}
|
|
484
|
+
text.push(char);
|
|
485
|
+
code.push(char);
|
|
486
|
+
i++;
|
|
487
|
+
if (/[\w$]/.test(char)) word += char;
|
|
488
|
+
else if (!/\s/.test(char)) {
|
|
489
|
+
const closesIncrement = (char === "+" || char === "-") && prev === char && !closedIncrement;
|
|
490
|
+
closedIncrement = closesIncrement;
|
|
491
|
+
word = "";
|
|
492
|
+
slashDivides = closesIncrement || /[)\]}<]/.test(char);
|
|
493
|
+
}
|
|
494
|
+
prev = char;
|
|
495
|
+
}
|
|
496
|
+
};
|
|
497
|
+
readCode(false);
|
|
498
|
+
return {
|
|
499
|
+
code: code.join(""),
|
|
500
|
+
text: text.join("")
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
/** Every module specifier `text` (comment-free source) imports at runtime, in source order. */
|
|
504
|
+
function importSpecifiers(text) {
|
|
505
|
+
const specifiers = [];
|
|
506
|
+
for (const match of text.matchAll(STATIC_IMPORT)) {
|
|
507
|
+
if (match.groups?.type !== void 0) continue;
|
|
508
|
+
const specifier = match.groups?.from ?? match.groups?.side;
|
|
509
|
+
if (specifier !== void 0) specifiers.push(specifier);
|
|
510
|
+
}
|
|
511
|
+
for (const match of text.matchAll(DYNAMIC_IMPORT)) if (match.groups?.spec !== void 0) specifiers.push(match.groups.spec);
|
|
512
|
+
return specifiers;
|
|
513
|
+
}
|
|
514
|
+
/** Whether `code` loads a module importSpecifiers cannot name: a computed specifier, `import.meta.glob`, an argument list it does not read. */
|
|
515
|
+
function hasUnreadImports(code) {
|
|
516
|
+
for (const match of code.matchAll(DYNAMIC_LOAD)) {
|
|
517
|
+
READABLE_LOAD.lastIndex = match.index;
|
|
518
|
+
if (!READABLE_LOAD.test(code)) return true;
|
|
519
|
+
}
|
|
520
|
+
return false;
|
|
521
|
+
}
|
|
522
|
+
function inspectsUrl(code) {
|
|
523
|
+
return URL_TOKENS.test(code);
|
|
524
|
+
}
|
|
525
|
+
function classifySpecifier(specifier, fromDir, aliasDir, nodePath) {
|
|
526
|
+
if (specifier.includes("${")) return "opaque";
|
|
527
|
+
const file = specifier.replace(/\?.*$/, "");
|
|
528
|
+
if (file.startsWith("./") || file.startsWith("../")) return { file: nodePath.resolve(fromDir, file) };
|
|
529
|
+
if (file.startsWith("@/")) return { file: nodePath.resolve(aliasDir, file.slice(2)) };
|
|
530
|
+
return TRUSTED_SPECIFIERS.some((prefix) => specifier.startsWith(prefix)) ? "trusted" : "opaque";
|
|
531
|
+
}
|
|
532
|
+
async function isFile(file) {
|
|
533
|
+
const { stat } = await import("node:fs/promises");
|
|
534
|
+
return stat(file).then((stats) => stats.isFile(), () => false);
|
|
535
|
+
}
|
|
536
|
+
async function resolveLocalImport(base, nodePath) {
|
|
537
|
+
const candidates = [base, ...IMPORT_EXTENSIONS.map((extension) => `${base}${extension}`)];
|
|
538
|
+
const extension = nodePath.extname(base);
|
|
539
|
+
for (const swapped of JS_TO_TS[extension] ?? []) candidates.push(`${base.slice(0, -extension.length)}${swapped}`);
|
|
540
|
+
candidates.push(...IMPORT_EXTENSIONS.map((suffix) => nodePath.join(base, `index${suffix}`)));
|
|
541
|
+
for (const candidate of candidates) if (await isFile(candidate)) return candidate;
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Scans `entryFile` and every local module it imports, transitively; undefined without
|
|
545
|
+
* an entry. Anything it cannot read or vouch for leaves the scan incomplete, not guessed.
|
|
546
|
+
*/
|
|
547
|
+
async function scanEntry(entryFile, options) {
|
|
548
|
+
if (entryFile === void 0) return void 0;
|
|
549
|
+
const nodePath = await import("node:path");
|
|
550
|
+
const { readFile } = await import("node:fs/promises");
|
|
551
|
+
const scan = {
|
|
552
|
+
complete: true,
|
|
553
|
+
inspectsUrl: false,
|
|
554
|
+
requestMiddleware: false
|
|
555
|
+
};
|
|
556
|
+
const incomplete = () => ({
|
|
557
|
+
...scan,
|
|
558
|
+
complete: false
|
|
559
|
+
});
|
|
560
|
+
const seen = /* @__PURE__ */ new Set();
|
|
561
|
+
const queue = [entryFile];
|
|
562
|
+
let bytes = 0;
|
|
563
|
+
while (queue.length > 0) {
|
|
564
|
+
const file = queue.shift();
|
|
565
|
+
if (seen.has(file)) continue;
|
|
566
|
+
seen.add(file);
|
|
567
|
+
if (seen.size > 32 || file.split(nodePath.sep).includes("node_modules")) return incomplete();
|
|
568
|
+
const source = await readFile(file, "utf8").catch(() => void 0);
|
|
569
|
+
if (source === void 0) return incomplete();
|
|
570
|
+
bytes += Buffer.byteLength(source, "utf8");
|
|
571
|
+
if (bytes > 262144) return incomplete();
|
|
572
|
+
const { code, text } = lexSource(source);
|
|
573
|
+
scan.inspectsUrl ||= inspectsUrl(code);
|
|
574
|
+
scan.requestMiddleware ||= REQUEST_MIDDLEWARE.test(code);
|
|
575
|
+
if (hasUnreadImports(code)) return incomplete();
|
|
576
|
+
for (const specifier of importSpecifiers(text)) {
|
|
577
|
+
const resolution = classifySpecifier(specifier, nodePath.dirname(file), options.aliasDir, nodePath);
|
|
578
|
+
if (resolution === "trusted") continue;
|
|
579
|
+
const resolved = resolution === "opaque" ? void 0 : await resolveLocalImport(resolution.file, nodePath);
|
|
580
|
+
if (resolved === void 0) return incomplete();
|
|
581
|
+
if (CODE_EXTENSIONS.has(nodePath.extname(resolved))) queue.push(resolved);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
return scan;
|
|
585
|
+
}
|
|
586
|
+
/** The manifest's verdict over the server entry and the start entry (request middleware). */
|
|
587
|
+
function urlInspectionOf(server, start) {
|
|
588
|
+
const serverInspects = server?.inspectsUrl === true;
|
|
589
|
+
const middlewareInspects = start?.inspectsUrl === true;
|
|
590
|
+
if (serverInspects && middlewareInspects) return "both";
|
|
591
|
+
if (serverInspects) return "server_entry";
|
|
592
|
+
if (middlewareInspects) return "request_middleware";
|
|
593
|
+
if (server?.complete === false || start?.complete === false) return "unknown";
|
|
594
|
+
return "none";
|
|
595
|
+
}
|
|
596
|
+
//#endregion
|
|
367
597
|
//#region src/routes.ts
|
|
368
598
|
const ROUTE_MANIFEST_VERSION = 1;
|
|
369
599
|
const GENERATOR = `${name}@${version}`;
|
|
@@ -638,13 +868,17 @@ function createRouteManifestPlugin(inputs) {
|
|
|
638
868
|
logWarning("Not publishing a route manifest: the route tree has no routes.");
|
|
639
869
|
return;
|
|
640
870
|
}
|
|
641
|
-
const
|
|
871
|
+
const serverFile = await resolveEntryFile(sourceDir, stringOption(subOptions(tanstackStart, "server"), "entry", "server"));
|
|
872
|
+
const startFile = await resolveEntryFile(sourceDir, stringOption(subOptions(tanstackStart, "start"), "entry", "start"));
|
|
873
|
+
const aliasDir = nodePath.resolve(process.cwd(), "src");
|
|
874
|
+
const [server, start] = await Promise.all([scanEntry(serverFile, { aliasDir }), scanEntry(startFile, { aliasDir })]);
|
|
642
875
|
const payload = serializeRouteManifest({
|
|
643
876
|
version: ROUTE_MANIFEST_VERSION,
|
|
644
877
|
generator: GENERATOR,
|
|
645
878
|
...basepath === "" ? {} : { basepath },
|
|
646
|
-
serverEntry:
|
|
647
|
-
...
|
|
879
|
+
serverEntry: serverFile === void 0 ? "default" : "custom",
|
|
880
|
+
...start === void 0 ? {} : { requestMiddleware: start.requestMiddleware },
|
|
881
|
+
urlInspection: urlInspectionOf(server, start),
|
|
648
882
|
routes
|
|
649
883
|
});
|
|
650
884
|
if (payload === void 0) return;
|
|
@@ -1631,14 +1865,6 @@ function defineConfig(configOrOptions = {}) {
|
|
|
1631
1865
|
plugins: [...internalPlugins, ...options.plugins ?? []]
|
|
1632
1866
|
};
|
|
1633
1867
|
if (options.vite) config = mergeConfig(config, options.vite);
|
|
1634
|
-
if (command === "serve" && isSandbox && bundledDevRequested()) {
|
|
1635
|
-
const viteVersion = await resolveInstalledViteVersion();
|
|
1636
|
-
if (viteVersionSupportsBundledDev(viteVersion)) config = mergeConfig(config, {
|
|
1637
|
-
experimental: { bundledDev: true },
|
|
1638
|
-
plugins: [bundledDevCssUrlShim()]
|
|
1639
|
-
});
|
|
1640
|
-
else logWarning(`Bundled dev requested (${BUNDLED_DEV_ENV_VAR}) but the installed vite (${viteVersion ?? "unresolved"}) is outside the supported 8.1.x range — starting classic dev.`);
|
|
1641
|
-
}
|
|
1642
1868
|
if (isSandbox) {
|
|
1643
1869
|
validateConfig(config);
|
|
1644
1870
|
config = cleanServerConfig(mergeConfig(config, { server: {
|