@tanstack/router-generator 1.121.0-alpha.22 → 1.121.0-alpha.27

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 (90) hide show
  1. package/dist/cjs/config.cjs +23 -5
  2. package/dist/cjs/config.cjs.map +1 -1
  3. package/dist/cjs/config.d.cts +7 -3
  4. package/dist/cjs/filesystem/physical/getRouteNodes.cjs +5 -3
  5. package/dist/cjs/filesystem/physical/getRouteNodes.cjs.map +1 -1
  6. package/dist/cjs/filesystem/virtual/getRouteNodes.cjs +1 -1
  7. package/dist/cjs/filesystem/virtual/getRouteNodes.cjs.map +1 -1
  8. package/dist/cjs/generator.cjs +828 -665
  9. package/dist/cjs/generator.cjs.map +1 -1
  10. package/dist/cjs/generator.d.cts +78 -1
  11. package/dist/cjs/index.cjs +5 -2
  12. package/dist/cjs/index.cjs.map +1 -1
  13. package/dist/cjs/index.d.cts +7 -3
  14. package/dist/cjs/logger.cjs +37 -0
  15. package/dist/cjs/logger.cjs.map +1 -0
  16. package/dist/cjs/logger.d.cts +10 -0
  17. package/dist/cjs/plugin/default-generator-plugin.cjs +88 -0
  18. package/dist/cjs/plugin/default-generator-plugin.cjs.map +1 -0
  19. package/dist/cjs/plugin/default-generator-plugin.d.cts +2 -0
  20. package/dist/cjs/plugin/types.d.cts +46 -0
  21. package/dist/cjs/template.cjs +10 -10
  22. package/dist/cjs/template.cjs.map +1 -1
  23. package/dist/cjs/template.d.cts +2 -2
  24. package/dist/cjs/transform/default-transform-plugin.cjs +95 -0
  25. package/dist/cjs/transform/default-transform-plugin.cjs.map +1 -0
  26. package/dist/cjs/transform/default-transform-plugin.d.cts +2 -0
  27. package/dist/cjs/transform/transform.cjs +351 -0
  28. package/dist/cjs/transform/transform.cjs.map +1 -0
  29. package/dist/cjs/transform/transform.d.cts +4 -0
  30. package/dist/cjs/transform/types.d.cts +43 -0
  31. package/dist/cjs/transform/utils.cjs +36 -0
  32. package/dist/cjs/transform/utils.cjs.map +1 -0
  33. package/dist/cjs/transform/utils.d.cts +2 -0
  34. package/dist/cjs/types.d.cts +22 -0
  35. package/dist/cjs/utils.cjs +237 -40
  36. package/dist/cjs/utils.cjs.map +1 -1
  37. package/dist/cjs/utils.d.cts +76 -9
  38. package/dist/esm/config.d.ts +7 -3
  39. package/dist/esm/config.js +21 -3
  40. package/dist/esm/config.js.map +1 -1
  41. package/dist/esm/filesystem/physical/getRouteNodes.js +3 -1
  42. package/dist/esm/filesystem/physical/getRouteNodes.js.map +1 -1
  43. package/dist/esm/filesystem/virtual/getRouteNodes.js +1 -1
  44. package/dist/esm/filesystem/virtual/getRouteNodes.js.map +1 -1
  45. package/dist/esm/generator.d.ts +78 -1
  46. package/dist/esm/generator.js +817 -653
  47. package/dist/esm/generator.js.map +1 -1
  48. package/dist/esm/index.d.ts +7 -3
  49. package/dist/esm/index.js +7 -4
  50. package/dist/esm/index.js.map +1 -1
  51. package/dist/esm/logger.d.ts +10 -0
  52. package/dist/esm/logger.js +37 -0
  53. package/dist/esm/logger.js.map +1 -0
  54. package/dist/esm/plugin/default-generator-plugin.d.ts +2 -0
  55. package/dist/esm/plugin/default-generator-plugin.js +88 -0
  56. package/dist/esm/plugin/default-generator-plugin.js.map +1 -0
  57. package/dist/esm/plugin/types.d.ts +46 -0
  58. package/dist/esm/template.d.ts +2 -2
  59. package/dist/esm/template.js +10 -10
  60. package/dist/esm/template.js.map +1 -1
  61. package/dist/esm/transform/default-transform-plugin.d.ts +2 -0
  62. package/dist/esm/transform/default-transform-plugin.js +95 -0
  63. package/dist/esm/transform/default-transform-plugin.js.map +1 -0
  64. package/dist/esm/transform/transform.d.ts +4 -0
  65. package/dist/esm/transform/transform.js +351 -0
  66. package/dist/esm/transform/transform.js.map +1 -0
  67. package/dist/esm/transform/types.d.ts +43 -0
  68. package/dist/esm/transform/utils.d.ts +2 -0
  69. package/dist/esm/transform/utils.js +36 -0
  70. package/dist/esm/transform/utils.js.map +1 -0
  71. package/dist/esm/types.d.ts +22 -0
  72. package/dist/esm/utils.d.ts +76 -9
  73. package/dist/esm/utils.js +237 -40
  74. package/dist/esm/utils.js.map +1 -1
  75. package/package.json +9 -10
  76. package/src/config.ts +23 -2
  77. package/src/filesystem/physical/getRouteNodes.ts +2 -1
  78. package/src/filesystem/virtual/getRouteNodes.ts +1 -1
  79. package/src/generator.ts +1108 -934
  80. package/src/index.ts +25 -3
  81. package/src/logger.ts +43 -0
  82. package/src/plugin/default-generator-plugin.ts +96 -0
  83. package/src/plugin/types.ts +51 -0
  84. package/src/template.ts +33 -12
  85. package/src/transform/default-transform-plugin.ts +103 -0
  86. package/src/transform/transform.ts +430 -0
  87. package/src/transform/types.ts +50 -0
  88. package/src/transform/utils.ts +42 -0
  89. package/src/types.ts +25 -0
  90. package/src/utils.ts +351 -36
package/src/utils.ts CHANGED
@@ -1,5 +1,9 @@
1
1
  import * as fsp from 'node:fs/promises'
2
+ import path from 'node:path'
2
3
  import * as prettier from 'prettier'
4
+ import { rootPathId } from './filesystem/physical/rootPathId'
5
+ import type { Config } from './config'
6
+ import type { ImportDeclaration, RouteNode } from './types'
3
7
 
4
8
  export function multiSortBy<T>(
5
9
  arr: Array<T>,
@@ -40,42 +44,6 @@ export function trimPathLeft(path: string) {
40
44
  return path === '/' ? path : path.replace(/^\/{1,}/, '')
41
45
  }
42
46
 
43
- export function logging(config: { disabled: boolean }) {
44
- function stripEmojis(str: string) {
45
- return str.replace(
46
- /[\p{Emoji_Presentation}\p{Extended_Pictographic}]/gu,
47
- '',
48
- )
49
- }
50
-
51
- function formatLogArgs(args: Array<any>): Array<any> {
52
- if (process.env.CI) {
53
- return args.map((arg) =>
54
- typeof arg === 'string' ? stripEmojis(arg) : arg,
55
- )
56
- }
57
- return args
58
- }
59
-
60
- return {
61
- log: (...args: Array<any>) => {
62
- if (!config.disabled) console.log(...formatLogArgs(args))
63
- },
64
- debug: (...args: Array<any>) => {
65
- if (!config.disabled) console.debug(...formatLogArgs(args))
66
- },
67
- info: (...args: Array<any>) => {
68
- if (!config.disabled) console.info(...formatLogArgs(args))
69
- },
70
- warn: (...args: Array<any>) => {
71
- if (!config.disabled) console.warn(...formatLogArgs(args))
72
- },
73
- error: (...args: Array<any>) => {
74
- if (!config.disabled) console.error(...formatLogArgs(args))
75
- },
76
- }
77
- }
78
-
79
47
  export function removeLeadingSlash(path: string): string {
80
48
  return path.replace(/^\//, '')
81
49
  }
@@ -268,3 +236,350 @@ export async function checkFileExists(file: string) {
268
236
  return false
269
237
  }
270
238
  }
239
+
240
+ const possiblyNestedRouteGroupPatternRegex = /\([^/]+\)\/?/g
241
+ export function removeGroups(s: string) {
242
+ return s.replace(possiblyNestedRouteGroupPatternRegex, '')
243
+ }
244
+
245
+ /**
246
+ * Removes all segments from a given path that start with an underscore ('_').
247
+ *
248
+ * @param {string} routePath - The path from which to remove segments. Defaults to '/'.
249
+ * @returns {string} The path with all underscore-prefixed segments removed.
250
+ * @example
251
+ * removeLayoutSegments('/workspace/_auth/foo') // '/workspace/foo'
252
+ */
253
+ export function removeLayoutSegments(routePath: string = '/'): string {
254
+ const segments = routePath.split('/')
255
+ const newSegments = segments.filter((segment) => !segment.startsWith('_'))
256
+ return newSegments.join('/')
257
+ }
258
+
259
+ /**
260
+ * The `node.path` is used as the `id` in the route definition.
261
+ * This function checks if the given node has a parent and if so, it determines the correct path for the given node.
262
+ * @param node - The node to determine the path for.
263
+ * @returns The correct path for the given node.
264
+ */
265
+ export function determineNodePath(node: RouteNode) {
266
+ return (node.path = node.parent
267
+ ? node.routePath?.replace(node.parent.routePath ?? '', '') || '/'
268
+ : node.routePath)
269
+ }
270
+
271
+ /**
272
+ * Removes the last segment from a given path. Segments are considered to be separated by a '/'.
273
+ *
274
+ * @param {string} routePath - The path from which to remove the last segment. Defaults to '/'.
275
+ * @returns {string} The path with the last segment removed.
276
+ * @example
277
+ * removeLastSegmentFromPath('/workspace/_auth/foo') // '/workspace/_auth'
278
+ */
279
+ export function removeLastSegmentFromPath(routePath: string = '/'): string {
280
+ const segments = routePath.split('/')
281
+ segments.pop() // Remove the last segment
282
+ return segments.join('/')
283
+ }
284
+
285
+ export function hasParentRoute(
286
+ routes: Array<RouteNode>,
287
+ node: RouteNode,
288
+ routePathToCheck: string | undefined,
289
+ ): RouteNode | null {
290
+ if (!routePathToCheck || routePathToCheck === '/') {
291
+ return null
292
+ }
293
+
294
+ const sortedNodes = multiSortBy(routes, [
295
+ (d) => d.routePath!.length * -1,
296
+ (d) => d.variableName,
297
+ ]).filter((d) => d.routePath !== `/${rootPathId}`)
298
+
299
+ for (const route of sortedNodes) {
300
+ if (route.routePath === '/') continue
301
+
302
+ if (
303
+ routePathToCheck.startsWith(`${route.routePath}/`) &&
304
+ route.routePath !== routePathToCheck
305
+ ) {
306
+ return route
307
+ }
308
+ }
309
+
310
+ const segments = routePathToCheck.split('/')
311
+ segments.pop() // Remove the last segment
312
+ const parentRoutePath = segments.join('/')
313
+
314
+ return hasParentRoute(routes, node, parentRoutePath)
315
+ }
316
+
317
+ /**
318
+ * Gets the final variable name for a route
319
+ */
320
+ export const getResolvedRouteNodeVariableName = (
321
+ routeNode: RouteNode,
322
+ variableNameSuffix: string,
323
+ ): string => {
324
+ return routeNode.children?.length
325
+ ? `${routeNode.variableName}${variableNameSuffix}WithChildren`
326
+ : `${routeNode.variableName}${variableNameSuffix}`
327
+ }
328
+
329
+ /**
330
+ * Checks if a given RouteNode is valid for augmenting it with typing based on conditions.
331
+ * Also asserts that the RouteNode is defined.
332
+ *
333
+ * @param routeNode - The RouteNode to check.
334
+ * @returns A boolean indicating whether the RouteNode is defined.
335
+ */
336
+ export function isRouteNodeValidForAugmentation(
337
+ routeNode?: RouteNode,
338
+ ): routeNode is RouteNode {
339
+ if (!routeNode || routeNode.isVirtual) {
340
+ return false
341
+ }
342
+ return true
343
+ }
344
+
345
+ /**
346
+ * Infers the path for use by TS
347
+ */
348
+ export const inferPath = (routeNode: RouteNode): string => {
349
+ return routeNode.cleanedPath === '/'
350
+ ? routeNode.cleanedPath
351
+ : (routeNode.cleanedPath?.replace(/\/$/, '') ?? '')
352
+ }
353
+
354
+ /**
355
+ * Infers the full path for use by TS
356
+ */
357
+ export const inferFullPath = (routeNode: RouteNode): string => {
358
+ const fullPath = removeGroups(
359
+ removeUnderscores(removeLayoutSegments(routeNode.routePath)) ?? '',
360
+ )
361
+
362
+ return routeNode.cleanedPath === '/' ? fullPath : fullPath.replace(/\/$/, '')
363
+ }
364
+
365
+ /**
366
+ * Creates a map from fullPath to routeNode
367
+ */
368
+ export const createRouteNodesByFullPath = (
369
+ routeNodes: Array<RouteNode>,
370
+ ): Map<string, RouteNode> => {
371
+ return new Map(
372
+ routeNodes.map((routeNode) => [inferFullPath(routeNode), routeNode]),
373
+ )
374
+ }
375
+
376
+ /**
377
+ * Create a map from 'to' to a routeNode
378
+ */
379
+ export const createRouteNodesByTo = (
380
+ routeNodes: Array<RouteNode>,
381
+ ): Map<string, RouteNode> => {
382
+ return new Map(
383
+ dedupeBranchesAndIndexRoutes(routeNodes).map((routeNode) => [
384
+ inferTo(routeNode),
385
+ routeNode,
386
+ ]),
387
+ )
388
+ }
389
+
390
+ /**
391
+ * Create a map from 'id' to a routeNode
392
+ */
393
+ export const createRouteNodesById = (
394
+ routeNodes: Array<RouteNode>,
395
+ ): Map<string, RouteNode> => {
396
+ return new Map(
397
+ routeNodes.map((routeNode) => {
398
+ const id = routeNode.routePath ?? ''
399
+ return [id, routeNode]
400
+ }),
401
+ )
402
+ }
403
+
404
+ /**
405
+ * Infers to path
406
+ */
407
+ export const inferTo = (routeNode: RouteNode): string => {
408
+ const fullPath = inferFullPath(routeNode)
409
+
410
+ if (fullPath === '/') return fullPath
411
+
412
+ return fullPath.replace(/\/$/, '')
413
+ }
414
+
415
+ /**
416
+ * Dedupes branches and index routes
417
+ */
418
+ export const dedupeBranchesAndIndexRoutes = (
419
+ routes: Array<RouteNode>,
420
+ ): Array<RouteNode> => {
421
+ return routes.filter((route) => {
422
+ if (route.children?.find((child) => child.cleanedPath === '/')) return false
423
+ return true
424
+ })
425
+ }
426
+
427
+ function checkUnique<TElement>(routes: Array<TElement>, key: keyof TElement) {
428
+ // Check no two routes have the same `key`
429
+ // if they do, throw an error with the conflicting filePaths
430
+ const keys = routes.map((d) => d[key])
431
+ const uniqueKeys = new Set(keys)
432
+ if (keys.length !== uniqueKeys.size) {
433
+ const duplicateKeys = keys.filter((d, i) => keys.indexOf(d) !== i)
434
+ const conflictingFiles = routes.filter((d) =>
435
+ duplicateKeys.includes(d[key]),
436
+ )
437
+ return conflictingFiles
438
+ }
439
+ return undefined
440
+ }
441
+
442
+ export function checkRouteFullPathUniqueness(
443
+ _routes: Array<RouteNode>,
444
+ config: Config,
445
+ ) {
446
+ const routes = _routes.map((d) => {
447
+ const inferredFullPath = inferFullPath(d)
448
+ return { ...d, inferredFullPath }
449
+ })
450
+
451
+ const conflictingFiles = checkUnique(routes, 'inferredFullPath')
452
+
453
+ if (conflictingFiles !== undefined) {
454
+ const errorMessage = `Conflicting configuration paths were found for the following route${conflictingFiles.length > 1 ? 's' : ''}: ${conflictingFiles
455
+ .map((p) => `"${p.inferredFullPath}"`)
456
+ .join(', ')}.
457
+ Please ensure each Route has a unique full path.
458
+ Conflicting files: \n ${conflictingFiles.map((d) => path.resolve(config.routesDirectory, d.filePath)).join('\n ')}\n`
459
+ throw new Error(errorMessage)
460
+ }
461
+ }
462
+
463
+ export function buildRouteTreeConfig(
464
+ nodes: Array<RouteNode>,
465
+ exportName: string,
466
+ disableTypes: boolean,
467
+ depth = 1,
468
+ ): Array<string> {
469
+ const children = nodes.map((node) => {
470
+ if (node._fsRouteType === '__root') {
471
+ return
472
+ }
473
+
474
+ if (node._fsRouteType === 'pathless_layout' && !node.children?.length) {
475
+ return
476
+ }
477
+
478
+ const route = `${node.variableName}`
479
+
480
+ if (node.children?.length) {
481
+ const childConfigs = buildRouteTreeConfig(
482
+ node.children,
483
+ exportName,
484
+ disableTypes,
485
+ depth + 1,
486
+ )
487
+
488
+ const childrenDeclaration = disableTypes
489
+ ? ''
490
+ : `interface ${route}${exportName}Children {
491
+ ${node.children.map((child) => `${child.variableName}${exportName}: typeof ${getResolvedRouteNodeVariableName(child, exportName)}`).join(',')}
492
+ }`
493
+
494
+ const children = `const ${route}${exportName}Children${disableTypes ? '' : `: ${route}${exportName}Children`} = {
495
+ ${node.children.map((child) => `${child.variableName}${exportName}: ${getResolvedRouteNodeVariableName(child, exportName)}`).join(',')}
496
+ }`
497
+
498
+ const routeWithChildren = `const ${route}${exportName}WithChildren = ${route}${exportName}._addFileChildren(${route}${exportName}Children)`
499
+
500
+ return [
501
+ childConfigs.join('\n'),
502
+ childrenDeclaration,
503
+ children,
504
+ routeWithChildren,
505
+ ].join('\n\n')
506
+ }
507
+
508
+ return undefined
509
+ })
510
+
511
+ return children.filter((x) => x !== undefined)
512
+ }
513
+
514
+ export function buildImportString(
515
+ importDeclaration: ImportDeclaration,
516
+ ): string {
517
+ const { source, specifiers, importKind } = importDeclaration
518
+ return specifiers.length
519
+ ? `import ${importKind === 'type' ? 'type ' : ''}{ ${specifiers.map((s) => (s.local ? `${s.imported} as ${s.local}` : s.imported)).join(', ')} } from '${source}'`
520
+ : ''
521
+ }
522
+
523
+ export function lowerCaseFirstChar(value: string) {
524
+ if (!value[0]) {
525
+ return value
526
+ }
527
+
528
+ return value[0].toLowerCase() + value.slice(1)
529
+ }
530
+
531
+ export function mergeImportDeclarations(
532
+ imports: Array<ImportDeclaration>,
533
+ ): Array<ImportDeclaration> {
534
+ const merged: Record<string, ImportDeclaration> = {}
535
+
536
+ for (const imp of imports) {
537
+ const key = `${imp.source}-${imp.importKind}`
538
+ if (!merged[key]) {
539
+ merged[key] = { ...imp, specifiers: [] }
540
+ }
541
+ for (const specifier of imp.specifiers) {
542
+ // check if the specifier already exists in the merged import
543
+ if (
544
+ !merged[key].specifiers.some(
545
+ (existing) =>
546
+ existing.imported === specifier.imported &&
547
+ existing.local === specifier.local,
548
+ )
549
+ ) {
550
+ merged[key].specifiers.push(specifier)
551
+ }
552
+ }
553
+ }
554
+
555
+ return Object.values(merged)
556
+ }
557
+
558
+ export function hasChildWithExport(
559
+ node: RouteNode,
560
+ exportName: string,
561
+ ): boolean {
562
+ return (
563
+ node.children?.some((child) => hasChildWithExport(child, exportName)) ??
564
+ false
565
+ )
566
+ }
567
+
568
+ export const findParent = (
569
+ node: RouteNode | undefined,
570
+ exportName: string,
571
+ ): string => {
572
+ if (!node) {
573
+ return `root${exportName}Import`
574
+ }
575
+ if (node.parent) {
576
+ if (node.parent.exports?.includes(exportName)) {
577
+ if (node.isVirtualParentRequired) {
578
+ return `${node.parent.variableName}${exportName}`
579
+ } else {
580
+ return `${node.parent.variableName}${exportName}`
581
+ }
582
+ }
583
+ }
584
+ return findParent(node.parent, exportName)
585
+ }