@tanstack/router-generator 1.121.0-alpha.27 → 1.121.0-alpha.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/config.cjs +1 -3
- package/dist/cjs/config.cjs.map +1 -1
- package/dist/cjs/config.d.cts +3 -3
- package/dist/cjs/filesystem/physical/getRouteNodes.cjs +18 -4
- package/dist/cjs/filesystem/physical/getRouteNodes.cjs.map +1 -1
- package/dist/cjs/filesystem/physical/getRouteNodes.d.cts +1 -0
- package/dist/cjs/filesystem/virtual/config.cjs.map +1 -1
- package/dist/cjs/filesystem/virtual/getRouteNodes.cjs +18 -12
- package/dist/cjs/filesystem/virtual/getRouteNodes.cjs.map +1 -1
- package/dist/cjs/filesystem/virtual/getRouteNodes.d.cts +4 -1
- package/dist/cjs/filesystem/virtual/loadConfigFile.cjs.map +1 -1
- package/dist/cjs/generator.cjs +144 -97
- package/dist/cjs/generator.cjs.map +1 -1
- package/dist/cjs/generator.d.cts +11 -9
- package/dist/cjs/index.d.cts +1 -1
- package/dist/cjs/logger.cjs.map +1 -1
- package/dist/cjs/plugin/default-generator-plugin.cjs +16 -10
- package/dist/cjs/plugin/default-generator-plugin.cjs.map +1 -1
- package/dist/cjs/template.cjs.map +1 -1
- package/dist/cjs/transform/default-transform-plugin.cjs +6 -4
- package/dist/cjs/transform/default-transform-plugin.cjs.map +1 -1
- package/dist/cjs/transform/transform.cjs +41 -20
- package/dist/cjs/transform/transform.cjs.map +1 -1
- package/dist/cjs/transform/utils.cjs.map +1 -1
- package/dist/cjs/types.d.cts +5 -0
- package/dist/cjs/utils.cjs +43 -24
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/cjs/utils.d.cts +6 -0
- package/dist/esm/config.d.ts +3 -3
- package/dist/esm/config.js +2 -4
- package/dist/esm/config.js.map +1 -1
- package/dist/esm/filesystem/physical/getRouteNodes.d.ts +1 -0
- package/dist/esm/filesystem/physical/getRouteNodes.js +19 -5
- package/dist/esm/filesystem/physical/getRouteNodes.js.map +1 -1
- package/dist/esm/filesystem/virtual/config.js.map +1 -1
- package/dist/esm/filesystem/virtual/getRouteNodes.d.ts +4 -1
- package/dist/esm/filesystem/virtual/getRouteNodes.js +19 -13
- package/dist/esm/filesystem/virtual/getRouteNodes.js.map +1 -1
- package/dist/esm/filesystem/virtual/loadConfigFile.js.map +1 -1
- package/dist/esm/generator.d.ts +11 -9
- package/dist/esm/generator.js +147 -100
- package/dist/esm/generator.js.map +1 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/logger.js.map +1 -1
- package/dist/esm/plugin/default-generator-plugin.js +16 -10
- package/dist/esm/plugin/default-generator-plugin.js.map +1 -1
- package/dist/esm/template.js.map +1 -1
- package/dist/esm/transform/default-transform-plugin.js +6 -4
- package/dist/esm/transform/default-transform-plugin.js.map +1 -1
- package/dist/esm/transform/transform.js +41 -20
- package/dist/esm/transform/transform.js.map +1 -1
- package/dist/esm/transform/utils.js.map +1 -1
- package/dist/esm/types.d.ts +5 -0
- package/dist/esm/utils.d.ts +6 -0
- package/dist/esm/utils.js +43 -24
- package/dist/esm/utils.js.map +1 -1
- package/package.json +5 -5
- package/src/config.ts +1 -2
- package/src/filesystem/physical/getRouteNodes.ts +31 -11
- package/src/filesystem/virtual/getRouteNodes.ts +32 -23
- package/src/generator.ts +193 -71
- package/src/index.ts +2 -0
- package/src/plugin/default-generator-plugin.ts +16 -3
- package/src/transform/default-transform-plugin.ts +5 -2
- package/src/transform/transform.ts +59 -25
- package/src/types.ts +7 -0
- package/src/utils.ts +79 -31
package/src/generator.ts
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import path from 'node:path'
|
|
2
2
|
import * as fsp from 'node:fs/promises'
|
|
3
|
-
import {
|
|
3
|
+
import { mkdirSync } from 'node:fs'
|
|
4
4
|
import crypto from 'node:crypto'
|
|
5
5
|
import { deepEqual, rootRouteId } from '@tanstack/router-core'
|
|
6
6
|
import { logging } from './logger'
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
isVirtualConfigFile,
|
|
9
|
+
getRouteNodes as physicalGetRouteNodes,
|
|
10
|
+
} from './filesystem/physical/getRouteNodes'
|
|
8
11
|
import { getRouteNodes as virtualGetRouteNodes } from './filesystem/virtual/getRouteNodes'
|
|
9
12
|
import { rootPathId } from './filesystem/physical/rootPathId'
|
|
10
13
|
import {
|
|
14
|
+
buildFileRoutesByPathInterface,
|
|
11
15
|
buildImportString,
|
|
12
16
|
buildRouteTreeConfig,
|
|
13
17
|
checkFileExists,
|
|
@@ -19,8 +23,6 @@ import {
|
|
|
19
23
|
format,
|
|
20
24
|
getResolvedRouteNodeVariableName,
|
|
21
25
|
hasParentRoute,
|
|
22
|
-
inferFullPath,
|
|
23
|
-
inferPath,
|
|
24
26
|
isRouteNodeValidForAugmentation,
|
|
25
27
|
lowerCaseFirstChar,
|
|
26
28
|
mergeImportDeclarations,
|
|
@@ -46,6 +48,7 @@ import type { TargetTemplate } from './template'
|
|
|
46
48
|
import type {
|
|
47
49
|
FsRouteType,
|
|
48
50
|
GetRouteNodesResult,
|
|
51
|
+
GetRoutesByFileMapResult,
|
|
49
52
|
HandleNodeAccumulator,
|
|
50
53
|
ImportDeclaration,
|
|
51
54
|
RouteNode,
|
|
@@ -55,8 +58,9 @@ import type { Logger } from './logger'
|
|
|
55
58
|
import type { TransformPlugin } from './transform/types'
|
|
56
59
|
|
|
57
60
|
interface fs {
|
|
58
|
-
stat: (
|
|
59
|
-
|
|
61
|
+
stat: (
|
|
62
|
+
filePath: string,
|
|
63
|
+
) => Promise<{ mtimeMs: bigint; mode: number; uid: number; gid: number }>
|
|
60
64
|
rename: (oldPath: string, newPath: string) => Promise<void>
|
|
61
65
|
writeFile: (filePath: string, content: string) => Promise<void>
|
|
62
66
|
readFile: (
|
|
@@ -64,11 +68,20 @@ interface fs {
|
|
|
64
68
|
) => Promise<
|
|
65
69
|
{ stat: { mtimeMs: bigint }; fileContent: string } | 'file-not-existing'
|
|
66
70
|
>
|
|
71
|
+
chmod: (filePath: string, mode: number) => Promise<void>
|
|
72
|
+
chown: (filePath: string, uid: number, gid: number) => Promise<void>
|
|
67
73
|
}
|
|
68
74
|
|
|
69
75
|
const DefaultFileSystem: fs = {
|
|
70
|
-
stat: (filePath) =>
|
|
71
|
-
|
|
76
|
+
stat: async (filePath) => {
|
|
77
|
+
const res = await fsp.stat(filePath, { bigint: true })
|
|
78
|
+
return {
|
|
79
|
+
mtimeMs: res.mtimeMs,
|
|
80
|
+
mode: Number(res.mode),
|
|
81
|
+
uid: Number(res.uid),
|
|
82
|
+
gid: Number(res.gid),
|
|
83
|
+
}
|
|
84
|
+
},
|
|
72
85
|
rename: (oldPath, newPath) => fsp.rename(oldPath, newPath),
|
|
73
86
|
writeFile: (filePath, content) => fsp.writeFile(filePath, content),
|
|
74
87
|
readFile: async (filePath: string) => {
|
|
@@ -87,6 +100,8 @@ const DefaultFileSystem: fs = {
|
|
|
87
100
|
throw e
|
|
88
101
|
}
|
|
89
102
|
},
|
|
103
|
+
chmod: (filePath, mode) => fsp.chmod(filePath, mode),
|
|
104
|
+
chown: (filePath, uid, gid) => fsp.chown(filePath, uid, gid),
|
|
90
105
|
}
|
|
91
106
|
|
|
92
107
|
interface Rerun {
|
|
@@ -135,6 +150,7 @@ interface GeneratorCacheEntry {
|
|
|
135
150
|
|
|
136
151
|
interface RouteNodeCacheEntry extends GeneratorCacheEntry {
|
|
137
152
|
exports: Array<string>
|
|
153
|
+
routeId: string
|
|
138
154
|
}
|
|
139
155
|
|
|
140
156
|
type GeneratorRouteNodeCache = Map</** filePath **/ string, RouteNodeCacheEntry>
|
|
@@ -160,7 +176,7 @@ export class Generator {
|
|
|
160
176
|
|
|
161
177
|
private root: string
|
|
162
178
|
private routesDirectoryPath: string
|
|
163
|
-
private
|
|
179
|
+
private sessionId?: string
|
|
164
180
|
private fs: fs
|
|
165
181
|
private logger: Logger
|
|
166
182
|
private generatedRouteTreePath: string
|
|
@@ -171,15 +187,13 @@ export class Generator {
|
|
|
171
187
|
// this is just a cache for the transform plugins since we need them for each route file that is to be processed
|
|
172
188
|
private transformPlugins: Array<TransformPlugin> = []
|
|
173
189
|
private routeGroupPatternRegex = /\(.+\)/g
|
|
190
|
+
private physicalDirectories: Array<string> = []
|
|
174
191
|
|
|
175
192
|
constructor(opts: { config: Config; root: string; fs?: fs }) {
|
|
176
193
|
this.config = opts.config
|
|
177
194
|
this.logger = logging({ disabled: this.config.disableLogging })
|
|
178
195
|
this.root = opts.root
|
|
179
196
|
this.fs = opts.fs || DefaultFileSystem
|
|
180
|
-
this.tmpDir = this.fs.mkdtempSync(
|
|
181
|
-
path.join(this.config.tmpDir, 'router-generator-'),
|
|
182
|
-
)
|
|
183
197
|
this.generatedRouteTreePath = path.resolve(this.config.generatedRouteTree)
|
|
184
198
|
this.targetTemplate = getTargetTemplate(this.config)
|
|
185
199
|
|
|
@@ -204,17 +218,22 @@ export class Generator {
|
|
|
204
218
|
: path.resolve(this.root, this.config.routesDirectory)
|
|
205
219
|
}
|
|
206
220
|
|
|
221
|
+
public getRoutesByFileMap(): GetRoutesByFileMapResult {
|
|
222
|
+
return new Map(
|
|
223
|
+
[...this.routeNodeCache.entries()].map(([filePath, cacheEntry]) => [
|
|
224
|
+
filePath,
|
|
225
|
+
{ routePath: cacheEntry.routeId },
|
|
226
|
+
]),
|
|
227
|
+
)
|
|
228
|
+
}
|
|
229
|
+
|
|
207
230
|
public async run(event?: GeneratorEvent): Promise<void> {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
)
|
|
215
|
-
) {
|
|
216
|
-
return
|
|
217
|
-
}
|
|
231
|
+
if (
|
|
232
|
+
event &&
|
|
233
|
+
event.type !== 'rerun' &&
|
|
234
|
+
!this.isFileRelevantForRouteTreeGeneration(event.path)
|
|
235
|
+
) {
|
|
236
|
+
return
|
|
218
237
|
}
|
|
219
238
|
this.fileEventQueue.push(event ?? { type: 'rerun' })
|
|
220
239
|
// only allow a single run at a time
|
|
@@ -234,7 +253,7 @@ export class Generator {
|
|
|
234
253
|
await Promise.all(
|
|
235
254
|
tempQueue.map(async (e) => {
|
|
236
255
|
if (e.type === 'update') {
|
|
237
|
-
let cacheEntry
|
|
256
|
+
let cacheEntry: GeneratorCacheEntry | undefined
|
|
238
257
|
if (e.path === this.generatedRouteTreePath) {
|
|
239
258
|
cacheEntry = this.routeTreeFileCache
|
|
240
259
|
} else {
|
|
@@ -292,7 +311,7 @@ export class Generator {
|
|
|
292
311
|
}
|
|
293
312
|
|
|
294
313
|
private async generatorInternal() {
|
|
295
|
-
let writeRouteTreeFile = false
|
|
314
|
+
let writeRouteTreeFile: boolean | 'force' = false
|
|
296
315
|
|
|
297
316
|
let getRouteNodesResult: GetRouteNodesResult
|
|
298
317
|
|
|
@@ -302,7 +321,11 @@ export class Generator {
|
|
|
302
321
|
getRouteNodesResult = await physicalGetRouteNodes(this.config, this.root)
|
|
303
322
|
}
|
|
304
323
|
|
|
305
|
-
const {
|
|
324
|
+
const {
|
|
325
|
+
rootRouteNode,
|
|
326
|
+
routeNodes: beforeRouteNodes,
|
|
327
|
+
physicalDirectories,
|
|
328
|
+
} = getRouteNodesResult
|
|
306
329
|
if (rootRouteNode === undefined) {
|
|
307
330
|
let errorMessage = `rootRouteNode must not be undefined. Make sure you've added your root route into the route-tree.`
|
|
308
331
|
if (!this.config.virtualRouteConfig) {
|
|
@@ -310,6 +333,7 @@ export class Generator {
|
|
|
310
333
|
}
|
|
311
334
|
throw new Error(errorMessage)
|
|
312
335
|
}
|
|
336
|
+
this.physicalDirectories = physicalDirectories
|
|
313
337
|
|
|
314
338
|
writeRouteTreeFile = await this.handleRootNode(rootRouteNode)
|
|
315
339
|
|
|
@@ -376,9 +400,40 @@ export class Generator {
|
|
|
376
400
|
}
|
|
377
401
|
}
|
|
378
402
|
writeRouteTreeFile = true
|
|
403
|
+
} else {
|
|
404
|
+
const routeTreeFileChange = await this.didFileChangeComparedToCache(
|
|
405
|
+
{ path: this.generatedRouteTreePath },
|
|
406
|
+
this.routeTreeFileCache,
|
|
407
|
+
)
|
|
408
|
+
if (routeTreeFileChange.result !== false) {
|
|
409
|
+
writeRouteTreeFile = 'force'
|
|
410
|
+
if (routeTreeFileChange.result === true) {
|
|
411
|
+
const routeTreeFile = await this.fs.readFile(
|
|
412
|
+
this.generatedRouteTreePath,
|
|
413
|
+
)
|
|
414
|
+
if (routeTreeFile !== 'file-not-existing') {
|
|
415
|
+
this.routeTreeFileCache = {
|
|
416
|
+
fileContent: routeTreeFile.fileContent,
|
|
417
|
+
mtimeMs: routeTreeFile.stat.mtimeMs,
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
379
422
|
}
|
|
380
423
|
|
|
381
424
|
if (!writeRouteTreeFile) {
|
|
425
|
+
// only needs to be done if no other changes have been detected yet
|
|
426
|
+
// compare shadowCache and cache to identify deleted routes
|
|
427
|
+
for (const fullPath of this.routeNodeCache.keys()) {
|
|
428
|
+
if (!this.routeNodeShadowCache.has(fullPath)) {
|
|
429
|
+
writeRouteTreeFile = true
|
|
430
|
+
break
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if (!writeRouteTreeFile) {
|
|
436
|
+
this.swapCaches()
|
|
382
437
|
return
|
|
383
438
|
}
|
|
384
439
|
|
|
@@ -393,7 +448,10 @@ export class Generator {
|
|
|
393
448
|
|
|
394
449
|
let newMtimeMs: bigint | undefined
|
|
395
450
|
if (this.routeTreeFileCache) {
|
|
396
|
-
if (
|
|
451
|
+
if (
|
|
452
|
+
writeRouteTreeFile !== 'force' &&
|
|
453
|
+
this.routeTreeFileCache.fileContent === routeTreeContent
|
|
454
|
+
) {
|
|
397
455
|
// existing route tree file is already up-to-date, don't write it
|
|
398
456
|
// we should only get here in the initial run when the route cache is not filled yet
|
|
399
457
|
} else {
|
|
@@ -425,7 +483,10 @@ export class Generator {
|
|
|
425
483
|
}
|
|
426
484
|
}
|
|
427
485
|
|
|
428
|
-
|
|
486
|
+
this.swapCaches()
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
private swapCaches() {
|
|
429
490
|
this.routeNodeCache = this.routeNodeShadowCache
|
|
430
491
|
this.routeNodeShadowCache = new Map()
|
|
431
492
|
}
|
|
@@ -602,16 +663,18 @@ export class Generator {
|
|
|
602
663
|
if (!this.config.disableTypes && hasMatchingRouteFiles) {
|
|
603
664
|
fileRoutesByFullPathPerPlugin = [
|
|
604
665
|
`export interface File${exportName}sByFullPath {
|
|
605
|
-
${[...createRouteNodesByFullPath(acc.routeNodes).entries()]
|
|
606
|
-
([fullPath
|
|
666
|
+
${[...createRouteNodesByFullPath(acc.routeNodes).entries()]
|
|
667
|
+
.filter(([fullPath]) => fullPath)
|
|
668
|
+
.map(([fullPath, routeNode]) => {
|
|
607
669
|
return `'${fullPath}': typeof ${getResolvedRouteNodeVariableName(routeNode, exportName)}`
|
|
608
|
-
}
|
|
609
|
-
)}
|
|
670
|
+
})}
|
|
610
671
|
}`,
|
|
611
672
|
`export interface File${exportName}sByTo {
|
|
612
|
-
${[...createRouteNodesByTo(acc.routeNodes).entries()]
|
|
613
|
-
|
|
614
|
-
|
|
673
|
+
${[...createRouteNodesByTo(acc.routeNodes).entries()]
|
|
674
|
+
.filter(([to]) => to)
|
|
675
|
+
.map(([to, routeNode]) => {
|
|
676
|
+
return `'${to}': typeof ${getResolvedRouteNodeVariableName(routeNode, exportName)}`
|
|
677
|
+
})}
|
|
615
678
|
}`,
|
|
616
679
|
`export interface File${exportName}sById {
|
|
617
680
|
'${rootRouteId}': typeof root${exportName}Import,
|
|
@@ -621,9 +684,23 @@ ${[...createRouteNodesById(acc.routeNodes).entries()].map(([id, routeNode]) => {
|
|
|
621
684
|
}`,
|
|
622
685
|
`export interface File${exportName}Types {
|
|
623
686
|
file${exportName}sByFullPath: File${exportName}sByFullPath
|
|
624
|
-
fullPaths: ${
|
|
687
|
+
fullPaths: ${
|
|
688
|
+
acc.routeNodes.length > 0
|
|
689
|
+
? [...createRouteNodesByFullPath(acc.routeNodes).keys()]
|
|
690
|
+
.filter((fullPath) => fullPath)
|
|
691
|
+
.map((fullPath) => `'${fullPath}'`)
|
|
692
|
+
.join('|')
|
|
693
|
+
: 'never'
|
|
694
|
+
}
|
|
625
695
|
file${exportName}sByTo: File${exportName}sByTo
|
|
626
|
-
to: ${
|
|
696
|
+
to: ${
|
|
697
|
+
acc.routeNodes.length > 0
|
|
698
|
+
? [...createRouteNodesByTo(acc.routeNodes).keys()]
|
|
699
|
+
.filter((to) => to)
|
|
700
|
+
.map((to) => `'${to}'`)
|
|
701
|
+
.join('|')
|
|
702
|
+
: 'never'
|
|
703
|
+
}
|
|
627
704
|
id: ${[`'${rootRouteId}'`, ...[...createRouteNodesById(acc.routeNodes).keys()].map((id) => `'${id}'`)].join('|')}
|
|
628
705
|
file${exportName}sById: File${exportName}sById
|
|
629
706
|
}`,
|
|
@@ -634,7 +711,13 @@ ${acc.routeTree.map((child) => `${child.variableName}${exportName}: typeof ${get
|
|
|
634
711
|
|
|
635
712
|
fileRoutesByPathInterfacePerPlugin = buildFileRoutesByPathInterface({
|
|
636
713
|
...plugin.moduleAugmentation({ generator: this }),
|
|
637
|
-
routeNodes:
|
|
714
|
+
routeNodes:
|
|
715
|
+
this.config.verboseFileRoutes !== false
|
|
716
|
+
? sortedRouteNodes
|
|
717
|
+
: [
|
|
718
|
+
...routeFileResult.map(({ node }) => node),
|
|
719
|
+
...sortedRouteNodes.filter((d) => d.isVirtual),
|
|
720
|
+
],
|
|
638
721
|
exportName,
|
|
639
722
|
})
|
|
640
723
|
}
|
|
@@ -784,6 +867,7 @@ ${acc.routeTree.map((child) => `${child.variableName}${exportName}: typeof ${get
|
|
|
784
867
|
fileContent: existingRouteFile.fileContent,
|
|
785
868
|
mtimeMs: existingRouteFile.stat.mtimeMs,
|
|
786
869
|
exports: [],
|
|
870
|
+
routeId: node.routePath ?? '$$TSR_NO_ROUTE_PATH_ASSIGNED$$',
|
|
787
871
|
}
|
|
788
872
|
|
|
789
873
|
const escapedRoutePath = node.routePath?.replaceAll('$', '$$') ?? ''
|
|
@@ -804,7 +888,7 @@ ${acc.routeTree.map((child) => `${child.variableName}${exportName}: typeof ${get
|
|
|
804
888
|
tLazyRouteTemplate.template(),
|
|
805
889
|
{
|
|
806
890
|
tsrImports: tLazyRouteTemplate.imports.tsrImports(),
|
|
807
|
-
tsrPath: escapedRoutePath.replaceAll(/\{(
|
|
891
|
+
tsrPath: escapedRoutePath.replaceAll(/\{(.+?)\}/gm, '$1'),
|
|
808
892
|
tsrExportStart:
|
|
809
893
|
tLazyRouteTemplate.imports.tsrExportStart(escapedRoutePath),
|
|
810
894
|
tsrExportEnd: tLazyRouteTemplate.imports.tsrExportEnd(),
|
|
@@ -832,7 +916,7 @@ ${acc.routeTree.map((child) => `${child.variableName}${exportName}: typeof ${get
|
|
|
832
916
|
tRouteTemplate.template(),
|
|
833
917
|
{
|
|
834
918
|
tsrImports: tRouteTemplate.imports.tsrImports(),
|
|
835
|
-
tsrPath: escapedRoutePath.replaceAll(/\{(
|
|
919
|
+
tsrPath: escapedRoutePath.replaceAll(/\{(.+?)\}/gm, '$1'),
|
|
836
920
|
tsrExportStart:
|
|
837
921
|
tRouteTemplate.imports.tsrExportStart(escapedRoutePath),
|
|
838
922
|
tsrExportEnd: tRouteTemplate.imports.tsrExportEnd(),
|
|
@@ -956,6 +1040,31 @@ ${acc.routeTree.map((child) => `${child.variableName}${exportName}: typeof ${get
|
|
|
956
1040
|
event: { type: 'update', path: opts.filePath },
|
|
957
1041
|
})
|
|
958
1042
|
}
|
|
1043
|
+
const newFileState = await this.fs.stat(tmpPath)
|
|
1044
|
+
if (newFileState.mode !== beforeStat.mode) {
|
|
1045
|
+
await this.fs.chmod(tmpPath, beforeStat.mode)
|
|
1046
|
+
}
|
|
1047
|
+
if (
|
|
1048
|
+
newFileState.uid !== beforeStat.uid ||
|
|
1049
|
+
newFileState.gid !== beforeStat.gid
|
|
1050
|
+
) {
|
|
1051
|
+
try {
|
|
1052
|
+
await this.fs.chown(tmpPath, beforeStat.uid, beforeStat.gid)
|
|
1053
|
+
} catch (err) {
|
|
1054
|
+
if (
|
|
1055
|
+
typeof err === 'object' &&
|
|
1056
|
+
err !== null &&
|
|
1057
|
+
'code' in err &&
|
|
1058
|
+
(err as any).code === 'EPERM'
|
|
1059
|
+
) {
|
|
1060
|
+
console.warn(
|
|
1061
|
+
`[safeFileWrite] chown failed: ${(err as any).message}`,
|
|
1062
|
+
)
|
|
1063
|
+
} else {
|
|
1064
|
+
throw err
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
959
1068
|
} else {
|
|
960
1069
|
if (await checkFileExists(opts.filePath)) {
|
|
961
1070
|
throw rerun({
|
|
@@ -975,7 +1084,13 @@ ${acc.routeTree.map((child) => `${child.variableName}${exportName}: typeof ${get
|
|
|
975
1084
|
private getTempFileName(filePath: string) {
|
|
976
1085
|
const absPath = path.resolve(filePath)
|
|
977
1086
|
const hash = crypto.createHash('md5').update(absPath).digest('hex')
|
|
978
|
-
|
|
1087
|
+
// lazy initialize sessionId to only create tmpDir when it is first needed
|
|
1088
|
+
if (!this.sessionId) {
|
|
1089
|
+
// ensure the directory exists
|
|
1090
|
+
mkdirSync(this.config.tmpDir, { recursive: true })
|
|
1091
|
+
this.sessionId = crypto.randomBytes(4).toString('hex')
|
|
1092
|
+
}
|
|
1093
|
+
return path.join(this.config.tmpDir, `${this.sessionId}-${hash}`)
|
|
979
1094
|
}
|
|
980
1095
|
|
|
981
1096
|
private async isRouteFileCacheFresh(node: RouteNode): Promise<
|
|
@@ -1063,6 +1178,7 @@ ${acc.routeTree.map((child) => `${child.variableName}${exportName}: typeof ${get
|
|
|
1063
1178
|
fileContent: rootNodeFile.fileContent,
|
|
1064
1179
|
mtimeMs: rootNodeFile.stat.mtimeMs,
|
|
1065
1180
|
exports: [],
|
|
1181
|
+
routeId: node.routePath ?? '$$TSR_NO_ROOT_ROUTE_PATH_ASSIGNED$$',
|
|
1066
1182
|
}
|
|
1067
1183
|
|
|
1068
1184
|
// scaffold the root route
|
|
@@ -1095,6 +1211,8 @@ ${acc.routeTree.map((child) => `${child.variableName}${exportName}: typeof ${get
|
|
|
1095
1211
|
const rootRouteExports: Array<string> = []
|
|
1096
1212
|
for (const plugin of this.pluginsWithTransform) {
|
|
1097
1213
|
const exportName = plugin.transformPlugin.exportName
|
|
1214
|
+
// TODO we need to parse instead of just string match
|
|
1215
|
+
// otherwise a commented out export will still be detected
|
|
1098
1216
|
if (rootNodeFile.fileContent.includes(`export const ${exportName}`)) {
|
|
1099
1217
|
rootRouteExports.push(exportName)
|
|
1100
1218
|
}
|
|
@@ -1143,7 +1261,7 @@ ${acc.routeTree.map((child) => `${child.variableName}${exportName}: typeof ${get
|
|
|
1143
1261
|
|
|
1144
1262
|
node.isNonPath =
|
|
1145
1263
|
lastRouteSegment.startsWith('_') ||
|
|
1146
|
-
this.routeGroupPatternRegex.test(
|
|
1264
|
+
split.every((part) => this.routeGroupPatternRegex.test(part))
|
|
1147
1265
|
|
|
1148
1266
|
node.cleanedPath = removeGroups(
|
|
1149
1267
|
removeUnderscores(removeLayoutSegments(node.path)) ?? '',
|
|
@@ -1254,38 +1372,42 @@ ${acc.routeTree.map((child) => `${child.variableName}${exportName}: typeof ${get
|
|
|
1254
1372
|
|
|
1255
1373
|
acc.routeNodes.push(node)
|
|
1256
1374
|
}
|
|
1257
|
-
}
|
|
1258
1375
|
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
}
|
|
1265
|
-
return `declare module '${opts.module}' {
|
|
1266
|
-
interface ${opts.interfaceName} {
|
|
1267
|
-
${opts.routeNodes
|
|
1268
|
-
.map((routeNode) => {
|
|
1269
|
-
const filePathId = routeNode.routePath
|
|
1270
|
-
let preloaderRoute = ''
|
|
1271
|
-
|
|
1272
|
-
if (routeNode.exports?.includes(opts.exportName)) {
|
|
1273
|
-
preloaderRoute = `typeof ${routeNode.variableName}${opts.exportName}Import`
|
|
1274
|
-
} else {
|
|
1275
|
-
preloaderRoute = 'unknown'
|
|
1276
|
-
}
|
|
1376
|
+
// only process files that are relevant for the route tree generation
|
|
1377
|
+
private isFileRelevantForRouteTreeGeneration(filePath: string): boolean {
|
|
1378
|
+
// the generated route tree file
|
|
1379
|
+
if (filePath === this.generatedRouteTreePath) {
|
|
1380
|
+
return true
|
|
1381
|
+
}
|
|
1277
1382
|
|
|
1278
|
-
|
|
1383
|
+
// files inside the routes folder
|
|
1384
|
+
if (filePath.startsWith(this.routesDirectoryPath)) {
|
|
1385
|
+
return true
|
|
1386
|
+
}
|
|
1279
1387
|
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1388
|
+
// the virtual route config file passed into `virtualRouteConfig`
|
|
1389
|
+
if (
|
|
1390
|
+
typeof this.config.virtualRouteConfig === 'string' &&
|
|
1391
|
+
filePath === this.config.virtualRouteConfig
|
|
1392
|
+
) {
|
|
1393
|
+
return true
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
// this covers all files that are mounted via `virtualRouteConfig` or any `__virtual.ts` files
|
|
1397
|
+
if (this.routeNodeCache.has(filePath)) {
|
|
1398
|
+
return true
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
// virtual config files such as`__virtual.ts`
|
|
1402
|
+
if (isVirtualConfigFile(path.basename(filePath))) {
|
|
1403
|
+
return true
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
// route files inside directories mounted via `physical()` inside a virtual route config
|
|
1407
|
+
if (this.physicalDirectories.some((dir) => filePath.startsWith(dir))) {
|
|
1408
|
+
return true
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
return false
|
|
1289
1412
|
}
|
|
1290
|
-
}`
|
|
1291
1413
|
}
|
package/src/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
import type { ImportDeclaration } from '../types'
|
|
7
7
|
import type { GeneratorPluginWithTransform } from './types'
|
|
8
8
|
|
|
9
|
+
const EXPORT_NAME = 'Route'
|
|
9
10
|
export function defaultGeneratorPlugin(): GeneratorPluginWithTransform {
|
|
10
11
|
return {
|
|
11
12
|
name: 'default',
|
|
@@ -47,6 +48,16 @@ export function defaultGeneratorPlugin(): GeneratorPluginWithTransform {
|
|
|
47
48
|
imports.push(typeImport)
|
|
48
49
|
}
|
|
49
50
|
}
|
|
51
|
+
const hasMatchingRouteFiles = opts.acc.routeNodes.length > 0
|
|
52
|
+
if (hasMatchingRouteFiles) {
|
|
53
|
+
// needs a virtual root route
|
|
54
|
+
if (!opts.rootRouteNode.exports?.includes(EXPORT_NAME)) {
|
|
55
|
+
imports.push({
|
|
56
|
+
specifiers: [{ imported: 'createRootRoute' }],
|
|
57
|
+
source: opts.generator.targetTemplate.fullPkg,
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
}
|
|
50
61
|
return imports
|
|
51
62
|
},
|
|
52
63
|
moduleAugmentation: ({ generator }) => ({
|
|
@@ -54,7 +65,9 @@ export function defaultGeneratorPlugin(): GeneratorPluginWithTransform {
|
|
|
54
65
|
interfaceName: 'FileRoutesByPath',
|
|
55
66
|
}),
|
|
56
67
|
onRouteTreesChanged: ({ routeTrees, generator }) => {
|
|
57
|
-
const routeTree = routeTrees.find(
|
|
68
|
+
const routeTree = routeTrees.find(
|
|
69
|
+
(tree) => tree.exportName === EXPORT_NAME,
|
|
70
|
+
)
|
|
58
71
|
if (!routeTree) {
|
|
59
72
|
throw new Error(
|
|
60
73
|
'No route tree found with export name "Route". Please ensure your routes are correctly defined.',
|
|
@@ -65,7 +78,7 @@ export function defaultGeneratorPlugin(): GeneratorPluginWithTransform {
|
|
|
65
78
|
(d) =>
|
|
66
79
|
d.children === undefined &&
|
|
67
80
|
'lazy' !== d._fsRouteType &&
|
|
68
|
-
d.exports?.includes(
|
|
81
|
+
d.exports?.includes(EXPORT_NAME),
|
|
69
82
|
),
|
|
70
83
|
generator.config,
|
|
71
84
|
)
|
|
@@ -83,7 +96,7 @@ export function defaultGeneratorPlugin(): GeneratorPluginWithTransform {
|
|
|
83
96
|
`
|
|
84
97
|
}
|
|
85
98
|
},
|
|
86
|
-
createRootRouteCode: () => `
|
|
99
|
+
createRootRouteCode: () => `createRootRoute()`,
|
|
87
100
|
createVirtualRouteCode: ({ node }) =>
|
|
88
101
|
`createFileRoute('${node.routePath}')()`,
|
|
89
102
|
config: ({ sortedRouteNodes }) => {
|
|
@@ -4,9 +4,10 @@ import type { TransformImportsConfig, TransformPlugin } from './types'
|
|
|
4
4
|
|
|
5
5
|
const b = types.builders
|
|
6
6
|
|
|
7
|
+
const EXPORT_NAME = 'Route'
|
|
7
8
|
export const defaultTransformPlugin: TransformPlugin = {
|
|
8
9
|
name: 'default-transform',
|
|
9
|
-
exportName:
|
|
10
|
+
exportName: EXPORT_NAME,
|
|
10
11
|
imports: (ctx) => {
|
|
11
12
|
const imports: TransformImportsConfig = {}
|
|
12
13
|
const targetModule = `@tanstack/${ctx.target}-router`
|
|
@@ -87,7 +88,9 @@ export const defaultTransformPlugin: TransformPlugin = {
|
|
|
87
88
|
}
|
|
88
89
|
}
|
|
89
90
|
if (identifier === undefined) {
|
|
90
|
-
throw new Error(
|
|
91
|
+
throw new Error(
|
|
92
|
+
`expected identifier to be present in ${ctx.routeId} for export ${EXPORT_NAME}`,
|
|
93
|
+
)
|
|
91
94
|
}
|
|
92
95
|
if (identifier.name === 'createFileRoute' && ctx.lazy) {
|
|
93
96
|
identifier.name = 'createLazyFileRoute'
|
|
@@ -56,35 +56,69 @@ export async function transform({
|
|
|
56
56
|
registeredExports.set(exportName, plugin)
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
function onExportFound(
|
|
60
|
+
decl: types.namedTypes.VariableDeclarator,
|
|
61
|
+
exportName: string,
|
|
62
|
+
plugin: TransformPlugin,
|
|
63
|
+
) {
|
|
64
|
+
const pluginAppliedChanges = plugin.onExportFound({
|
|
65
|
+
decl,
|
|
66
|
+
ctx: { ...ctx, preferredQuote },
|
|
67
|
+
})
|
|
68
|
+
if (pluginAppliedChanges) {
|
|
69
|
+
appliedChanges = true
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// export is handled, remove it from the registered exports
|
|
73
|
+
registeredExports.delete(exportName)
|
|
74
|
+
// store the export so we can later return it once the file is transformed
|
|
75
|
+
foundExports.push(exportName)
|
|
76
|
+
}
|
|
77
|
+
|
|
59
78
|
const program: types.namedTypes.Program = ast.program
|
|
60
79
|
// first pass: find registered exports
|
|
61
80
|
for (const n of program.body) {
|
|
62
|
-
if (
|
|
63
|
-
|
|
64
|
-
n.type === '
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
if (
|
|
81
|
-
|
|
81
|
+
if (registeredExports.size > 0 && n.type === 'ExportNamedDeclaration') {
|
|
82
|
+
// direct export of a variable declaration, e.g. `export const Route = createFileRoute('/path')`
|
|
83
|
+
if (n.declaration?.type === 'VariableDeclaration') {
|
|
84
|
+
const decl = n.declaration.declarations[0]
|
|
85
|
+
if (
|
|
86
|
+
decl &&
|
|
87
|
+
decl.type === 'VariableDeclarator' &&
|
|
88
|
+
decl.id.type === 'Identifier'
|
|
89
|
+
) {
|
|
90
|
+
const plugin = registeredExports.get(decl.id.name)
|
|
91
|
+
if (plugin) {
|
|
92
|
+
onExportFound(decl, decl.id.name, plugin)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// this is an export without a declaration, e.g. `export { Route }`
|
|
97
|
+
else if (n.declaration === null && n.specifiers) {
|
|
98
|
+
for (const spec of n.specifiers) {
|
|
99
|
+
if (typeof spec.exported.name === 'string') {
|
|
100
|
+
const plugin = registeredExports.get(spec.exported.name)
|
|
101
|
+
if (plugin) {
|
|
102
|
+
const variableName = spec.local?.name || spec.exported.name
|
|
103
|
+
// find the matching variable declaration by iterating over the top-level declarations
|
|
104
|
+
for (const decl of program.body) {
|
|
105
|
+
if (
|
|
106
|
+
decl.type === 'VariableDeclaration' &&
|
|
107
|
+
decl.declarations[0]
|
|
108
|
+
) {
|
|
109
|
+
const variable = decl.declarations[0]
|
|
110
|
+
if (
|
|
111
|
+
variable.type === 'VariableDeclarator' &&
|
|
112
|
+
variable.id.type === 'Identifier' &&
|
|
113
|
+
variable.id.name === variableName
|
|
114
|
+
) {
|
|
115
|
+
onExportFound(variable, spec.exported.name, plugin)
|
|
116
|
+
break
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
82
121
|
}
|
|
83
|
-
|
|
84
|
-
// export is handled, remove it from the registered exports
|
|
85
|
-
registeredExports.delete(decl.id.name)
|
|
86
|
-
// store the export so we can later return it once the file is transformed
|
|
87
|
-
foundExports.push(decl.id.name)
|
|
88
122
|
}
|
|
89
123
|
}
|
|
90
124
|
}
|
package/src/types.ts
CHANGED
|
@@ -18,6 +18,7 @@ export type RouteNode = {
|
|
|
18
18
|
export interface GetRouteNodesResult {
|
|
19
19
|
rootRouteNode?: RouteNode
|
|
20
20
|
routeNodes: Array<RouteNode>
|
|
21
|
+
physicalDirectories: Array<string>
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
export type FsRouteType =
|
|
@@ -54,3 +55,9 @@ export type HandleNodeAccumulator = {
|
|
|
54
55
|
routePiecesByPath: Record<string, RouteSubNode>
|
|
55
56
|
routeNodes: Array<RouteNode>
|
|
56
57
|
}
|
|
58
|
+
|
|
59
|
+
export type GetRoutesByFileMapResultValue = { routePath: string }
|
|
60
|
+
export type GetRoutesByFileMapResult = Map<
|
|
61
|
+
string,
|
|
62
|
+
GetRoutesByFileMapResultValue
|
|
63
|
+
>
|