@tanstack/router-generator 1.166.9 → 1.166.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/dist/cjs/_virtual/_rolldown/runtime.cjs +23 -0
  2. package/dist/cjs/config.cjs +111 -147
  3. package/dist/cjs/config.cjs.map +1 -1
  4. package/dist/cjs/filesystem/physical/getRouteNodes.cjs +224 -303
  5. package/dist/cjs/filesystem/physical/getRouteNodes.cjs.map +1 -1
  6. package/dist/cjs/filesystem/physical/rootPathId.cjs +5 -4
  7. package/dist/cjs/filesystem/physical/rootPathId.cjs.map +1 -1
  8. package/dist/cjs/filesystem/virtual/config.cjs +32 -30
  9. package/dist/cjs/filesystem/virtual/config.cjs.map +1 -1
  10. package/dist/cjs/filesystem/virtual/getRouteNodes.cjs +164 -209
  11. package/dist/cjs/filesystem/virtual/getRouteNodes.cjs.map +1 -1
  12. package/dist/cjs/filesystem/virtual/loadConfigFile.cjs +9 -8
  13. package/dist/cjs/filesystem/virtual/loadConfigFile.cjs.map +1 -1
  14. package/dist/cjs/generator.cjs +766 -1106
  15. package/dist/cjs/generator.cjs.map +1 -1
  16. package/dist/cjs/index.cjs +32 -34
  17. package/dist/cjs/logger.cjs +28 -34
  18. package/dist/cjs/logger.cjs.map +1 -1
  19. package/dist/cjs/template.cjs +144 -151
  20. package/dist/cjs/template.cjs.map +1 -1
  21. package/dist/cjs/transform/transform.cjs +287 -426
  22. package/dist/cjs/transform/transform.cjs.map +1 -1
  23. package/dist/cjs/transform/utils.cjs +31 -33
  24. package/dist/cjs/transform/utils.cjs.map +1 -1
  25. package/dist/cjs/utils.cjs +534 -544
  26. package/dist/cjs/utils.cjs.map +1 -1
  27. package/dist/cjs/validate-route-params.cjs +66 -51
  28. package/dist/cjs/validate-route-params.cjs.map +1 -1
  29. package/dist/esm/config.js +106 -147
  30. package/dist/esm/config.js.map +1 -1
  31. package/dist/esm/filesystem/physical/getRouteNodes.js +220 -286
  32. package/dist/esm/filesystem/physical/getRouteNodes.js.map +1 -1
  33. package/dist/esm/filesystem/physical/rootPathId.js +6 -5
  34. package/dist/esm/filesystem/physical/rootPathId.js.map +1 -1
  35. package/dist/esm/filesystem/virtual/config.js +31 -30
  36. package/dist/esm/filesystem/virtual/config.js.map +1 -1
  37. package/dist/esm/filesystem/virtual/getRouteNodes.js +161 -208
  38. package/dist/esm/filesystem/virtual/getRouteNodes.js.map +1 -1
  39. package/dist/esm/filesystem/virtual/loadConfigFile.js +7 -7
  40. package/dist/esm/filesystem/virtual/loadConfigFile.js.map +1 -1
  41. package/dist/esm/generator.js +756 -1083
  42. package/dist/esm/generator.js.map +1 -1
  43. package/dist/esm/index.js +4 -31
  44. package/dist/esm/logger.js +29 -35
  45. package/dist/esm/logger.js.map +1 -1
  46. package/dist/esm/template.js +144 -152
  47. package/dist/esm/template.js.map +1 -1
  48. package/dist/esm/transform/transform.js +285 -425
  49. package/dist/esm/transform/transform.js.map +1 -1
  50. package/dist/esm/transform/utils.js +31 -33
  51. package/dist/esm/transform/utils.js.map +1 -1
  52. package/dist/esm/utils.js +529 -564
  53. package/dist/esm/utils.js.map +1 -1
  54. package/dist/esm/validate-route-params.js +67 -52
  55. package/dist/esm/validate-route-params.js.map +1 -1
  56. package/package.json +5 -5
  57. package/dist/cjs/index.cjs.map +0 -1
  58. package/dist/esm/index.js.map +0 -1
@@ -1,589 +1,418 @@
1
- import path from "node:path";
2
- import * as fsp from "node:fs/promises";
3
- import { existsSync, mkdirSync } from "node:fs";
4
- import crypto from "node:crypto";
5
- import { rootRouteId } from "@tanstack/router-core";
6
1
  import { logging } from "./logger.js";
7
- import { getRouteNodes as getRouteNodes$1, isVirtualConfigFile } from "./filesystem/physical/getRouteNodes.js";
8
- import { getRouteNodes } from "./filesystem/virtual/getRouteNodes.js";
9
2
  import { rootPathId } from "./filesystem/physical/rootPathId.js";
10
- import { createTokenRegex, multiSortBy, RoutePrefixMap, format, getImportForRouteNode, isRouteNodeValidForAugmentation, buildRouteTreeConfig, findParent, replaceBackslash, removeExt, createRouteNodesByFullPath, createRouteNodesByTo, createRouteNodesById, getResolvedRouteNodeVariableName, buildFileRoutesByPathInterface, checkRouteFullPathUniqueness, mergeImportDeclarations, buildImportString, checkFileExists, hasParentRoute, determineNodePath, trimPathLeft, isSegmentPathless, removeGroups, removeUnderscoresWithEscape, removeLayoutSegmentsWithEscape, removeTrailingSlash, removeLastSegmentFromPath, getImportPath } from "./utils.js";
11
- import { getTargetTemplate, fillTemplate } from "./template.js";
3
+ import { RoutePrefixMap, buildFileRoutesByPathInterface, buildImportString, buildRouteTreeConfig, checkFileExists, checkRouteFullPathUniqueness, createRouteNodesByFullPath, createRouteNodesById, createRouteNodesByTo, createTokenRegex, determineNodePath, findParent, format, getImportForRouteNode, getImportPath, getResolvedRouteNodeVariableName, hasParentRoute, isRouteNodeValidForAugmentation, isSegmentPathless, mergeImportDeclarations, multiSortBy, removeExt, removeGroups, removeLastSegmentFromPath, removeLayoutSegmentsWithEscape, removeTrailingSlash, removeUnderscoresWithEscape, replaceBackslash, trimPathLeft } from "./utils.js";
4
+ import { getRouteNodes } from "./filesystem/virtual/getRouteNodes.js";
5
+ import { getRouteNodes as getRouteNodes$1, isVirtualConfigFile } from "./filesystem/physical/getRouteNodes.js";
6
+ import { fillTemplate, getTargetTemplate } from "./template.js";
12
7
  import { transform } from "./transform/transform.js";
13
8
  import { validateRouteParams } from "./validate-route-params.js";
14
- const DefaultFileSystem = {
15
- stat: async (filePath) => {
16
- const res = await fsp.stat(filePath, { bigint: true });
17
- return {
18
- mtimeMs: res.mtimeMs,
19
- mode: Number(res.mode),
20
- uid: Number(res.uid),
21
- gid: Number(res.gid)
22
- };
23
- },
24
- rename: (oldPath, newPath) => fsp.rename(oldPath, newPath),
25
- writeFile: (filePath, content) => fsp.writeFile(filePath, content),
26
- readFile: async (filePath) => {
27
- try {
28
- const fileHandle = await fsp.open(filePath, "r");
29
- const stat = await fileHandle.stat({ bigint: true });
30
- const fileContent = (await fileHandle.readFile()).toString();
31
- await fileHandle.close();
32
- return { stat, fileContent };
33
- } catch (e) {
34
- if ("code" in e) {
35
- if (e.code === "ENOENT") {
36
- return "file-not-existing";
37
- }
38
- }
39
- throw e;
40
- }
41
- },
42
- chmod: (filePath, mode) => fsp.chmod(filePath, mode),
43
- chown: (filePath, uid, gid) => fsp.chown(filePath, uid, gid)
9
+ import path from "node:path";
10
+ import { existsSync, mkdirSync } from "node:fs";
11
+ import * as fsp from "node:fs/promises";
12
+ import crypto from "node:crypto";
13
+ import { rootRouteId } from "@tanstack/router-core";
14
+ //#region src/generator.ts
15
+ var DefaultFileSystem = {
16
+ stat: async (filePath) => {
17
+ const res = await fsp.stat(filePath, { bigint: true });
18
+ return {
19
+ mtimeMs: res.mtimeMs,
20
+ mode: Number(res.mode),
21
+ uid: Number(res.uid),
22
+ gid: Number(res.gid)
23
+ };
24
+ },
25
+ rename: (oldPath, newPath) => fsp.rename(oldPath, newPath),
26
+ writeFile: (filePath, content) => fsp.writeFile(filePath, content),
27
+ readFile: async (filePath) => {
28
+ try {
29
+ const fileHandle = await fsp.open(filePath, "r");
30
+ const stat = await fileHandle.stat({ bigint: true });
31
+ const fileContent = (await fileHandle.readFile()).toString();
32
+ await fileHandle.close();
33
+ return {
34
+ stat,
35
+ fileContent
36
+ };
37
+ } catch (e) {
38
+ if ("code" in e) {
39
+ if (e.code === "ENOENT") return "file-not-existing";
40
+ }
41
+ throw e;
42
+ }
43
+ },
44
+ chmod: (filePath, mode) => fsp.chmod(filePath, mode),
45
+ chown: (filePath, uid, gid) => fsp.chown(filePath, uid, gid)
44
46
  };
45
47
  function rerun(opts) {
46
- const { event, ...rest } = opts;
47
- return { rerun: true, event: event ?? { type: "rerun" }, ...rest };
48
+ const { event, ...rest } = opts;
49
+ return {
50
+ rerun: true,
51
+ event: event ?? { type: "rerun" },
52
+ ...rest
53
+ };
48
54
  }
49
55
  function isRerun(result) {
50
- return typeof result === "object" && result !== null && "rerun" in result && result.rerun === true;
56
+ return typeof result === "object" && result !== null && "rerun" in result && result.rerun === true;
51
57
  }
52
- const _Generator = class _Generator {
53
- constructor(opts) {
54
- this.routeNodeCache = /* @__PURE__ */ new Map();
55
- this.routeNodeShadowCache = /* @__PURE__ */ new Map();
56
- this.fileEventQueue = [];
57
- this.plugins = [];
58
- this.physicalDirectories = [];
59
- this.config = opts.config;
60
- this.logger = logging({ disabled: this.config.disableLogging });
61
- this.root = opts.root;
62
- this.fs = opts.fs || DefaultFileSystem;
63
- this.generatedRouteTreePath = this.getGeneratedRouteTreePath();
64
- this.targetTemplate = getTargetTemplate(this.config);
65
- this.routesDirectoryPath = this.getRoutesDirectoryPath();
66
- this.plugins.push(...opts.config.plugins || []);
67
- this.indexTokenFilenameRegex = createTokenRegex(this.config.indexToken, {
68
- type: "filename"
69
- });
70
- this.routeTokenFilenameRegex = createTokenRegex(this.config.routeToken, {
71
- type: "filename"
72
- });
73
- this.indexTokenSegmentRegex = createTokenRegex(this.config.indexToken, {
74
- type: "segment"
75
- });
76
- this.routeTokenSegmentRegex = createTokenRegex(this.config.routeToken, {
77
- type: "segment"
78
- });
79
- for (const plugin of this.plugins) {
80
- plugin.init?.({ generator: this });
81
- }
82
- }
83
- getGeneratedRouteTreePath() {
84
- const generatedRouteTreePath = path.isAbsolute(
85
- this.config.generatedRouteTree
86
- ) ? this.config.generatedRouteTree : path.resolve(this.root, this.config.generatedRouteTree);
87
- const generatedRouteTreeDir = path.dirname(generatedRouteTreePath);
88
- if (!existsSync(generatedRouteTreeDir)) {
89
- mkdirSync(generatedRouteTreeDir, { recursive: true });
90
- }
91
- return generatedRouteTreePath;
92
- }
93
- getRoutesDirectoryPath() {
94
- return path.isAbsolute(this.config.routesDirectory) ? this.config.routesDirectory : path.resolve(this.root, this.config.routesDirectory);
95
- }
96
- getRoutesByFileMap() {
97
- return new Map(
98
- [...this.routeNodeCache.entries()].map(([filePath, cacheEntry]) => [
99
- filePath,
100
- { routePath: cacheEntry.routeId }
101
- ])
102
- );
103
- }
104
- async run(event) {
105
- if (event && event.type !== "rerun" && !this.isFileRelevantForRouteTreeGeneration(event.path)) {
106
- return;
107
- }
108
- this.fileEventQueue.push(event ?? { type: "rerun" });
109
- if (this.runPromise) {
110
- return this.runPromise;
111
- }
112
- this.runPromise = (async () => {
113
- do {
114
- const tempQueue = this.fileEventQueue;
115
- this.fileEventQueue = [];
116
- const remainingEvents = (await Promise.all(
117
- tempQueue.map(async (e) => {
118
- if (e.type === "update") {
119
- let cacheEntry;
120
- if (e.path === this.generatedRouteTreePath) {
121
- cacheEntry = this.routeTreeFileCache;
122
- } else {
123
- cacheEntry = this.routeNodeCache.get(e.path);
124
- }
125
- const change = await this.didFileChangeComparedToCache(
126
- { path: e.path },
127
- cacheEntry
128
- );
129
- if (change.result === false) {
130
- return null;
131
- }
132
- }
133
- return e;
134
- })
135
- )).filter((e) => e !== null);
136
- if (remainingEvents.length === 0) {
137
- break;
138
- }
139
- try {
140
- await this.generatorInternal();
141
- } catch (err) {
142
- const errArray = !Array.isArray(err) ? [err] : err;
143
- const recoverableErrors = errArray.filter((e) => isRerun(e));
144
- if (recoverableErrors.length === errArray.length) {
145
- this.fileEventQueue.push(...recoverableErrors.map((e) => e.event));
146
- recoverableErrors.forEach((e) => {
147
- if (e.msg) {
148
- this.logger.info(e.msg);
149
- }
150
- });
151
- } else {
152
- const unrecoverableErrors = errArray.filter((e) => !isRerun(e));
153
- this.runPromise = void 0;
154
- throw new Error(
155
- unrecoverableErrors.map((e) => e.message).join()
156
- );
157
- }
158
- }
159
- } while (this.fileEventQueue.length);
160
- this.runPromise = void 0;
161
- })();
162
- return this.runPromise;
163
- }
164
- async generatorInternal() {
165
- let writeRouteTreeFile = false;
166
- let getRouteNodesResult;
167
- if (this.config.virtualRouteConfig) {
168
- getRouteNodesResult = await getRouteNodes(this.config, this.root, {
169
- indexTokenSegmentRegex: this.indexTokenSegmentRegex,
170
- routeTokenSegmentRegex: this.routeTokenSegmentRegex
171
- });
172
- } else {
173
- getRouteNodesResult = await getRouteNodes$1(
174
- this.config,
175
- this.root,
176
- {
177
- indexTokenSegmentRegex: this.indexTokenSegmentRegex,
178
- routeTokenSegmentRegex: this.routeTokenSegmentRegex
179
- }
180
- );
181
- }
182
- const {
183
- rootRouteNode,
184
- routeNodes: beforeRouteNodes,
185
- physicalDirectories
186
- } = getRouteNodesResult;
187
- if (rootRouteNode === void 0) {
188
- let errorMessage = `rootRouteNode must not be undefined. Make sure you've added your root route into the route-tree.`;
189
- if (!this.config.virtualRouteConfig) {
190
- errorMessage += `
191
- Make sure that you add a "${rootPathId}.${this.config.disableTypes ? "js" : "tsx"}" file to your routes directory.
192
- Add the file in: "${this.config.routesDirectory}/${rootPathId}.${this.config.disableTypes ? "js" : "tsx"}"`;
193
- }
194
- throw new Error(errorMessage);
195
- }
196
- this.physicalDirectories = physicalDirectories;
197
- await this.handleRootNode(rootRouteNode);
198
- const preRouteNodes = multiSortBy(beforeRouteNodes, [
199
- (d) => d.routePath === "/" ? -1 : 1,
200
- (d) => d.routePath?.split("/").length,
201
- (d) => d.filePath.match(this.indexTokenFilenameRegex) ? 1 : -1,
202
- (d) => d.filePath.match(_Generator.componentPieceRegex) ? 1 : -1,
203
- (d) => d.filePath.match(this.routeTokenFilenameRegex) ? -1 : 1,
204
- (d) => d.routePath?.endsWith("/") ? -1 : 1,
205
- (d) => d.routePath
206
- ]).filter((d) => {
207
- if (d.routePath === `/${rootPathId}`) {
208
- return [
209
- "component",
210
- "errorComponent",
211
- "notFoundComponent",
212
- "pendingComponent",
213
- "loader",
214
- "lazy"
215
- ].includes(d._fsRouteType);
216
- }
217
- return true;
218
- });
219
- const routeFileAllResult = await Promise.allSettled(
220
- preRouteNodes.filter((n) => !n.isVirtualParentRoute && !n.isVirtual).map((n) => this.processRouteNodeFile(n))
221
- );
222
- const rejections = routeFileAllResult.filter(
223
- (result) => result.status === "rejected"
224
- );
225
- if (rejections.length > 0) {
226
- throw rejections.map((e) => e.reason);
227
- }
228
- const routeFileResult = routeFileAllResult.flatMap((result) => {
229
- if (result.status === "fulfilled" && result.value !== null) {
230
- if (result.value.shouldWriteTree) {
231
- writeRouteTreeFile = true;
232
- }
233
- return result.value.node;
234
- }
235
- return [];
236
- });
237
- routeFileResult.forEach((r) => r.children = void 0);
238
- const acc = {
239
- routeTree: [],
240
- routeNodes: [],
241
- routePiecesByPath: {},
242
- routeNodesByPath: /* @__PURE__ */ new Map()
243
- };
244
- const prefixMap = new RoutePrefixMap(routeFileResult);
245
- for (const node of routeFileResult) {
246
- _Generator.handleNode(node, acc, prefixMap, this.config);
247
- }
248
- this.crawlingResult = { rootRouteNode, routeFileResult, acc };
249
- if (!this.routeTreeFileCache) {
250
- const routeTreeFile = await this.fs.readFile(this.generatedRouteTreePath);
251
- if (routeTreeFile !== "file-not-existing") {
252
- this.routeTreeFileCache = {
253
- fileContent: routeTreeFile.fileContent,
254
- mtimeMs: routeTreeFile.stat.mtimeMs
255
- };
256
- }
257
- writeRouteTreeFile = true;
258
- } else {
259
- const routeTreeFileChange = await this.didFileChangeComparedToCache(
260
- { path: this.generatedRouteTreePath },
261
- this.routeTreeFileCache
262
- );
263
- if (routeTreeFileChange.result !== false) {
264
- writeRouteTreeFile = "force";
265
- if (routeTreeFileChange.result === true) {
266
- const routeTreeFile = await this.fs.readFile(
267
- this.generatedRouteTreePath
268
- );
269
- if (routeTreeFile !== "file-not-existing") {
270
- this.routeTreeFileCache = {
271
- fileContent: routeTreeFile.fileContent,
272
- mtimeMs: routeTreeFile.stat.mtimeMs
273
- };
274
- }
275
- }
276
- }
277
- }
278
- if (!writeRouteTreeFile) {
279
- if (this.routeNodeCache.size !== this.routeNodeShadowCache.size) {
280
- writeRouteTreeFile = true;
281
- } else {
282
- for (const fullPath of this.routeNodeCache.keys()) {
283
- if (!this.routeNodeShadowCache.has(fullPath)) {
284
- writeRouteTreeFile = true;
285
- break;
286
- }
287
- }
288
- }
289
- }
290
- if (!writeRouteTreeFile) {
291
- this.swapCaches();
292
- return;
293
- }
294
- const buildResult = this.buildRouteTree({
295
- rootRouteNode,
296
- acc,
297
- routeFileResult
298
- });
299
- let routeTreeContent = buildResult.routeTreeContent;
300
- routeTreeContent = this.config.enableRouteTreeFormatting ? await format(routeTreeContent, this.config) : routeTreeContent;
301
- let newMtimeMs;
302
- if (this.routeTreeFileCache) {
303
- if (writeRouteTreeFile !== "force" && this.routeTreeFileCache.fileContent === routeTreeContent) ;
304
- else {
305
- const newRouteTreeFileStat = await this.safeFileWrite({
306
- filePath: this.generatedRouteTreePath,
307
- newContent: routeTreeContent,
308
- strategy: {
309
- type: "mtime",
310
- expectedMtimeMs: this.routeTreeFileCache.mtimeMs
311
- }
312
- });
313
- newMtimeMs = newRouteTreeFileStat.mtimeMs;
314
- }
315
- } else {
316
- const newRouteTreeFileStat = await this.safeFileWrite({
317
- filePath: this.generatedRouteTreePath,
318
- newContent: routeTreeContent,
319
- strategy: {
320
- type: "new-file"
321
- }
322
- });
323
- newMtimeMs = newRouteTreeFileStat.mtimeMs;
324
- }
325
- if (newMtimeMs !== void 0) {
326
- this.routeTreeFileCache = {
327
- fileContent: routeTreeContent,
328
- mtimeMs: newMtimeMs
329
- };
330
- }
331
- this.plugins.map((plugin) => {
332
- return plugin.onRouteTreeChanged?.({
333
- routeTree: buildResult.routeTree,
334
- routeNodes: buildResult.routeNodes,
335
- acc,
336
- rootRouteNode
337
- });
338
- });
339
- this.swapCaches();
340
- }
341
- swapCaches() {
342
- this.routeNodeCache = this.routeNodeShadowCache;
343
- this.routeNodeShadowCache = /* @__PURE__ */ new Map();
344
- }
345
- buildRouteTree(opts) {
346
- const config = { ...this.config, ...opts.config || {} };
347
- const { rootRouteNode, acc } = opts;
348
- const indexTokenSegmentRegex = config.indexToken === this.config.indexToken ? this.indexTokenSegmentRegex : createTokenRegex(config.indexToken, { type: "segment" });
349
- const sortedRouteNodes = multiSortBy(acc.routeNodes, [
350
- (d) => d.routePath?.includes(`/${rootPathId}`) ? -1 : 1,
351
- (d) => d.routePath?.split("/").length,
352
- (d) => {
353
- const segments = d.routePath?.split("/").filter(Boolean) ?? [];
354
- const last = segments[segments.length - 1] ?? "";
355
- return indexTokenSegmentRegex.test(last) ? -1 : 1;
356
- },
357
- (d) => d
358
- ]);
359
- const routeImports = [];
360
- const virtualRouteNodes = [];
361
- for (const node of sortedRouteNodes) {
362
- if (node.isVirtual) {
363
- virtualRouteNodes.push(
364
- `const ${node.variableName}RouteImport = createFileRoute('${node.routePath}')()`
365
- );
366
- } else {
367
- routeImports.push(
368
- getImportForRouteNode(
369
- node,
370
- config,
371
- this.generatedRouteTreePath,
372
- this.root
373
- )
374
- );
375
- }
376
- }
377
- const imports = [];
378
- if (virtualRouteNodes.length > 0) {
379
- imports.push({
380
- specifiers: [{ imported: "createFileRoute" }],
381
- source: this.targetTemplate.fullPkg
382
- });
383
- }
384
- let hasComponentPieces = false;
385
- let hasLoaderPieces = false;
386
- for (const node of sortedRouteNodes) {
387
- const pieces = acc.routePiecesByPath[node.routePath];
388
- if (pieces) {
389
- if (pieces.component || pieces.errorComponent || pieces.notFoundComponent || pieces.pendingComponent) {
390
- hasComponentPieces = true;
391
- }
392
- if (pieces.loader) {
393
- hasLoaderPieces = true;
394
- }
395
- if (hasComponentPieces && hasLoaderPieces) break;
396
- }
397
- }
398
- if (hasComponentPieces || hasLoaderPieces) {
399
- const runtimeImport = {
400
- specifiers: [],
401
- source: this.targetTemplate.fullPkg
402
- };
403
- if (hasComponentPieces) {
404
- runtimeImport.specifiers.push({ imported: "lazyRouteComponent" });
405
- }
406
- if (hasLoaderPieces) {
407
- runtimeImport.specifiers.push({ imported: "lazyFn" });
408
- }
409
- imports.push(runtimeImport);
410
- }
411
- if (config.verboseFileRoutes === false) {
412
- const typeImport = {
413
- specifiers: [],
414
- source: this.targetTemplate.fullPkg,
415
- importKind: "type"
416
- };
417
- let needsCreateFileRoute = false;
418
- let needsCreateLazyFileRoute = false;
419
- for (const node of sortedRouteNodes) {
420
- if (isRouteNodeValidForAugmentation(node)) {
421
- if (node._fsRouteType !== "lazy") {
422
- needsCreateFileRoute = true;
423
- }
424
- if (acc.routePiecesByPath[node.routePath]?.lazy) {
425
- needsCreateLazyFileRoute = true;
426
- }
427
- }
428
- if (needsCreateFileRoute && needsCreateLazyFileRoute) break;
429
- }
430
- if (needsCreateFileRoute) {
431
- typeImport.specifiers.push({ imported: "CreateFileRoute" });
432
- }
433
- if (needsCreateLazyFileRoute) {
434
- typeImport.specifiers.push({ imported: "CreateLazyFileRoute" });
435
- }
436
- if (typeImport.specifiers.length > 0) {
437
- typeImport.specifiers.push({ imported: "FileRoutesByPath" });
438
- imports.push(typeImport);
439
- }
440
- }
441
- const routeTreeConfig = buildRouteTreeConfig(
442
- acc.routeTree,
443
- config.disableTypes
444
- );
445
- const createUpdateRoutes = sortedRouteNodes.map((node) => {
446
- const pieces = acc.routePiecesByPath[node.routePath];
447
- const loaderNode = pieces?.loader;
448
- const componentNode = pieces?.component;
449
- const errorComponentNode = pieces?.errorComponent;
450
- const notFoundComponentNode = pieces?.notFoundComponent;
451
- const pendingComponentNode = pieces?.pendingComponent;
452
- const lazyComponentNode = pieces?.lazy;
453
- return [
454
- [
455
- `const ${node.variableName}Route = ${node.variableName}RouteImport.update({
58
+ var Generator = class Generator {
59
+ static {
60
+ this.routeGroupPatternRegex = /\(.+\)/;
61
+ }
62
+ static {
63
+ this.componentPieceRegex = /[./](component|errorComponent|notFoundComponent|pendingComponent|loader|lazy)[.]/;
64
+ }
65
+ constructor(opts) {
66
+ this.routeNodeCache = /* @__PURE__ */ new Map();
67
+ this.routeNodeShadowCache = /* @__PURE__ */ new Map();
68
+ this.fileEventQueue = [];
69
+ this.plugins = [];
70
+ this.physicalDirectories = [];
71
+ this.config = opts.config;
72
+ this.logger = logging({ disabled: this.config.disableLogging });
73
+ this.root = opts.root;
74
+ this.fs = opts.fs || DefaultFileSystem;
75
+ this.generatedRouteTreePath = this.getGeneratedRouteTreePath();
76
+ this.targetTemplate = getTargetTemplate(this.config);
77
+ this.routesDirectoryPath = this.getRoutesDirectoryPath();
78
+ this.plugins.push(...opts.config.plugins || []);
79
+ this.indexTokenFilenameRegex = createTokenRegex(this.config.indexToken, { type: "filename" });
80
+ this.routeTokenFilenameRegex = createTokenRegex(this.config.routeToken, { type: "filename" });
81
+ this.indexTokenSegmentRegex = createTokenRegex(this.config.indexToken, { type: "segment" });
82
+ this.routeTokenSegmentRegex = createTokenRegex(this.config.routeToken, { type: "segment" });
83
+ for (const plugin of this.plugins) plugin.init?.({ generator: this });
84
+ }
85
+ getGeneratedRouteTreePath() {
86
+ const generatedRouteTreePath = path.isAbsolute(this.config.generatedRouteTree) ? this.config.generatedRouteTree : path.resolve(this.root, this.config.generatedRouteTree);
87
+ const generatedRouteTreeDir = path.dirname(generatedRouteTreePath);
88
+ if (!existsSync(generatedRouteTreeDir)) mkdirSync(generatedRouteTreeDir, { recursive: true });
89
+ return generatedRouteTreePath;
90
+ }
91
+ getRoutesDirectoryPath() {
92
+ return path.isAbsolute(this.config.routesDirectory) ? this.config.routesDirectory : path.resolve(this.root, this.config.routesDirectory);
93
+ }
94
+ getRoutesByFileMap() {
95
+ return new Map([...this.routeNodeCache.entries()].map(([filePath, cacheEntry]) => [filePath, { routePath: cacheEntry.routeId }]));
96
+ }
97
+ async run(event) {
98
+ if (event && event.type !== "rerun" && !this.isFileRelevantForRouteTreeGeneration(event.path)) return;
99
+ this.fileEventQueue.push(event ?? { type: "rerun" });
100
+ if (this.runPromise) return this.runPromise;
101
+ this.runPromise = (async () => {
102
+ do {
103
+ const tempQueue = this.fileEventQueue;
104
+ this.fileEventQueue = [];
105
+ if ((await Promise.all(tempQueue.map(async (e) => {
106
+ if (e.type === "update") {
107
+ let cacheEntry;
108
+ if (e.path === this.generatedRouteTreePath) cacheEntry = this.routeTreeFileCache;
109
+ else cacheEntry = this.routeNodeCache.get(e.path);
110
+ if ((await this.didFileChangeComparedToCache({ path: e.path }, cacheEntry)).result === false) return null;
111
+ }
112
+ return e;
113
+ }))).filter((e) => e !== null).length === 0) break;
114
+ try {
115
+ await this.generatorInternal();
116
+ } catch (err) {
117
+ const errArray = !Array.isArray(err) ? [err] : err;
118
+ const recoverableErrors = errArray.filter((e) => isRerun(e));
119
+ if (recoverableErrors.length === errArray.length) {
120
+ this.fileEventQueue.push(...recoverableErrors.map((e) => e.event));
121
+ recoverableErrors.forEach((e) => {
122
+ if (e.msg) this.logger.info(e.msg);
123
+ });
124
+ } else {
125
+ const unrecoverableErrors = errArray.filter((e) => !isRerun(e));
126
+ this.runPromise = void 0;
127
+ throw new Error(unrecoverableErrors.map((e) => e.message).join());
128
+ }
129
+ }
130
+ } while (this.fileEventQueue.length);
131
+ this.runPromise = void 0;
132
+ })();
133
+ return this.runPromise;
134
+ }
135
+ async generatorInternal() {
136
+ let writeRouteTreeFile = false;
137
+ let getRouteNodesResult;
138
+ if (this.config.virtualRouteConfig) getRouteNodesResult = await getRouteNodes(this.config, this.root, {
139
+ indexTokenSegmentRegex: this.indexTokenSegmentRegex,
140
+ routeTokenSegmentRegex: this.routeTokenSegmentRegex
141
+ });
142
+ else getRouteNodesResult = await getRouteNodes$1(this.config, this.root, {
143
+ indexTokenSegmentRegex: this.indexTokenSegmentRegex,
144
+ routeTokenSegmentRegex: this.routeTokenSegmentRegex
145
+ });
146
+ const { rootRouteNode, routeNodes: beforeRouteNodes, physicalDirectories } = getRouteNodesResult;
147
+ if (rootRouteNode === void 0) {
148
+ let errorMessage = `rootRouteNode must not be undefined. Make sure you've added your root route into the route-tree.`;
149
+ if (!this.config.virtualRouteConfig) errorMessage += `\nMake sure that you add a "${rootPathId}.${this.config.disableTypes ? "js" : "tsx"}" file to your routes directory.\nAdd the file in: "${this.config.routesDirectory}/${rootPathId}.${this.config.disableTypes ? "js" : "tsx"}"`;
150
+ throw new Error(errorMessage);
151
+ }
152
+ this.physicalDirectories = physicalDirectories;
153
+ await this.handleRootNode(rootRouteNode);
154
+ const preRouteNodes = multiSortBy(beforeRouteNodes, [
155
+ (d) => d.routePath === "/" ? -1 : 1,
156
+ (d) => d.routePath?.split("/").length,
157
+ (d) => d.filePath.match(this.indexTokenFilenameRegex) ? 1 : -1,
158
+ (d) => d.filePath.match(Generator.componentPieceRegex) ? 1 : -1,
159
+ (d) => d.filePath.match(this.routeTokenFilenameRegex) ? -1 : 1,
160
+ (d) => d.routePath?.endsWith("/") ? -1 : 1,
161
+ (d) => d.routePath
162
+ ]).filter((d) => {
163
+ if (d.routePath === `/__root`) return [
164
+ "component",
165
+ "errorComponent",
166
+ "notFoundComponent",
167
+ "pendingComponent",
168
+ "loader",
169
+ "lazy"
170
+ ].includes(d._fsRouteType);
171
+ return true;
172
+ });
173
+ const routeFileAllResult = await Promise.allSettled(preRouteNodes.filter((n) => !n.isVirtualParentRoute && !n.isVirtual).map((n) => this.processRouteNodeFile(n)));
174
+ const rejections = routeFileAllResult.filter((result) => result.status === "rejected");
175
+ if (rejections.length > 0) throw rejections.map((e) => e.reason);
176
+ const routeFileResult = routeFileAllResult.flatMap((result) => {
177
+ if (result.status === "fulfilled" && result.value !== null) {
178
+ if (result.value.shouldWriteTree) writeRouteTreeFile = true;
179
+ return result.value.node;
180
+ }
181
+ return [];
182
+ });
183
+ routeFileResult.forEach((r) => r.children = void 0);
184
+ const acc = {
185
+ routeTree: [],
186
+ routeNodes: [],
187
+ routePiecesByPath: {},
188
+ routeNodesByPath: /* @__PURE__ */ new Map()
189
+ };
190
+ const prefixMap = new RoutePrefixMap(routeFileResult);
191
+ for (const node of routeFileResult) Generator.handleNode(node, acc, prefixMap, this.config);
192
+ this.crawlingResult = {
193
+ rootRouteNode,
194
+ routeFileResult,
195
+ acc
196
+ };
197
+ if (!this.routeTreeFileCache) {
198
+ const routeTreeFile = await this.fs.readFile(this.generatedRouteTreePath);
199
+ if (routeTreeFile !== "file-not-existing") this.routeTreeFileCache = {
200
+ fileContent: routeTreeFile.fileContent,
201
+ mtimeMs: routeTreeFile.stat.mtimeMs
202
+ };
203
+ writeRouteTreeFile = true;
204
+ } else {
205
+ const routeTreeFileChange = await this.didFileChangeComparedToCache({ path: this.generatedRouteTreePath }, this.routeTreeFileCache);
206
+ if (routeTreeFileChange.result !== false) {
207
+ writeRouteTreeFile = "force";
208
+ if (routeTreeFileChange.result === true) {
209
+ const routeTreeFile = await this.fs.readFile(this.generatedRouteTreePath);
210
+ if (routeTreeFile !== "file-not-existing") this.routeTreeFileCache = {
211
+ fileContent: routeTreeFile.fileContent,
212
+ mtimeMs: routeTreeFile.stat.mtimeMs
213
+ };
214
+ }
215
+ }
216
+ }
217
+ if (!writeRouteTreeFile) {
218
+ if (this.routeNodeCache.size !== this.routeNodeShadowCache.size) writeRouteTreeFile = true;
219
+ else for (const fullPath of this.routeNodeCache.keys()) if (!this.routeNodeShadowCache.has(fullPath)) {
220
+ writeRouteTreeFile = true;
221
+ break;
222
+ }
223
+ }
224
+ if (!writeRouteTreeFile) {
225
+ this.swapCaches();
226
+ return;
227
+ }
228
+ const buildResult = this.buildRouteTree({
229
+ rootRouteNode,
230
+ acc,
231
+ routeFileResult
232
+ });
233
+ let routeTreeContent = buildResult.routeTreeContent;
234
+ routeTreeContent = this.config.enableRouteTreeFormatting ? await format(routeTreeContent, this.config) : routeTreeContent;
235
+ let newMtimeMs;
236
+ if (this.routeTreeFileCache) if (writeRouteTreeFile !== "force" && this.routeTreeFileCache.fileContent === routeTreeContent) {} else newMtimeMs = (await this.safeFileWrite({
237
+ filePath: this.generatedRouteTreePath,
238
+ newContent: routeTreeContent,
239
+ strategy: {
240
+ type: "mtime",
241
+ expectedMtimeMs: this.routeTreeFileCache.mtimeMs
242
+ }
243
+ })).mtimeMs;
244
+ else newMtimeMs = (await this.safeFileWrite({
245
+ filePath: this.generatedRouteTreePath,
246
+ newContent: routeTreeContent,
247
+ strategy: { type: "new-file" }
248
+ })).mtimeMs;
249
+ if (newMtimeMs !== void 0) this.routeTreeFileCache = {
250
+ fileContent: routeTreeContent,
251
+ mtimeMs: newMtimeMs
252
+ };
253
+ this.plugins.map((plugin) => {
254
+ return plugin.onRouteTreeChanged?.({
255
+ routeTree: buildResult.routeTree,
256
+ routeNodes: buildResult.routeNodes,
257
+ acc,
258
+ rootRouteNode
259
+ });
260
+ });
261
+ this.swapCaches();
262
+ }
263
+ swapCaches() {
264
+ this.routeNodeCache = this.routeNodeShadowCache;
265
+ this.routeNodeShadowCache = /* @__PURE__ */ new Map();
266
+ }
267
+ buildRouteTree(opts) {
268
+ const config = {
269
+ ...this.config,
270
+ ...opts.config || {}
271
+ };
272
+ const { rootRouteNode, acc } = opts;
273
+ const indexTokenSegmentRegex = config.indexToken === this.config.indexToken ? this.indexTokenSegmentRegex : createTokenRegex(config.indexToken, { type: "segment" });
274
+ const sortedRouteNodes = multiSortBy(acc.routeNodes, [
275
+ (d) => d.routePath?.includes(`/__root`) ? -1 : 1,
276
+ (d) => d.routePath?.split("/").length,
277
+ (d) => {
278
+ const segments = d.routePath?.split("/").filter(Boolean) ?? [];
279
+ const last = segments[segments.length - 1] ?? "";
280
+ return indexTokenSegmentRegex.test(last) ? -1 : 1;
281
+ },
282
+ (d) => d
283
+ ]);
284
+ const routeImports = [];
285
+ const virtualRouteNodes = [];
286
+ for (const node of sortedRouteNodes) if (node.isVirtual) virtualRouteNodes.push(`const ${node.variableName}RouteImport = createFileRoute('${node.routePath}')()`);
287
+ else routeImports.push(getImportForRouteNode(node, config, this.generatedRouteTreePath, this.root));
288
+ const imports = [];
289
+ if (virtualRouteNodes.length > 0) imports.push({
290
+ specifiers: [{ imported: "createFileRoute" }],
291
+ source: this.targetTemplate.fullPkg
292
+ });
293
+ let hasComponentPieces = false;
294
+ let hasLoaderPieces = false;
295
+ for (const node of sortedRouteNodes) {
296
+ const pieces = acc.routePiecesByPath[node.routePath];
297
+ if (pieces) {
298
+ if (pieces.component || pieces.errorComponent || pieces.notFoundComponent || pieces.pendingComponent) hasComponentPieces = true;
299
+ if (pieces.loader) hasLoaderPieces = true;
300
+ if (hasComponentPieces && hasLoaderPieces) break;
301
+ }
302
+ }
303
+ if (hasComponentPieces || hasLoaderPieces) {
304
+ const runtimeImport = {
305
+ specifiers: [],
306
+ source: this.targetTemplate.fullPkg
307
+ };
308
+ if (hasComponentPieces) runtimeImport.specifiers.push({ imported: "lazyRouteComponent" });
309
+ if (hasLoaderPieces) runtimeImport.specifiers.push({ imported: "lazyFn" });
310
+ imports.push(runtimeImport);
311
+ }
312
+ if (config.verboseFileRoutes === false) {
313
+ const typeImport = {
314
+ specifiers: [],
315
+ source: this.targetTemplate.fullPkg,
316
+ importKind: "type"
317
+ };
318
+ let needsCreateFileRoute = false;
319
+ let needsCreateLazyFileRoute = false;
320
+ for (const node of sortedRouteNodes) {
321
+ if (isRouteNodeValidForAugmentation(node)) {
322
+ if (node._fsRouteType !== "lazy") needsCreateFileRoute = true;
323
+ if (acc.routePiecesByPath[node.routePath]?.lazy) needsCreateLazyFileRoute = true;
324
+ }
325
+ if (needsCreateFileRoute && needsCreateLazyFileRoute) break;
326
+ }
327
+ if (needsCreateFileRoute) typeImport.specifiers.push({ imported: "CreateFileRoute" });
328
+ if (needsCreateLazyFileRoute) typeImport.specifiers.push({ imported: "CreateLazyFileRoute" });
329
+ if (typeImport.specifiers.length > 0) {
330
+ typeImport.specifiers.push({ imported: "FileRoutesByPath" });
331
+ imports.push(typeImport);
332
+ }
333
+ }
334
+ const routeTreeConfig = buildRouteTreeConfig(acc.routeTree, config.disableTypes);
335
+ const createUpdateRoutes = sortedRouteNodes.map((node) => {
336
+ const pieces = acc.routePiecesByPath[node.routePath];
337
+ const loaderNode = pieces?.loader;
338
+ const componentNode = pieces?.component;
339
+ const errorComponentNode = pieces?.errorComponent;
340
+ const notFoundComponentNode = pieces?.notFoundComponent;
341
+ const pendingComponentNode = pieces?.pendingComponent;
342
+ const lazyComponentNode = pieces?.lazy;
343
+ return [[
344
+ `const ${node.variableName}Route = ${node.variableName}RouteImport.update({
456
345
  ${[
457
- `id: '${node.path}'`,
458
- !node.isNonPath || node._fsRouteType === "pathless_layout" && node.cleanedPath ? `path: '${node.cleanedPath}'` : void 0,
459
- `getParentRoute: () => ${findParent(node)}`
460
- ].filter(Boolean).join(",")}
346
+ `id: '${node.path}'`,
347
+ !node.isNonPath || node._fsRouteType === "pathless_layout" && node.cleanedPath ? `path: '${node.cleanedPath}'` : void 0,
348
+ `getParentRoute: () => ${findParent(node)}`
349
+ ].filter(Boolean).join(",")}
461
350
  }${config.disableTypes ? "" : "as any"})`,
462
- loaderNode ? `.updateLoader({ loader: lazyFn(() => import('./${replaceBackslash(
463
- removeExt(
464
- path.relative(
465
- path.dirname(config.generatedRouteTree),
466
- path.resolve(config.routesDirectory, loaderNode.filePath)
467
- ),
468
- config.addExtensions
469
- )
470
- )}'), 'loader') })` : "",
471
- componentNode || errorComponentNode || notFoundComponentNode || pendingComponentNode ? `.update({
351
+ loaderNode ? `.updateLoader({ loader: lazyFn(() => import('./${replaceBackslash(removeExt(path.relative(path.dirname(config.generatedRouteTree), path.resolve(config.routesDirectory, loaderNode.filePath)), config.addExtensions))}'), 'loader') })` : "",
352
+ componentNode || errorComponentNode || notFoundComponentNode || pendingComponentNode ? `.update({
472
353
  ${[
473
- ["component", componentNode],
474
- ["errorComponent", errorComponentNode],
475
- ["notFoundComponent", notFoundComponentNode],
476
- ["pendingComponent", pendingComponentNode]
477
- ].filter((d) => d[1]).map((d) => {
478
- const isVueFile = d[1].filePath.endsWith(".vue");
479
- const exportName = isVueFile ? "default" : d[0];
480
- const importPath = replaceBackslash(
481
- isVueFile ? path.relative(
482
- path.dirname(config.generatedRouteTree),
483
- path.resolve(
484
- config.routesDirectory,
485
- d[1].filePath
486
- )
487
- ) : removeExt(
488
- path.relative(
489
- path.dirname(config.generatedRouteTree),
490
- path.resolve(
491
- config.routesDirectory,
492
- d[1].filePath
493
- )
494
- ),
495
- config.addExtensions
496
- )
497
- );
498
- return `${d[0]}: lazyRouteComponent(() => import('./${importPath}'), '${exportName}')`;
499
- }).join("\n,")}
354
+ ["component", componentNode],
355
+ ["errorComponent", errorComponentNode],
356
+ ["notFoundComponent", notFoundComponentNode],
357
+ ["pendingComponent", pendingComponentNode]
358
+ ].filter((d) => d[1]).map((d) => {
359
+ const isVueFile = d[1].filePath.endsWith(".vue");
360
+ const exportName = isVueFile ? "default" : d[0];
361
+ const importPath = replaceBackslash(isVueFile ? path.relative(path.dirname(config.generatedRouteTree), path.resolve(config.routesDirectory, d[1].filePath)) : removeExt(path.relative(path.dirname(config.generatedRouteTree), path.resolve(config.routesDirectory, d[1].filePath)), config.addExtensions));
362
+ return `${d[0]}: lazyRouteComponent(() => import('./${importPath}'), '${exportName}')`;
363
+ }).join("\n,")}
500
364
  })` : "",
501
- lazyComponentNode ? (() => {
502
- const isVueFile = lazyComponentNode.filePath.endsWith(".vue");
503
- const exportAccessor = isVueFile ? "d.default" : "d.Route";
504
- const importPath = replaceBackslash(
505
- isVueFile ? path.relative(
506
- path.dirname(config.generatedRouteTree),
507
- path.resolve(
508
- config.routesDirectory,
509
- lazyComponentNode.filePath
510
- )
511
- ) : removeExt(
512
- path.relative(
513
- path.dirname(config.generatedRouteTree),
514
- path.resolve(
515
- config.routesDirectory,
516
- lazyComponentNode.filePath
517
- )
518
- ),
519
- config.addExtensions
520
- )
521
- );
522
- return `.lazy(() => import('./${importPath}').then((d) => ${exportAccessor}))`;
523
- })() : ""
524
- ].join("")
525
- ].join("\n\n");
526
- });
527
- const rootRoutePath = `/${rootPathId}`;
528
- const rootPieces = acc.routePiecesByPath[rootRoutePath];
529
- const rootComponentNode = rootPieces?.component;
530
- const rootErrorComponentNode = rootPieces?.errorComponent;
531
- const rootNotFoundComponentNode = rootPieces?.notFoundComponent;
532
- const rootPendingComponentNode = rootPieces?.pendingComponent;
533
- let rootRouteUpdate = "";
534
- if (rootComponentNode || rootErrorComponentNode || rootNotFoundComponentNode || rootPendingComponentNode) {
535
- rootRouteUpdate = `const rootRouteWithChildren = rootRouteImport${rootComponentNode || rootErrorComponentNode || rootNotFoundComponentNode || rootPendingComponentNode ? `.update({
365
+ lazyComponentNode ? (() => {
366
+ const isVueFile = lazyComponentNode.filePath.endsWith(".vue");
367
+ const exportAccessor = isVueFile ? "d.default" : "d.Route";
368
+ return `.lazy(() => import('./${replaceBackslash(isVueFile ? path.relative(path.dirname(config.generatedRouteTree), path.resolve(config.routesDirectory, lazyComponentNode.filePath)) : removeExt(path.relative(path.dirname(config.generatedRouteTree), path.resolve(config.routesDirectory, lazyComponentNode.filePath)), config.addExtensions))}').then((d) => ${exportAccessor}))`;
369
+ })() : ""
370
+ ].join("")].join("\n\n");
371
+ });
372
+ const rootRoutePath = `/${rootPathId}`;
373
+ const rootPieces = acc.routePiecesByPath[rootRoutePath];
374
+ const rootComponentNode = rootPieces?.component;
375
+ const rootErrorComponentNode = rootPieces?.errorComponent;
376
+ const rootNotFoundComponentNode = rootPieces?.notFoundComponent;
377
+ const rootPendingComponentNode = rootPieces?.pendingComponent;
378
+ let rootRouteUpdate = "";
379
+ if (rootComponentNode || rootErrorComponentNode || rootNotFoundComponentNode || rootPendingComponentNode) rootRouteUpdate = `const rootRouteWithChildren = rootRouteImport${rootComponentNode || rootErrorComponentNode || rootNotFoundComponentNode || rootPendingComponentNode ? `.update({
536
380
  ${[
537
- ["component", rootComponentNode],
538
- ["errorComponent", rootErrorComponentNode],
539
- ["notFoundComponent", rootNotFoundComponentNode],
540
- ["pendingComponent", rootPendingComponentNode]
541
- ].filter((d) => d[1]).map((d) => {
542
- const isVueFile = d[1].filePath.endsWith(".vue");
543
- const exportName = isVueFile ? "default" : d[0];
544
- const importPath = replaceBackslash(
545
- isVueFile ? path.relative(
546
- path.dirname(config.generatedRouteTree),
547
- path.resolve(config.routesDirectory, d[1].filePath)
548
- ) : removeExt(
549
- path.relative(
550
- path.dirname(config.generatedRouteTree),
551
- path.resolve(
552
- config.routesDirectory,
553
- d[1].filePath
554
- )
555
- ),
556
- config.addExtensions
557
- )
558
- );
559
- return `${d[0]}: lazyRouteComponent(() => import('./${importPath}'), '${exportName}')`;
560
- }).join("\n,")}
381
+ ["component", rootComponentNode],
382
+ ["errorComponent", rootErrorComponentNode],
383
+ ["notFoundComponent", rootNotFoundComponentNode],
384
+ ["pendingComponent", rootPendingComponentNode]
385
+ ].filter((d) => d[1]).map((d) => {
386
+ const isVueFile = d[1].filePath.endsWith(".vue");
387
+ const exportName = isVueFile ? "default" : d[0];
388
+ const importPath = replaceBackslash(isVueFile ? path.relative(path.dirname(config.generatedRouteTree), path.resolve(config.routesDirectory, d[1].filePath)) : removeExt(path.relative(path.dirname(config.generatedRouteTree), path.resolve(config.routesDirectory, d[1].filePath)), config.addExtensions));
389
+ return `${d[0]}: lazyRouteComponent(() => import('./${importPath}'), '${exportName}')`;
390
+ }).join("\n,")}
561
391
  })` : ""}._addFileChildren(rootRouteChildren)${config.disableTypes ? "" : `._addFileTypes<FileRouteTypes>()`}`;
562
- }
563
- let fileRoutesByPathInterface = "";
564
- let fileRoutesByFullPath = "";
565
- if (!config.disableTypes) {
566
- const routeNodesByFullPath = createRouteNodesByFullPath(acc.routeNodes);
567
- const routeNodesByTo = createRouteNodesByTo(acc.routeNodes);
568
- const routeNodesById = createRouteNodesById(acc.routeNodes);
569
- fileRoutesByFullPath = [
570
- `export interface FileRoutesByFullPath {
392
+ let fileRoutesByPathInterface = "";
393
+ let fileRoutesByFullPath = "";
394
+ if (!config.disableTypes) {
395
+ const routeNodesByFullPath = createRouteNodesByFullPath(acc.routeNodes);
396
+ const routeNodesByTo = createRouteNodesByTo(acc.routeNodes);
397
+ const routeNodesById = createRouteNodesById(acc.routeNodes);
398
+ fileRoutesByFullPath = [
399
+ `export interface FileRoutesByFullPath {
571
400
  ${[...routeNodesByFullPath.entries()].filter(([fullPath]) => fullPath).map(([fullPath, routeNode]) => {
572
- return `'${fullPath}': typeof ${getResolvedRouteNodeVariableName(routeNode)}`;
573
- })}
401
+ return `'${fullPath}': typeof ${getResolvedRouteNodeVariableName(routeNode)}`;
402
+ })}
574
403
  }`,
575
- `export interface FileRoutesByTo {
404
+ `export interface FileRoutesByTo {
576
405
  ${[...routeNodesByTo.entries()].filter(([to]) => to).map(([to, routeNode]) => {
577
- return `'${to}': typeof ${getResolvedRouteNodeVariableName(routeNode)}`;
578
- })}
406
+ return `'${to}': typeof ${getResolvedRouteNodeVariableName(routeNode)}`;
407
+ })}
579
408
  }`,
580
- `export interface FileRoutesById {
409
+ `export interface FileRoutesById {
581
410
  '${rootRouteId}': typeof rootRouteImport,
582
411
  ${[...routeNodesById.entries()].map(([id, routeNode]) => {
583
- return `'${id}': typeof ${getResolvedRouteNodeVariableName(routeNode)}`;
584
- })}
412
+ return `'${id}': typeof ${getResolvedRouteNodeVariableName(routeNode)}`;
413
+ })}
585
414
  }`,
586
- `export interface FileRouteTypes {
415
+ `export interface FileRouteTypes {
587
416
  fileRoutesByFullPath: FileRoutesByFullPath
588
417
  fullPaths: ${acc.routeNodes.length > 0 ? [...routeNodesByFullPath.keys()].filter((fullPath) => fullPath).map((fullPath) => `'${fullPath}'`).join("|") : "never"}
589
418
  fileRoutesByTo: FileRoutesByTo
@@ -591,534 +420,378 @@ to: ${acc.routeNodes.length > 0 ? [...routeNodesByTo.keys()].filter((to) => to).
591
420
  id: ${[`'${rootRouteId}'`, ...[...routeNodesById.keys()].map((id) => `'${id}'`)].join("|")}
592
421
  fileRoutesById: FileRoutesById
593
422
  }`,
594
- `export interface RootRouteChildren {
423
+ `export interface RootRouteChildren {
595
424
  ${acc.routeTree.map((child) => `${child.variableName}Route: typeof ${getResolvedRouteNodeVariableName(child)}`).join(",")}
596
425
  }`
597
- ].join("\n");
598
- fileRoutesByPathInterface = buildFileRoutesByPathInterface({
599
- module: this.targetTemplate.fullPkg,
600
- interfaceName: "FileRoutesByPath",
601
- routeNodes: sortedRouteNodes
602
- });
603
- }
604
- const routeTree = [
605
- `const rootRouteChildren${config.disableTypes ? "" : `: RootRouteChildren`} = {
606
- ${acc.routeTree.map(
607
- (child) => `${child.variableName}Route: ${getResolvedRouteNodeVariableName(child)}`
608
- ).join(",")}
609
- }`,
610
- rootRouteUpdate ? rootRouteUpdate.replace(
611
- "const rootRouteWithChildren = ",
612
- "export const routeTree = "
613
- ) : `export const routeTree = rootRouteImport._addFileChildren(rootRouteChildren)${config.disableTypes ? "" : `._addFileTypes<FileRouteTypes>()`}`
614
- ].join("\n");
615
- checkRouteFullPathUniqueness(
616
- sortedRouteNodes.filter(
617
- (d) => d.children === void 0 && "lazy" !== d._fsRouteType
618
- ),
619
- config
620
- );
621
- let mergedImports = mergeImportDeclarations(imports);
622
- if (config.disableTypes) {
623
- mergedImports = mergedImports.filter((d) => d.importKind !== "type");
624
- }
625
- const importStatements = mergedImports.map(buildImportString);
626
- let moduleAugmentation = "";
627
- if (config.verboseFileRoutes === false && !config.disableTypes) {
628
- moduleAugmentation = opts.routeFileResult.map((node) => {
629
- const getModuleDeclaration = (routeNode) => {
630
- if (!isRouteNodeValidForAugmentation(routeNode)) {
631
- return "";
632
- }
633
- let moduleAugmentation2 = "";
634
- if (routeNode._fsRouteType === "lazy") {
635
- moduleAugmentation2 = `const createLazyFileRoute: CreateLazyFileRoute<FileRoutesByPath['${routeNode.routePath}']['preLoaderRoute']>`;
636
- } else {
637
- moduleAugmentation2 = `const createFileRoute: CreateFileRoute<'${routeNode.routePath}',
426
+ ].join("\n");
427
+ fileRoutesByPathInterface = buildFileRoutesByPathInterface({
428
+ module: this.targetTemplate.fullPkg,
429
+ interfaceName: "FileRoutesByPath",
430
+ routeNodes: sortedRouteNodes,
431
+ config
432
+ });
433
+ }
434
+ const routeTree = [`const rootRouteChildren${config.disableTypes ? "" : `: RootRouteChildren`} = {
435
+ ${acc.routeTree.map((child) => `${child.variableName}Route: ${getResolvedRouteNodeVariableName(child)}`).join(",")}
436
+ }`, rootRouteUpdate ? rootRouteUpdate.replace("const rootRouteWithChildren = ", "export const routeTree = ") : `export const routeTree = rootRouteImport._addFileChildren(rootRouteChildren)${config.disableTypes ? "" : `._addFileTypes<FileRouteTypes>()`}`].join("\n");
437
+ checkRouteFullPathUniqueness(sortedRouteNodes.filter((d) => d.children === void 0 && "lazy" !== d._fsRouteType), config);
438
+ let mergedImports = mergeImportDeclarations(imports);
439
+ if (config.disableTypes) mergedImports = mergedImports.filter((d) => d.importKind !== "type");
440
+ const importStatements = mergedImports.map(buildImportString);
441
+ let moduleAugmentation = "";
442
+ if (config.verboseFileRoutes === false && !config.disableTypes) moduleAugmentation = opts.routeFileResult.map((node) => {
443
+ const getModuleDeclaration = (routeNode) => {
444
+ if (!isRouteNodeValidForAugmentation(routeNode)) return "";
445
+ let moduleAugmentation = "";
446
+ if (routeNode._fsRouteType === "lazy") moduleAugmentation = `const createLazyFileRoute: CreateLazyFileRoute<FileRoutesByPath['${routeNode.routePath}']['preLoaderRoute']>`;
447
+ else moduleAugmentation = `const createFileRoute: CreateFileRoute<'${routeNode.routePath}',
638
448
  FileRoutesByPath['${routeNode.routePath}']['parentRoute'],
639
449
  FileRoutesByPath['${routeNode.routePath}']['id'],
640
450
  FileRoutesByPath['${routeNode.routePath}']['path'],
641
451
  FileRoutesByPath['${routeNode.routePath}']['fullPath']
642
452
  >
643
453
  `;
644
- }
645
- return `declare module './${getImportPath(routeNode, config, this.generatedRouteTreePath)}' {
646
- ${moduleAugmentation2}
454
+ return `declare module './${getImportPath(routeNode, config, this.generatedRouteTreePath)}' {
455
+ ${moduleAugmentation}
647
456
  }`;
648
- };
649
- return getModuleDeclaration(node);
650
- }).join("\n");
651
- }
652
- const rootRouteImport = getImportForRouteNode(
653
- rootRouteNode,
654
- config,
655
- this.generatedRouteTreePath,
656
- this.root
657
- );
658
- routeImports.unshift(rootRouteImport);
659
- let footer = [];
660
- if (config.routeTreeFileFooter) {
661
- if (Array.isArray(config.routeTreeFileFooter)) {
662
- footer = config.routeTreeFileFooter;
663
- } else {
664
- footer = config.routeTreeFileFooter();
665
- }
666
- }
667
- const routeTreeContent = [
668
- ...config.routeTreeFileHeader,
669
- `// This file was automatically generated by TanStack Router.
457
+ };
458
+ return getModuleDeclaration(node);
459
+ }).join("\n");
460
+ const rootRouteImport = getImportForRouteNode(rootRouteNode, config, this.generatedRouteTreePath, this.root);
461
+ routeImports.unshift(rootRouteImport);
462
+ let footer = [];
463
+ if (config.routeTreeFileFooter) if (Array.isArray(config.routeTreeFileFooter)) footer = config.routeTreeFileFooter;
464
+ else footer = config.routeTreeFileFooter();
465
+ return {
466
+ routeTreeContent: [
467
+ ...config.routeTreeFileHeader,
468
+ `// This file was automatically generated by TanStack Router.
670
469
  // You should NOT make any changes in this file as it will be overwritten.
671
470
  // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.`,
672
- [...importStatements].join("\n"),
673
- mergeImportDeclarations(routeImports).map(buildImportString).join("\n"),
674
- virtualRouteNodes.join("\n"),
675
- createUpdateRoutes.join("\n"),
676
- fileRoutesByFullPath,
677
- fileRoutesByPathInterface,
678
- moduleAugmentation,
679
- routeTreeConfig.join("\n"),
680
- routeTree,
681
- ...footer
682
- ].filter(Boolean).join("\n\n");
683
- return {
684
- routeTreeContent,
685
- routeTree: acc.routeTree,
686
- routeNodes: acc.routeNodes
687
- };
688
- }
689
- async processRouteNodeFile(node) {
690
- const result = await this.isRouteFileCacheFresh(node);
691
- if (result.status === "fresh") {
692
- return {
693
- node: result.cacheEntry.node,
694
- shouldWriteTree: false,
695
- cacheEntry: result.cacheEntry
696
- };
697
- }
698
- const previousCacheEntry = result.cacheEntry;
699
- const existingRouteFile = await this.fs.readFile(node.fullPath);
700
- if (existingRouteFile === "file-not-existing") {
701
- throw new Error(`⚠️ File ${node.fullPath} does not exist`);
702
- }
703
- if (node.routePath) {
704
- validateRouteParams(node.routePath, node.filePath, this.logger);
705
- }
706
- const updatedCacheEntry = {
707
- fileContent: existingRouteFile.fileContent,
708
- mtimeMs: existingRouteFile.stat.mtimeMs,
709
- routeId: node.routePath ?? "$$TSR_NO_ROUTE_PATH_ASSIGNED$$",
710
- node
711
- };
712
- const escapedRoutePath = node.routePath?.replaceAll("$", "$$") ?? "";
713
- let shouldWriteRouteFile = false;
714
- let shouldWriteTree = false;
715
- if (!existingRouteFile.fileContent) {
716
- shouldWriteRouteFile = true;
717
- shouldWriteTree = true;
718
- if (node._fsRouteType === "lazy") {
719
- const tLazyRouteTemplate = this.targetTemplate.lazyRoute;
720
- updatedCacheEntry.fileContent = await fillTemplate(
721
- this.config,
722
- (this.config.customScaffolding?.lazyRouteTemplate || this.config.customScaffolding?.routeTemplate) ?? tLazyRouteTemplate.template(),
723
- {
724
- tsrImports: tLazyRouteTemplate.imports.tsrImports(),
725
- tsrPath: escapedRoutePath.replaceAll(/\{(.+?)\}/gm, "$1"),
726
- tsrExportStart: tLazyRouteTemplate.imports.tsrExportStart(escapedRoutePath),
727
- tsrExportEnd: tLazyRouteTemplate.imports.tsrExportEnd()
728
- }
729
- );
730
- } else if (
731
- // Creating a new normal route file
732
- ["layout", "static"].some(
733
- (d) => d === node._fsRouteType
734
- ) || [
735
- "component",
736
- "pendingComponent",
737
- "errorComponent",
738
- "notFoundComponent",
739
- "loader"
740
- ].every((d) => d !== node._fsRouteType)
741
- ) {
742
- const tRouteTemplate = this.targetTemplate.route;
743
- updatedCacheEntry.fileContent = await fillTemplate(
744
- this.config,
745
- this.config.customScaffolding?.routeTemplate ?? tRouteTemplate.template(),
746
- {
747
- tsrImports: tRouteTemplate.imports.tsrImports(),
748
- tsrPath: escapedRoutePath.replaceAll(/\{(.+?)\}/gm, "$1"),
749
- tsrExportStart: tRouteTemplate.imports.tsrExportStart(escapedRoutePath),
750
- tsrExportEnd: tRouteTemplate.imports.tsrExportEnd()
751
- }
752
- );
753
- } else {
754
- return null;
755
- }
756
- }
757
- const isVueFile = node.filePath.endsWith(".vue");
758
- if (!isVueFile) {
759
- const transformResult = await transform({
760
- source: updatedCacheEntry.fileContent,
761
- ctx: {
762
- target: this.config.target,
763
- routeId: escapedRoutePath,
764
- lazy: node._fsRouteType === "lazy",
765
- verboseFileRoutes: !(this.config.verboseFileRoutes === false)
766
- },
767
- node
768
- });
769
- if (transformResult.result === "no-route-export") {
770
- const fileName = path.basename(node.fullPath);
771
- const dirName = path.dirname(node.fullPath);
772
- const ignorePrefix = this.config.routeFileIgnorePrefix;
773
- const ignorePattern = this.config.routeFileIgnorePattern;
774
- const suggestedFileName = `${ignorePrefix}${fileName}`;
775
- const suggestedFullPath = path.join(dirName, suggestedFileName);
776
- let message = `Warning: Route file "${node.fullPath}" does not export a Route. This file will not be included in the route tree.`;
777
- message += `
778
-
779
- If this file is not intended to be a route, you can exclude it using one of these options:`;
780
- message += `
781
- 1. Rename the file to "${suggestedFullPath}" (prefix with "${ignorePrefix}")`;
782
- message += `
783
- 2. Use 'routeFileIgnorePattern' in your config to match this file`;
784
- message += `
785
-
786
- Current configuration:`;
787
- message += `
788
- routeFileIgnorePrefix: "${ignorePrefix}"`;
789
- message += `
790
- routeFileIgnorePattern: ${ignorePattern ? `"${ignorePattern}"` : "undefined"}`;
791
- this.logger.warn(message);
792
- return null;
793
- }
794
- if (transformResult.result === "error") {
795
- throw new Error(
796
- `Error transforming route file ${node.fullPath}: ${transformResult.error}`
797
- );
798
- }
799
- if (transformResult.result === "modified") {
800
- updatedCacheEntry.fileContent = transformResult.output;
801
- shouldWriteRouteFile = true;
802
- }
803
- }
804
- for (const plugin of this.plugins) {
805
- plugin.afterTransform?.({ node, prevNode: previousCacheEntry?.node });
806
- }
807
- if (shouldWriteRouteFile) {
808
- const stats = await this.safeFileWrite({
809
- filePath: node.fullPath,
810
- newContent: updatedCacheEntry.fileContent,
811
- strategy: {
812
- type: "mtime",
813
- expectedMtimeMs: updatedCacheEntry.mtimeMs
814
- }
815
- });
816
- updatedCacheEntry.mtimeMs = stats.mtimeMs;
817
- }
818
- this.routeNodeShadowCache.set(node.fullPath, updatedCacheEntry);
819
- return {
820
- node,
821
- shouldWriteTree,
822
- cacheEntry: updatedCacheEntry
823
- };
824
- }
825
- async didRouteFileChangeComparedToCache(file, cache) {
826
- const cacheEntry = this[cache].get(file.path);
827
- return this.didFileChangeComparedToCache(file, cacheEntry);
828
- }
829
- async didFileChangeComparedToCache(file, cacheEntry) {
830
- if (!cacheEntry) {
831
- return { result: "file-not-in-cache" };
832
- }
833
- let mtimeMs = file.mtimeMs;
834
- if (mtimeMs === void 0) {
835
- try {
836
- const currentStat = await this.fs.stat(file.path);
837
- mtimeMs = currentStat.mtimeMs;
838
- } catch {
839
- return { result: "cannot-stat-file" };
840
- }
841
- }
842
- return { result: mtimeMs !== cacheEntry.mtimeMs, mtimeMs, cacheEntry };
843
- }
844
- async safeFileWrite(opts) {
845
- const tmpPath = this.getTempFileName(opts.filePath);
846
- await this.fs.writeFile(tmpPath, opts.newContent);
847
- if (opts.strategy.type === "mtime") {
848
- const beforeStat = await this.fs.stat(opts.filePath);
849
- if (beforeStat.mtimeMs !== opts.strategy.expectedMtimeMs) {
850
- throw rerun({
851
- msg: `File ${opts.filePath} was modified by another process during processing.`,
852
- event: { type: "update", path: opts.filePath }
853
- });
854
- }
855
- const newFileState = await this.fs.stat(tmpPath);
856
- if (newFileState.mode !== beforeStat.mode) {
857
- await this.fs.chmod(tmpPath, beforeStat.mode);
858
- }
859
- if (newFileState.uid !== beforeStat.uid || newFileState.gid !== beforeStat.gid) {
860
- try {
861
- await this.fs.chown(tmpPath, beforeStat.uid, beforeStat.gid);
862
- } catch (err) {
863
- if (typeof err === "object" && err !== null && "code" in err && err.code === "EPERM") {
864
- console.warn(
865
- `[safeFileWrite] chown failed: ${err.message}`
866
- );
867
- } else {
868
- throw err;
869
- }
870
- }
871
- }
872
- } else {
873
- if (await checkFileExists(opts.filePath)) {
874
- throw rerun({
875
- msg: `File ${opts.filePath} already exists. Cannot overwrite.`,
876
- event: { type: "update", path: opts.filePath }
877
- });
878
- }
879
- }
880
- const stat = await this.fs.stat(tmpPath);
881
- await this.fs.rename(tmpPath, opts.filePath);
882
- return stat;
883
- }
884
- getTempFileName(filePath) {
885
- const absPath = path.resolve(filePath);
886
- const hash = crypto.createHash("md5").update(absPath).digest("hex");
887
- if (!this.sessionId) {
888
- mkdirSync(this.config.tmpDir, { recursive: true });
889
- this.sessionId = crypto.randomBytes(4).toString("hex");
890
- }
891
- return path.join(this.config.tmpDir, `${this.sessionId}-${hash}`);
892
- }
893
- async isRouteFileCacheFresh(node) {
894
- const fileChangedCache = await this.didRouteFileChangeComparedToCache(
895
- { path: node.fullPath },
896
- "routeNodeCache"
897
- );
898
- if (fileChangedCache.result === false) {
899
- this.routeNodeShadowCache.set(node.fullPath, fileChangedCache.cacheEntry);
900
- return {
901
- status: "fresh",
902
- cacheEntry: fileChangedCache.cacheEntry
903
- };
904
- }
905
- if (fileChangedCache.result === "cannot-stat-file") {
906
- throw new Error(`⚠️ expected route file to exist at ${node.fullPath}`);
907
- }
908
- const mtimeMs = fileChangedCache.result === true ? fileChangedCache.mtimeMs : void 0;
909
- const shadowCacheFileChange = await this.didRouteFileChangeComparedToCache(
910
- { path: node.fullPath, mtimeMs },
911
- "routeNodeShadowCache"
912
- );
913
- if (shadowCacheFileChange.result === "cannot-stat-file") {
914
- throw new Error(`⚠️ expected route file to exist at ${node.fullPath}`);
915
- }
916
- if (shadowCacheFileChange.result === false) {
917
- if (fileChangedCache.result === true) {
918
- return {
919
- status: "fresh",
920
- cacheEntry: shadowCacheFileChange.cacheEntry
921
- };
922
- }
923
- }
924
- if (fileChangedCache.result === "file-not-in-cache") {
925
- return {
926
- status: "stale"
927
- };
928
- }
929
- return { status: "stale", cacheEntry: fileChangedCache.cacheEntry };
930
- }
931
- async handleRootNode(node) {
932
- const result = await this.isRouteFileCacheFresh(node);
933
- if (result.status === "fresh") {
934
- this.routeNodeShadowCache.set(node.fullPath, result.cacheEntry);
935
- }
936
- const rootNodeFile = await this.fs.readFile(node.fullPath);
937
- if (rootNodeFile === "file-not-existing") {
938
- throw new Error(`⚠️ expected root route to exist at ${node.fullPath}`);
939
- }
940
- const updatedCacheEntry = {
941
- fileContent: rootNodeFile.fileContent,
942
- mtimeMs: rootNodeFile.stat.mtimeMs,
943
- routeId: node.routePath ?? "$$TSR_NO_ROOT_ROUTE_PATH_ASSIGNED$$",
944
- node
945
- };
946
- if (!rootNodeFile.fileContent) {
947
- const rootTemplate = this.targetTemplate.rootRoute;
948
- const rootRouteContent = await fillTemplate(
949
- this.config,
950
- rootTemplate.template(),
951
- {
952
- tsrImports: rootTemplate.imports.tsrImports(),
953
- tsrPath: rootPathId,
954
- tsrExportStart: rootTemplate.imports.tsrExportStart(),
955
- tsrExportEnd: rootTemplate.imports.tsrExportEnd()
956
- }
957
- );
958
- this.logger.log(`🟡 Creating ${node.fullPath}`);
959
- const stats = await this.safeFileWrite({
960
- filePath: node.fullPath,
961
- newContent: rootRouteContent,
962
- strategy: {
963
- type: "mtime",
964
- expectedMtimeMs: rootNodeFile.stat.mtimeMs
965
- }
966
- });
967
- updatedCacheEntry.fileContent = rootRouteContent;
968
- updatedCacheEntry.mtimeMs = stats.mtimeMs;
969
- }
970
- this.routeNodeShadowCache.set(node.fullPath, updatedCacheEntry);
971
- }
972
- async getCrawlingResult() {
973
- await this.runPromise;
974
- return this.crawlingResult;
975
- }
976
- static handleNode(node, acc, prefixMap, config) {
977
- let parentRoute = hasParentRoute(prefixMap, node, node.routePath);
978
- if (node.routePath) {
979
- let searchPath = node.routePath;
980
- while (searchPath.length > 0) {
981
- const lastSlash = searchPath.lastIndexOf("/");
982
- if (lastSlash <= 0) break;
983
- searchPath = searchPath.substring(0, lastSlash);
984
- const candidate = acc.routeNodesByPath.get(searchPath);
985
- if (candidate && candidate.routePath !== node.routePath) {
986
- if (candidate !== parentRoute) {
987
- if (!parentRoute || (candidate.routePath?.length ?? 0) > (parentRoute.routePath?.length ?? 0)) {
988
- parentRoute = candidate;
989
- }
990
- }
991
- break;
992
- }
993
- }
994
- }
995
- if (node._virtualParentRoutePath !== void 0) {
996
- const explicitParent = acc.routeNodesByPath.get(
997
- node._virtualParentRoutePath
998
- );
999
- if (explicitParent) {
1000
- parentRoute = explicitParent;
1001
- } else if (node._virtualParentRoutePath === `/${rootPathId}`) {
1002
- parentRoute = null;
1003
- }
1004
- }
1005
- if (parentRoute) node.parent = parentRoute;
1006
- node.path = determineNodePath(node);
1007
- const trimmedPath = trimPathLeft(node.path ?? "");
1008
- const trimmedOriginalPath = trimPathLeft(
1009
- node.originalRoutePath?.replace(
1010
- node.parent?.originalRoutePath ?? "",
1011
- ""
1012
- ) ?? ""
1013
- );
1014
- const split = trimmedPath.split("/");
1015
- const originalSplit = trimmedOriginalPath.split("/");
1016
- const lastRouteSegment = split[split.length - 1] ?? trimmedPath;
1017
- const lastOriginalSegment = originalSplit[originalSplit.length - 1] ?? trimmedOriginalPath;
1018
- node.isNonPath = isSegmentPathless(lastRouteSegment, lastOriginalSegment) || split.every((part) => this.routeGroupPatternRegex.test(part));
1019
- node.cleanedPath = removeGroups(
1020
- removeUnderscoresWithEscape(
1021
- removeLayoutSegmentsWithEscape(node.path, node.originalRoutePath),
1022
- node.originalRoutePath
1023
- )
1024
- );
1025
- if (node._fsRouteType === "layout" || node._fsRouteType === "pathless_layout") {
1026
- node.cleanedPath = removeTrailingSlash(node.cleanedPath);
1027
- }
1028
- if (!node.isVirtual && [
1029
- "lazy",
1030
- "loader",
1031
- "component",
1032
- "pendingComponent",
1033
- "errorComponent",
1034
- "notFoundComponent"
1035
- ].some((d) => d === node._fsRouteType)) {
1036
- acc.routePiecesByPath[node.routePath] = acc.routePiecesByPath[node.routePath] || {};
1037
- const pieceKey = node._fsRouteType === "lazy" ? "lazy" : node._fsRouteType;
1038
- acc.routePiecesByPath[node.routePath][pieceKey] = node;
1039
- const anchorRoute = acc.routeNodesByPath.get(node.routePath);
1040
- if (!anchorRoute && node.routePath !== `/${rootPathId}`) {
1041
- this.handleNode(
1042
- {
1043
- ...node,
1044
- isVirtual: true,
1045
- _fsRouteType: "static"
1046
- },
1047
- acc,
1048
- prefixMap,
1049
- config
1050
- );
1051
- }
1052
- return;
1053
- }
1054
- const isPathlessLayoutWithPath = node._fsRouteType === "pathless_layout" && node.cleanedPath && node.cleanedPath.length > 0;
1055
- if (!node.isVirtual && isPathlessLayoutWithPath) {
1056
- const immediateParentPath = removeLastSegmentFromPath(node.routePath) || "/";
1057
- const immediateParentOriginalPath = removeLastSegmentFromPath(node.originalRoutePath) || "/";
1058
- let searchPath = immediateParentPath;
1059
- while (searchPath) {
1060
- const candidate = acc.routeNodesByPath.get(searchPath);
1061
- if (candidate && !candidate.isVirtual && candidate.path !== "/") {
1062
- node.parent = candidate;
1063
- node.path = node.routePath?.replace(candidate.routePath ?? "", "") || "/";
1064
- const pathRelativeToParent = immediateParentPath.replace(candidate.routePath ?? "", "") || "/";
1065
- const originalPathRelativeToParent = immediateParentOriginalPath.replace(
1066
- candidate.originalRoutePath ?? "",
1067
- ""
1068
- ) || "/";
1069
- node.cleanedPath = removeGroups(
1070
- removeUnderscoresWithEscape(
1071
- removeLayoutSegmentsWithEscape(
1072
- pathRelativeToParent,
1073
- originalPathRelativeToParent
1074
- ),
1075
- originalPathRelativeToParent
1076
- )
1077
- );
1078
- break;
1079
- }
1080
- if (searchPath === "/") break;
1081
- searchPath = removeLastSegmentFromPath(searchPath) || "/";
1082
- }
1083
- }
1084
- if (node.parent) {
1085
- node.parent.children = node.parent.children ?? [];
1086
- node.parent.children.push(node);
1087
- } else {
1088
- acc.routeTree.push(node);
1089
- }
1090
- acc.routeNodes.push(node);
1091
- if (node.routePath) {
1092
- acc.routeNodesByPath.set(node.routePath, node);
1093
- }
1094
- }
1095
- // only process files that are relevant for the route tree generation
1096
- isFileRelevantForRouteTreeGeneration(filePath) {
1097
- if (filePath === this.generatedRouteTreePath) {
1098
- return true;
1099
- }
1100
- if (filePath.startsWith(this.routesDirectoryPath)) {
1101
- return true;
1102
- }
1103
- if (typeof this.config.virtualRouteConfig === "string" && filePath === this.config.virtualRouteConfig) {
1104
- return true;
1105
- }
1106
- if (this.routeNodeCache.has(filePath)) {
1107
- return true;
1108
- }
1109
- if (isVirtualConfigFile(path.basename(filePath))) {
1110
- return true;
1111
- }
1112
- if (this.physicalDirectories.some((dir) => filePath.startsWith(dir))) {
1113
- return true;
1114
- }
1115
- return false;
1116
- }
471
+ [...importStatements].join("\n"),
472
+ mergeImportDeclarations(routeImports).map(buildImportString).join("\n"),
473
+ virtualRouteNodes.join("\n"),
474
+ createUpdateRoutes.join("\n"),
475
+ fileRoutesByFullPath,
476
+ fileRoutesByPathInterface,
477
+ moduleAugmentation,
478
+ routeTreeConfig.join("\n"),
479
+ routeTree,
480
+ ...footer
481
+ ].filter(Boolean).join("\n\n"),
482
+ routeTree: acc.routeTree,
483
+ routeNodes: acc.routeNodes
484
+ };
485
+ }
486
+ async processRouteNodeFile(node) {
487
+ const result = await this.isRouteFileCacheFresh(node);
488
+ if (result.status === "fresh") return {
489
+ node: result.cacheEntry.node,
490
+ shouldWriteTree: false,
491
+ cacheEntry: result.cacheEntry
492
+ };
493
+ const previousCacheEntry = result.cacheEntry;
494
+ const existingRouteFile = await this.fs.readFile(node.fullPath);
495
+ if (existingRouteFile === "file-not-existing") throw new Error(`⚠️ File ${node.fullPath} does not exist`);
496
+ if (node.routePath) validateRouteParams(node.routePath, node.filePath, this.logger);
497
+ const updatedCacheEntry = {
498
+ fileContent: existingRouteFile.fileContent,
499
+ mtimeMs: existingRouteFile.stat.mtimeMs,
500
+ routeId: node.routePath ?? "$$TSR_NO_ROUTE_PATH_ASSIGNED$$",
501
+ node
502
+ };
503
+ const escapedRoutePath = node.routePath?.replaceAll("$", "$$") ?? "";
504
+ let shouldWriteRouteFile = false;
505
+ let shouldWriteTree = false;
506
+ if (!existingRouteFile.fileContent) {
507
+ shouldWriteRouteFile = true;
508
+ shouldWriteTree = true;
509
+ if (node._fsRouteType === "lazy") {
510
+ const tLazyRouteTemplate = this.targetTemplate.lazyRoute;
511
+ updatedCacheEntry.fileContent = await fillTemplate(this.config, (this.config.customScaffolding?.lazyRouteTemplate || this.config.customScaffolding?.routeTemplate) ?? tLazyRouteTemplate.template(), {
512
+ tsrImports: tLazyRouteTemplate.imports.tsrImports(),
513
+ tsrPath: escapedRoutePath.replaceAll(/\{(.+?)\}/gm, "$1"),
514
+ tsrExportStart: tLazyRouteTemplate.imports.tsrExportStart(escapedRoutePath),
515
+ tsrExportEnd: tLazyRouteTemplate.imports.tsrExportEnd()
516
+ });
517
+ } else if (["layout", "static"].some((d) => d === node._fsRouteType) || [
518
+ "component",
519
+ "pendingComponent",
520
+ "errorComponent",
521
+ "notFoundComponent",
522
+ "loader"
523
+ ].every((d) => d !== node._fsRouteType)) {
524
+ const tRouteTemplate = this.targetTemplate.route;
525
+ updatedCacheEntry.fileContent = await fillTemplate(this.config, this.config.customScaffolding?.routeTemplate ?? tRouteTemplate.template(), {
526
+ tsrImports: tRouteTemplate.imports.tsrImports(),
527
+ tsrPath: escapedRoutePath.replaceAll(/\{(.+?)\}/gm, "$1"),
528
+ tsrExportStart: tRouteTemplate.imports.tsrExportStart(escapedRoutePath),
529
+ tsrExportEnd: tRouteTemplate.imports.tsrExportEnd()
530
+ });
531
+ } else return null;
532
+ }
533
+ if (!node.filePath.endsWith(".vue")) {
534
+ const transformResult = await transform({
535
+ source: updatedCacheEntry.fileContent,
536
+ ctx: {
537
+ target: this.config.target,
538
+ routeId: escapedRoutePath,
539
+ lazy: node._fsRouteType === "lazy",
540
+ verboseFileRoutes: !(this.config.verboseFileRoutes === false)
541
+ },
542
+ node
543
+ });
544
+ if (transformResult.result === "no-route-export") {
545
+ const fileName = path.basename(node.fullPath);
546
+ const dirName = path.dirname(node.fullPath);
547
+ const ignorePrefix = this.config.routeFileIgnorePrefix;
548
+ const ignorePattern = this.config.routeFileIgnorePattern;
549
+ const suggestedFileName = `${ignorePrefix}${fileName}`;
550
+ const suggestedFullPath = path.join(dirName, suggestedFileName);
551
+ let message = `Warning: Route file "${node.fullPath}" does not export a Route. This file will not be included in the route tree.`;
552
+ message += `\n\nIf this file is not intended to be a route, you can exclude it using one of these options:`;
553
+ message += `\n 1. Rename the file to "${suggestedFullPath}" (prefix with "${ignorePrefix}")`;
554
+ message += `\n 2. Use 'routeFileIgnorePattern' in your config to match this file`;
555
+ message += `\n\nCurrent configuration:`;
556
+ message += `\n routeFileIgnorePrefix: "${ignorePrefix}"`;
557
+ message += `\n routeFileIgnorePattern: ${ignorePattern ? `"${ignorePattern}"` : "undefined"}`;
558
+ this.logger.warn(message);
559
+ return null;
560
+ }
561
+ if (transformResult.result === "error") throw new Error(`Error transforming route file ${node.fullPath}: ${transformResult.error}`);
562
+ if (transformResult.result === "modified") {
563
+ updatedCacheEntry.fileContent = transformResult.output;
564
+ shouldWriteRouteFile = true;
565
+ }
566
+ }
567
+ for (const plugin of this.plugins) plugin.afterTransform?.({
568
+ node,
569
+ prevNode: previousCacheEntry?.node
570
+ });
571
+ if (shouldWriteRouteFile) updatedCacheEntry.mtimeMs = (await this.safeFileWrite({
572
+ filePath: node.fullPath,
573
+ newContent: updatedCacheEntry.fileContent,
574
+ strategy: {
575
+ type: "mtime",
576
+ expectedMtimeMs: updatedCacheEntry.mtimeMs
577
+ }
578
+ })).mtimeMs;
579
+ this.routeNodeShadowCache.set(node.fullPath, updatedCacheEntry);
580
+ return {
581
+ node,
582
+ shouldWriteTree,
583
+ cacheEntry: updatedCacheEntry
584
+ };
585
+ }
586
+ async didRouteFileChangeComparedToCache(file, cache) {
587
+ const cacheEntry = this[cache].get(file.path);
588
+ return this.didFileChangeComparedToCache(file, cacheEntry);
589
+ }
590
+ async didFileChangeComparedToCache(file, cacheEntry) {
591
+ if (!cacheEntry) return { result: "file-not-in-cache" };
592
+ let mtimeMs = file.mtimeMs;
593
+ if (mtimeMs === void 0) try {
594
+ mtimeMs = (await this.fs.stat(file.path)).mtimeMs;
595
+ } catch {
596
+ return { result: "cannot-stat-file" };
597
+ }
598
+ return {
599
+ result: mtimeMs !== cacheEntry.mtimeMs,
600
+ mtimeMs,
601
+ cacheEntry
602
+ };
603
+ }
604
+ async safeFileWrite(opts) {
605
+ const tmpPath = this.getTempFileName(opts.filePath);
606
+ await this.fs.writeFile(tmpPath, opts.newContent);
607
+ if (opts.strategy.type === "mtime") {
608
+ const beforeStat = await this.fs.stat(opts.filePath);
609
+ if (beforeStat.mtimeMs !== opts.strategy.expectedMtimeMs) throw rerun({
610
+ msg: `File ${opts.filePath} was modified by another process during processing.`,
611
+ event: {
612
+ type: "update",
613
+ path: opts.filePath
614
+ }
615
+ });
616
+ const newFileState = await this.fs.stat(tmpPath);
617
+ if (newFileState.mode !== beforeStat.mode) await this.fs.chmod(tmpPath, beforeStat.mode);
618
+ if (newFileState.uid !== beforeStat.uid || newFileState.gid !== beforeStat.gid) try {
619
+ await this.fs.chown(tmpPath, beforeStat.uid, beforeStat.gid);
620
+ } catch (err) {
621
+ if (typeof err === "object" && err !== null && "code" in err && err.code === "EPERM") console.warn(`[safeFileWrite] chown failed: ${err.message}`);
622
+ else throw err;
623
+ }
624
+ } else if (await checkFileExists(opts.filePath)) throw rerun({
625
+ msg: `File ${opts.filePath} already exists. Cannot overwrite.`,
626
+ event: {
627
+ type: "update",
628
+ path: opts.filePath
629
+ }
630
+ });
631
+ const stat = await this.fs.stat(tmpPath);
632
+ await this.fs.rename(tmpPath, opts.filePath);
633
+ return stat;
634
+ }
635
+ getTempFileName(filePath) {
636
+ const absPath = path.resolve(filePath);
637
+ const hash = crypto.createHash("md5").update(absPath).digest("hex");
638
+ if (!this.sessionId) {
639
+ mkdirSync(this.config.tmpDir, { recursive: true });
640
+ this.sessionId = crypto.randomBytes(4).toString("hex");
641
+ }
642
+ return path.join(this.config.tmpDir, `${this.sessionId}-${hash}`);
643
+ }
644
+ async isRouteFileCacheFresh(node) {
645
+ const fileChangedCache = await this.didRouteFileChangeComparedToCache({ path: node.fullPath }, "routeNodeCache");
646
+ if (fileChangedCache.result === false) {
647
+ this.routeNodeShadowCache.set(node.fullPath, fileChangedCache.cacheEntry);
648
+ return {
649
+ status: "fresh",
650
+ cacheEntry: fileChangedCache.cacheEntry
651
+ };
652
+ }
653
+ if (fileChangedCache.result === "cannot-stat-file") throw new Error(`⚠️ expected route file to exist at ${node.fullPath}`);
654
+ const mtimeMs = fileChangedCache.result === true ? fileChangedCache.mtimeMs : void 0;
655
+ const shadowCacheFileChange = await this.didRouteFileChangeComparedToCache({
656
+ path: node.fullPath,
657
+ mtimeMs
658
+ }, "routeNodeShadowCache");
659
+ if (shadowCacheFileChange.result === "cannot-stat-file") throw new Error(`⚠️ expected route file to exist at ${node.fullPath}`);
660
+ if (shadowCacheFileChange.result === false) {
661
+ if (fileChangedCache.result === true) return {
662
+ status: "fresh",
663
+ cacheEntry: shadowCacheFileChange.cacheEntry
664
+ };
665
+ }
666
+ if (fileChangedCache.result === "file-not-in-cache") return { status: "stale" };
667
+ return {
668
+ status: "stale",
669
+ cacheEntry: fileChangedCache.cacheEntry
670
+ };
671
+ }
672
+ async handleRootNode(node) {
673
+ const result = await this.isRouteFileCacheFresh(node);
674
+ if (result.status === "fresh") this.routeNodeShadowCache.set(node.fullPath, result.cacheEntry);
675
+ const rootNodeFile = await this.fs.readFile(node.fullPath);
676
+ if (rootNodeFile === "file-not-existing") throw new Error(`⚠️ expected root route to exist at ${node.fullPath}`);
677
+ const updatedCacheEntry = {
678
+ fileContent: rootNodeFile.fileContent,
679
+ mtimeMs: rootNodeFile.stat.mtimeMs,
680
+ routeId: node.routePath ?? "$$TSR_NO_ROOT_ROUTE_PATH_ASSIGNED$$",
681
+ node
682
+ };
683
+ if (!rootNodeFile.fileContent) {
684
+ const rootTemplate = this.targetTemplate.rootRoute;
685
+ const rootRouteContent = await fillTemplate(this.config, rootTemplate.template(), {
686
+ tsrImports: rootTemplate.imports.tsrImports(),
687
+ tsrPath: rootPathId,
688
+ tsrExportStart: rootTemplate.imports.tsrExportStart(),
689
+ tsrExportEnd: rootTemplate.imports.tsrExportEnd()
690
+ });
691
+ this.logger.log(`🟡 Creating ${node.fullPath}`);
692
+ const stats = await this.safeFileWrite({
693
+ filePath: node.fullPath,
694
+ newContent: rootRouteContent,
695
+ strategy: {
696
+ type: "mtime",
697
+ expectedMtimeMs: rootNodeFile.stat.mtimeMs
698
+ }
699
+ });
700
+ updatedCacheEntry.fileContent = rootRouteContent;
701
+ updatedCacheEntry.mtimeMs = stats.mtimeMs;
702
+ }
703
+ this.routeNodeShadowCache.set(node.fullPath, updatedCacheEntry);
704
+ }
705
+ async getCrawlingResult() {
706
+ await this.runPromise;
707
+ return this.crawlingResult;
708
+ }
709
+ static handleNode(node, acc, prefixMap, config) {
710
+ let parentRoute = hasParentRoute(prefixMap, node, node.routePath);
711
+ if (node.routePath) {
712
+ let searchPath = node.routePath;
713
+ while (searchPath.length > 0) {
714
+ const lastSlash = searchPath.lastIndexOf("/");
715
+ if (lastSlash <= 0) break;
716
+ searchPath = searchPath.substring(0, lastSlash);
717
+ const candidate = acc.routeNodesByPath.get(searchPath);
718
+ if (candidate && candidate.routePath !== node.routePath) {
719
+ if (candidate !== parentRoute) {
720
+ if (!parentRoute || (candidate.routePath?.length ?? 0) > (parentRoute.routePath?.length ?? 0)) parentRoute = candidate;
721
+ }
722
+ break;
723
+ }
724
+ }
725
+ }
726
+ if (node._virtualParentRoutePath !== void 0) {
727
+ const explicitParent = acc.routeNodesByPath.get(node._virtualParentRoutePath);
728
+ if (explicitParent) parentRoute = explicitParent;
729
+ else if (node._virtualParentRoutePath === `/__root`) parentRoute = null;
730
+ }
731
+ if (parentRoute) node.parent = parentRoute;
732
+ node.path = determineNodePath(node);
733
+ const trimmedPath = trimPathLeft(node.path ?? "");
734
+ const trimmedOriginalPath = trimPathLeft(node.originalRoutePath?.replace(node.parent?.originalRoutePath ?? "", "") ?? "");
735
+ const split = trimmedPath.split("/");
736
+ const originalSplit = trimmedOriginalPath.split("/");
737
+ node.isNonPath = isSegmentPathless(split[split.length - 1] ?? trimmedPath, originalSplit[originalSplit.length - 1] ?? trimmedOriginalPath) || split.every((part) => this.routeGroupPatternRegex.test(part));
738
+ node.cleanedPath = removeGroups(removeUnderscoresWithEscape(removeLayoutSegmentsWithEscape(node.path, node.originalRoutePath), node.originalRoutePath));
739
+ if (node._fsRouteType === "layout" || node._fsRouteType === "pathless_layout") node.cleanedPath = removeTrailingSlash(node.cleanedPath);
740
+ if (!node.isVirtual && [
741
+ "lazy",
742
+ "loader",
743
+ "component",
744
+ "pendingComponent",
745
+ "errorComponent",
746
+ "notFoundComponent"
747
+ ].some((d) => d === node._fsRouteType)) {
748
+ acc.routePiecesByPath[node.routePath] = acc.routePiecesByPath[node.routePath] || {};
749
+ const pieceKey = node._fsRouteType === "lazy" ? "lazy" : node._fsRouteType;
750
+ acc.routePiecesByPath[node.routePath][pieceKey] = node;
751
+ if (!acc.routeNodesByPath.get(node.routePath) && node.routePath !== `/__root`) this.handleNode({
752
+ ...node,
753
+ isVirtual: true,
754
+ _fsRouteType: "static"
755
+ }, acc, prefixMap, config);
756
+ return;
757
+ }
758
+ const isPathlessLayoutWithPath = node._fsRouteType === "pathless_layout" && node.cleanedPath && node.cleanedPath.length > 0;
759
+ if (!node.isVirtual && isPathlessLayoutWithPath) {
760
+ const immediateParentPath = removeLastSegmentFromPath(node.routePath) || "/";
761
+ const immediateParentOriginalPath = removeLastSegmentFromPath(node.originalRoutePath) || "/";
762
+ let searchPath = immediateParentPath;
763
+ while (searchPath) {
764
+ const candidate = acc.routeNodesByPath.get(searchPath);
765
+ if (candidate && !candidate.isVirtual && candidate.path !== "/") {
766
+ node.parent = candidate;
767
+ node.path = node.routePath?.replace(candidate.routePath ?? "", "") || "/";
768
+ const pathRelativeToParent = immediateParentPath.replace(candidate.routePath ?? "", "") || "/";
769
+ const originalPathRelativeToParent = immediateParentOriginalPath.replace(candidate.originalRoutePath ?? "", "") || "/";
770
+ node.cleanedPath = removeGroups(removeUnderscoresWithEscape(removeLayoutSegmentsWithEscape(pathRelativeToParent, originalPathRelativeToParent), originalPathRelativeToParent));
771
+ break;
772
+ }
773
+ if (searchPath === "/") break;
774
+ searchPath = removeLastSegmentFromPath(searchPath) || "/";
775
+ }
776
+ }
777
+ if (node.parent) {
778
+ node.parent.children = node.parent.children ?? [];
779
+ node.parent.children.push(node);
780
+ } else acc.routeTree.push(node);
781
+ acc.routeNodes.push(node);
782
+ if (node.routePath) acc.routeNodesByPath.set(node.routePath, node);
783
+ }
784
+ isFileRelevantForRouteTreeGeneration(filePath) {
785
+ if (filePath === this.generatedRouteTreePath) return true;
786
+ if (filePath.startsWith(this.routesDirectoryPath)) return true;
787
+ if (typeof this.config.virtualRouteConfig === "string" && filePath === this.config.virtualRouteConfig) return true;
788
+ if (this.routeNodeCache.has(filePath)) return true;
789
+ if (isVirtualConfigFile(path.basename(filePath))) return true;
790
+ if (this.physicalDirectories.some((dir) => filePath.startsWith(dir))) return true;
791
+ return false;
792
+ }
1117
793
  };
1118
- _Generator.routeGroupPatternRegex = /\(.+\)/;
1119
- _Generator.componentPieceRegex = /[./](component|errorComponent|notFoundComponent|pendingComponent|loader|lazy)[.]/;
1120
- let Generator = _Generator;
1121
- export {
1122
- Generator
1123
- };
1124
- //# sourceMappingURL=generator.js.map
794
+ //#endregion
795
+ export { Generator };
796
+
797
+ //# sourceMappingURL=generator.js.map