@tanstack/router-cli 0.0.1-beta.69 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2021 Tanner Linsley
3
+ Copyright (c) 2021-present Tanner Linsley
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/bin/tsr.js CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- require('../build/cjs/index')
3
+ require('../build/cjs/index.js')
@@ -1,5 +1,5 @@
1
1
  /**
2
- * router-cli
2
+ * @tanstack/router-cli/src/index.ts
3
3
  *
4
4
  * Copyright (c) TanStack
5
5
  *
@@ -10,19 +10,23 @@
10
10
  */
11
11
  'use strict';
12
12
 
13
- Object.defineProperty(exports, '__esModule', { value: true });
14
-
15
13
  var path = require('path');
16
14
  var fs = require('fs-extra');
15
+ var zod = require('zod');
17
16
 
18
- function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
19
-
20
- var path__default = /*#__PURE__*/_interopDefaultLegacy(path);
21
- var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
22
-
23
- const configFilePathJson = path__default["default"].resolve(process.cwd(), 'tsr.config.json');
17
+ const configSchema = zod.z.object({
18
+ routeFilePrefix: zod.z.string().optional(),
19
+ routeFileIgnorePrefix: zod.z.string().optional(),
20
+ routesDirectory: zod.z.string(),
21
+ generatedRouteTree: zod.z.string()
22
+ });
23
+ const configFilePathJson = path.resolve(process.cwd(), 'tsr.config.json');
24
24
  async function getConfig() {
25
- return fs__default["default"].readJson(configFilePathJson);
25
+ const config = await fs.readJson(configFilePathJson);
26
+ return {
27
+ routeFileIgnorePrefix: '-',
28
+ ...configSchema.parse(config)
29
+ };
26
30
  }
27
31
 
28
32
  exports.getConfig = getConfig;
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sources":["../../src/config.ts"],"sourcesContent":["import path from 'path'\nimport fs from 'fs-extra'\n\nexport type Config = {\n rootDirectory: string\n sourceDirectory: string\n routesDirectory: string\n routeGenDirectory: string\n}\n\nconst configFilePathJson = path.resolve(process.cwd(), 'tsr.config.json')\n\nexport async function getConfig() {\n return fs.readJson(configFilePathJson)\n}\n"],"names":["configFilePathJson","path","resolve","process","cwd","getConfig","fs","readJson"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAUA,MAAMA,kBAAkB,GAAGC,wBAAI,CAACC,OAAO,CAACC,OAAO,CAACC,GAAG,EAAE,EAAE,iBAAiB,CAAC,CAAA;AAElE,eAAeC,SAAS,GAAG;AAChC,EAAA,OAAOC,sBAAE,CAACC,QAAQ,CAACP,kBAAkB,CAAC,CAAA;AACxC;;;;"}
1
+ {"version":3,"file":"config.js","sources":["../../src/config.ts"],"sourcesContent":["import path from 'path'\nimport fs from 'fs-extra'\nimport { z } from 'zod'\n\nconst configSchema = z.object({\n routeFilePrefix: z.string().optional(),\n routeFileIgnorePrefix: z.string().optional(),\n routesDirectory: z.string(),\n generatedRouteTree: z.string(),\n})\n\nexport type Config = z.infer<typeof configSchema>\n\nconst configFilePathJson = path.resolve(process.cwd(), 'tsr.config.json')\n\nexport async function getConfig(): Promise<Config> {\n const config = (await fs.readJson(configFilePathJson)) as unknown as Config\n\n return { routeFileIgnorePrefix: '-', ...configSchema.parse(config) }\n}\n"],"names":["configSchema","z","object","routeFilePrefix","string","optional","routeFileIgnorePrefix","routesDirectory","generatedRouteTree","configFilePathJson","path","resolve","process","cwd","getConfig","config","fs","readJson","parse"],"mappings":";;;;;;;;;;;;;;;;AAIA,MAAMA,YAAY,GAAGC,KAAC,CAACC,MAAM,CAAC;EAC5BC,eAAe,EAAEF,KAAC,CAACG,MAAM,EAAE,CAACC,QAAQ,EAAE;EACtCC,qBAAqB,EAAEL,KAAC,CAACG,MAAM,EAAE,CAACC,QAAQ,EAAE;AAC5CE,EAAAA,eAAe,EAAEN,KAAC,CAACG,MAAM,EAAE;AAC3BI,EAAAA,kBAAkB,EAAEP,KAAC,CAACG,MAAM,EAAC;AAC/B,CAAC,CAAC,CAAA;AAIF,MAAMK,kBAAkB,GAAGC,IAAI,CAACC,OAAO,CAACC,OAAO,CAACC,GAAG,EAAE,EAAE,iBAAiB,CAAC,CAAA;AAElE,eAAeC,SAASA,GAAoB;EACjD,MAAMC,MAAM,GAAI,MAAMC,EAAE,CAACC,QAAQ,CAACR,kBAAkB,CAAuB,CAAA;EAE3E,OAAO;AAAEH,IAAAA,qBAAqB,EAAE,GAAG;AAAE,IAAA,GAAGN,YAAY,CAACkB,KAAK,CAACH,MAAM,CAAA;GAAG,CAAA;AACtE;;;;"}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * router-cli
2
+ * @tanstack/router-cli/src/index.ts
3
3
  *
4
4
  * Copyright (c) TanStack
5
5
  *
@@ -10,8 +10,6 @@
10
10
  */
11
11
  'use strict';
12
12
 
13
- Object.defineProperty(exports, '__esModule', { value: true });
14
-
15
13
  var generator = require('./generator.js');
16
14
 
17
15
  async function generate(config) {
@@ -1 +1 @@
1
- {"version":3,"file":"generate.js","sources":["../../src/generate.ts"],"sourcesContent":["import { generator } from './generator'\nimport { Config } from './config'\n\nexport async function generate(config: Config) {\n try {\n await generator(config)\n process.exit(0)\n } catch (err) {\n console.error(err)\n process.exit(1)\n }\n}\n"],"names":["generate","config","generator","process","exit","err","console","error"],"mappings":";;;;;;;;;;;;;;;;AAGO,eAAeA,QAAQ,CAACC,MAAc,EAAE;EAC7C,IAAI;IACF,MAAMC,mBAAS,CAACD,MAAM,CAAC,CAAA;AACvBE,IAAAA,OAAO,CAACC,IAAI,CAAC,CAAC,CAAC,CAAA;GAChB,CAAC,OAAOC,GAAG,EAAE;AACZC,IAAAA,OAAO,CAACC,KAAK,CAACF,GAAG,CAAC,CAAA;AAClBF,IAAAA,OAAO,CAACC,IAAI,CAAC,CAAC,CAAC,CAAA;AACjB,GAAA;AACF;;;;"}
1
+ {"version":3,"file":"generate.js","sources":["../../src/generate.ts"],"sourcesContent":["import { generator } from './generator'\nimport { Config } from './config'\n\nexport async function generate(config: Config) {\n try {\n await generator(config)\n process.exit(0)\n } catch (err) {\n console.error(err)\n process.exit(1)\n }\n}\n"],"names":["generate","config","generator","process","exit","err","console","error"],"mappings":";;;;;;;;;;;;;;AAGO,eAAeA,QAAQA,CAACC,MAAc,EAAE;EAC7C,IAAI;IACF,MAAMC,mBAAS,CAACD,MAAM,CAAC,CAAA;AACvBE,IAAAA,OAAO,CAACC,IAAI,CAAC,CAAC,CAAC,CAAA;GAChB,CAAC,OAAOC,GAAG,EAAE;AACZC,IAAAA,OAAO,CAACC,KAAK,CAACF,GAAG,CAAC,CAAA;AAClBF,IAAAA,OAAO,CAACC,IAAI,CAAC,CAAC,CAAC,CAAA;AACjB,GAAA;AACF;;;;"}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * router-cli
2
+ * @tanstack/router-cli/src/index.ts
3
3
  *
4
4
  * Copyright (c) TanStack
5
5
  *
@@ -10,34 +10,92 @@
10
10
  */
11
11
  'use strict';
12
12
 
13
- Object.defineProperty(exports, '__esModule', { value: true });
14
-
15
- var klaw = require('klaw');
16
- var through2 = require('through2');
17
13
  var path = require('path');
18
14
  var fs = require('fs-extra');
19
- var crypto = require('crypto');
20
- var transformCode = require('./transformCode.js');
15
+ var prettier = require('prettier');
16
+ var reactRouter = require('@tanstack/react-router');
21
17
 
22
- function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
18
+ function _interopNamespaceDefault(e) {
19
+ var n = Object.create(null);
20
+ if (e) {
21
+ Object.keys(e).forEach(function (k) {
22
+ if (k !== 'default') {
23
+ var d = Object.getOwnPropertyDescriptor(e, k);
24
+ Object.defineProperty(n, k, d.get ? d : {
25
+ enumerable: true,
26
+ get: function () { return e[k]; }
27
+ });
28
+ }
29
+ });
30
+ }
31
+ n.default = e;
32
+ return Object.freeze(n);
33
+ }
23
34
 
24
- var klaw__default = /*#__PURE__*/_interopDefaultLegacy(klaw);
25
- var through2__default = /*#__PURE__*/_interopDefaultLegacy(through2);
26
- var path__default = /*#__PURE__*/_interopDefaultLegacy(path);
27
- var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
28
- var crypto__default = /*#__PURE__*/_interopDefaultLegacy(crypto);
35
+ var prettier__namespace = /*#__PURE__*/_interopNamespaceDefault(prettier);
29
36
 
30
37
  let latestTask = 0;
31
- const rootRouteName = '__root';
32
- const rootRouteClientName = '__root.client';
33
- let nodeCache = undefined;
38
+ const rootPathId = '__root';
39
+ const fileRouteRegex = /new\s+FileRoute\(([^)]*)\)/g;
40
+ async function getRouteNodes(config) {
41
+ const {
42
+ routeFilePrefix,
43
+ routeFileIgnorePrefix
44
+ } = config;
45
+ let routeNodes = [];
46
+ async function recurse(dir) {
47
+ const fullDir = path.resolve(config.routesDirectory, dir);
48
+ let dirList = await fs.readdir(fullDir);
49
+ dirList = dirList.filter(d => {
50
+ if (d.startsWith('.') || routeFileIgnorePrefix && d.startsWith(routeFileIgnorePrefix)) {
51
+ return false;
52
+ }
53
+ if (routeFilePrefix) {
54
+ return d.startsWith(routeFilePrefix);
55
+ }
56
+ return true;
57
+ });
58
+ await Promise.all(dirList.map(async fileName => {
59
+ const fullPath = path.join(fullDir, fileName);
60
+ const relativePath = path.join(dir, fileName);
61
+ const stat = await fs.stat(fullPath);
62
+ if (stat.isDirectory()) {
63
+ await recurse(relativePath);
64
+ } else {
65
+ const filePath = path.join(dir, fileName);
66
+ const filePathNoExt = removeExt(filePath);
67
+ let routePath = replaceBackslash(reactRouter.cleanPath(`/${filePathNoExt.split('.').join('/')}`)) ?? '';
68
+ const variableName = fileToVariable(routePath);
69
+
70
+ // Remove the index from the route path and
71
+ // if the route path is empty, use `/'
72
+ if (routePath === 'index') {
73
+ routePath = '/';
74
+ } else if (routePath.endsWith('/index')) {
75
+ routePath = routePath.replace(/\/index$/, '/');
76
+ }
77
+ routeNodes.push({
78
+ filePath,
79
+ fullPath,
80
+ routePath,
81
+ variableName
82
+ });
83
+ }
84
+ }));
85
+ return routeNodes;
86
+ }
87
+ await recurse('./');
88
+ return routeNodes;
89
+ }
90
+ let first = false;
91
+ let skipMessage = false;
34
92
  async function generator(config) {
35
93
  console.log();
36
- let first = false;
37
- if (!nodeCache) {
38
- first = true;
94
+ if (!first) {
39
95
  console.log('🔄 Generating routes...');
40
- nodeCache = [];
96
+ first = true;
97
+ } else if (skipMessage) {
98
+ skipMessage = false;
41
99
  } else {
42
100
  console.log('♻️ Regenerating routes...');
43
101
  }
@@ -45,243 +103,125 @@ async function generator(config) {
45
103
  latestTask = taskId;
46
104
  const checkLatest = () => {
47
105
  if (latestTask !== taskId) {
48
- console.log(`- Skipping since file changes were made while generating.`);
106
+ skipMessage = true;
49
107
  return false;
50
108
  }
51
109
  return true;
52
110
  };
53
111
  const start = Date.now();
54
- let routeConfigImports = [];
55
- let routeConfigClientImports = [];
56
- let nodesChanged = false;
57
- const fileQueue = [];
58
- const queueWriteFile = (filename, content) => {
59
- fileQueue.push([filename, content]);
60
- };
61
- async function reparent(dir) {
62
- let dirList;
63
- try {
64
- dirList = await fs__default["default"].readdir(dir);
65
- } catch (err) {
66
- console.log();
67
- console.error('TSR: Error reading the config.routesDirectory. Does it exist?');
68
- console.log();
69
- throw err;
112
+ const routePathIdPrefix = config.routeFilePrefix ?? '';
113
+ let routeNodes = await getRouteNodes(config);
114
+ routeNodes = multiSortBy(routeNodes, [d => d.routePath === '/' ? -1 : 1, d => d.routePath?.split('/').length, d => d.routePath?.endsWith('/') ? -1 : 1, d => d.routePath]).filter(d => d.routePath !== `/${routePathIdPrefix + rootPathId}`);
115
+ const routeTree = [];
116
+
117
+ // Loop over the flat list of routeNodes and
118
+ // build up a tree based on the routeNodes' routePath
119
+ routeNodes.forEach(node => {
120
+ // routeNodes.forEach((existingNode) => {
121
+ // if (
122
+ // node.routePath?.startsWith(`${existingNode?.routePath ?? ''}/`)
123
+ // // node.routePath.length > existingNode.routePath!.length
124
+ // ) {
125
+ // node.parent = existingNode
126
+ // }
127
+ // })
128
+ const parentRoute = hasParentRoute(routeNodes, node.routePath);
129
+ if (parentRoute) node.parent = parentRoute;
130
+ node.path = node.parent ? node.routePath?.replace(node.parent.routePath, '') || '/' : node.routePath;
131
+ const trimmedPath = reactRouter.trimPathLeft(node.path ?? '');
132
+ const split = trimmedPath?.split('/') ?? [];
133
+ let first = split[0] ?? trimmedPath ?? '';
134
+ node.isNonPath = first.startsWith('_');
135
+ node.isNonLayout = first.endsWith('_');
136
+ node.cleanedPath = removeUnderscores(node.path) ?? '';
137
+ if (node.parent) {
138
+ node.parent.children = node.parent.children ?? [];
139
+ node.parent.children.push(node);
140
+ } else {
141
+ routeTree.push(node);
70
142
  }
71
- const dirListCombo = multiSortBy(await Promise.all(dirList.map(async filename => {
72
- const fullPath = path__default["default"].resolve(dir, filename);
73
- const stat = await fs__default["default"].lstat(fullPath);
74
- const ext = path__default["default"].extname(filename);
75
- const clientFilename = filename.replace(ext, `.client${ext}`);
76
- const pathFromRoutes = path__default["default"].relative(config.routesDirectory, fullPath);
77
- const genPath = path__default["default"].resolve(config.routeGenDirectory, pathFromRoutes);
78
- const genPathNoExt = removeExt(genPath);
79
- const genDir = path__default["default"].resolve(genPath, '..');
80
- const fileNameNoExt = removeExt(filename);
81
- return {
82
- filename,
83
- clientFilename,
84
- fileNameNoExt,
85
- fullPath,
86
- fullDir: dir,
87
- genPath,
88
- genDir,
89
- genPathNoExt,
90
- variable: fileToVariable(removeExt(pathFromRoutes)),
91
- isDirectory: stat.isDirectory(),
92
- isIndex: fileNameNoExt === 'index'
93
- };
94
- })), [d => d.fileNameNoExt === 'index' ? -1 : 1, d => d.fileNameNoExt, d => d.isDirectory ? 1 : -1]);
95
- const reparented = [];
96
- dirListCombo.forEach(async (d, i) => {
97
- if (d.isDirectory) {
98
- const parent = reparented.find(dd => !dd.isDirectory && dd.fileNameNoExt === d.filename);
99
- if (parent) {
100
- parent.childRoutesDir = d.fullPath;
101
- } else {
102
- reparented.push(d);
103
- }
104
- } else {
105
- reparented.push(d);
106
- }
107
- });
108
- return Promise.all(reparented.map(async d => {
109
- if (d.childRoutesDir) {
110
- const children = await reparent(d.childRoutesDir);
111
- d = {
112
- ...d,
113
- children
114
- };
115
- children.forEach(child => child.parent = d);
116
- return d;
117
- }
118
- return d;
119
- }));
120
- }
121
- const reparented = await reparent(config.routesDirectory);
143
+ });
122
144
  async function buildRouteConfig(nodes, depth = 1) {
123
- const children = nodes.map(async n => {
124
- let node = nodeCache.find(d => d.fullPath === n.fullPath);
125
- if (node) {
126
- node.new = false;
127
- } else {
128
- node = n;
129
- nodeCache.push(node);
130
- if (!first) {
131
- node.new = true;
132
- }
133
- }
134
- node.version = latestTask;
135
- if (node.fileNameNoExt === '__root') {
136
- node.isRoot = true;
137
- }
138
- const routeCode = await fs__default["default"].readFile(node.fullPath, 'utf-8');
139
- const hashSum = crypto__default["default"].createHash('sha256');
140
- hashSum.update(routeCode);
141
- const hash = hashSum.digest('hex');
142
- node.changed = node.hash !== hash;
143
- if (node.changed) {
144
- nodesChanged = true;
145
- node.hash = hash;
146
- try {
147
- // Ensure the boilerplate for the route exists
148
- const code = await transformCode.ensureBoilerplate(node, routeCode);
149
- if (code) {
150
- await fs__default["default"].writeFile(node.fullPath, code);
151
- }
152
- let imports = [];
153
- if (!node.isRoot) {
154
- // Generate the isolated files
155
- const transforms = await Promise.all(transformCode.isolatedProperties.map(async key => {
156
- let exported = false;
157
- let exports = [];
158
- const transformed = await transformCode.isolateOptionToExport(node, routeCode, {
159
- isolate: key
160
- });
161
- if (transformed) {
162
- exports = await transformCode.detectExports(transformed);
163
- if (exports.includes(key)) {
164
- exported = true;
165
- }
166
- }
167
- return {
168
- key,
169
- exported,
170
- code: transformed
171
- };
172
- }));
173
- imports = transforms.filter(({
174
- exported
175
- }) => exported);
176
- node.importedFiles = await Promise.all(imports.map(({
177
- key,
178
- code
179
- }) => {
180
- const importFilename = `${node.genPathNoExt}-${key}.tsx`;
181
- queueWriteFile(importFilename, code);
182
- return importFilename;
183
- }));
184
- }
185
- const routeConfigCode = await transformCode.generateRouteConfig(node, routeCode, imports, false);
186
- const clientRouteConfigCode = await transformCode.generateRouteConfig(node, routeCode, imports, true);
187
- queueWriteFile(node.genPath, routeConfigCode);
188
- queueWriteFile(path__default["default"].resolve(node.genDir, node.clientFilename), clientRouteConfigCode);
189
- } catch (err) {
190
- node.hash = '';
191
- }
192
- }
193
- routeConfigImports.push(`import { route as ${node.variable}Route } from './${removeExt(path__default["default"].relative(config.routeGenDirectory, node.genPath).replace(/\\/gi, '/'))}'`);
194
- routeConfigClientImports.push(`import { route as ${node.variable}Route } from './${removeExt(path__default["default"].relative(config.routeGenDirectory, path__default["default"].resolve(node.genDir, node.clientFilename)).replace(/\\/gi, '/'))}'`);
145
+ const children = nodes.map(async node => {
146
+ const routeCode = await fs.readFile(node.fullPath, 'utf-8');
147
+
148
+ // Ensure the boilerplate for the route exists
195
149
  if (node.isRoot) {
196
- return undefined;
150
+ return;
151
+ }
152
+
153
+ // Ensure that new FileRoute(anything?) is replace with FileRoute(${node.routePath})
154
+ // routePath can contain $ characters, which have special meaning when used in replace
155
+ // so we have to escape it by turning all $ into $$. But since we do it through a replace call
156
+ // we have to double escape it into $$$$. For more information, see
157
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace#specifying_a_string_as_the_replacement
158
+ const escapedRoutePath = node.routePath?.replaceAll('$', '$$$$') ?? '';
159
+ const replaced = routeCode.replace(fileRouteRegex, `new FileRoute('${escapedRoutePath}')`);
160
+ if (replaced !== routeCode) {
161
+ await fs.writeFile(node.fullPath, replaced);
197
162
  }
198
- const route = `${node.variable}Route`;
163
+ const route = `${node.variableName}Route`;
199
164
  if (node.children?.length) {
200
165
  const childConfigs = await buildRouteConfig(node.children, depth + 1);
201
- return `${route}.addChildren([\n${spaces(depth * 4)}${childConfigs}\n${spaces(depth * 2)}])`;
166
+ return `${route}.addChildren([${spaces(depth * 4)}${childConfigs}])`;
202
167
  }
203
168
  return route;
204
169
  });
205
- return (await Promise.all(children)).filter(Boolean).join(`,\n${spaces(depth * 2)}`);
170
+ return (await Promise.all(children)).filter(Boolean).join(`,`);
206
171
  }
207
- const routeConfigChildrenText = await buildRouteConfig(reparented);
208
- routeConfigImports = multiSortBy(routeConfigImports, [d => d.includes('__root') ? -1 : 1, d => d.split('/').length, d => d.endsWith("index'") ? -1 : 1, d => d]);
209
- routeConfigClientImports = multiSortBy(routeConfigClientImports, [d => d.includes('__root') ? -1 : 1, d => d.split('/').length, d => d.endsWith("index.client'") ? -1 : 1, d => d]);
210
- const routeConfig = `export const routeTree = rootRoute.addChildren([\n ${routeConfigChildrenText}\n])\nexport type __GeneratedRouteConfig = typeof routeTree`;
211
- const routeConfigClient = `export const routeTreeClient = rootRoute.addChildren([\n ${routeConfigChildrenText}\n]) as __GeneratedRouteConfig`;
212
- const routeConfigFileContent = [routeConfigImports.join('\n'), routeConfig].join('\n\n');
213
- const routeConfigClientFileContent = [`import type { __GeneratedRouteConfig } from './routeTree'`, routeConfigClientImports.join('\n'), routeConfigClient].join('\n\n');
214
- if (nodesChanged) {
215
- queueWriteFile(path__default["default"].resolve(config.routeGenDirectory, 'routeTree.ts'), routeConfigFileContent);
216
- queueWriteFile(path__default["default"].resolve(config.routeGenDirectory, 'routeTree.client.ts'), routeConfigClientFileContent);
172
+ const routeConfigChildrenText = await buildRouteConfig(routeTree);
173
+ const routeImports = [`import { Route as rootRoute } from './${sanitize(path.relative(path.dirname(config.generatedRouteTree), path.resolve(config.routesDirectory, routePathIdPrefix + rootPathId)))}'`, ...multiSortBy(routeNodes, [d => d.routePath?.includes(`/${routePathIdPrefix + rootPathId}`) ? -1 : 1, d => d.routePath?.split('/').length, d => d.routePath?.endsWith("index'") ? -1 : 1, d => d]).map(node => {
174
+ return `import { Route as ${node.variableName}Route } from './${sanitize(removeExt(path.relative(path.dirname(config.generatedRouteTree), path.resolve(config.routesDirectory, node.filePath))))}'`;
175
+ })].join('\n');
176
+ const routeTypes = `declare module '@tanstack/react-router' {
177
+ interface FileRoutesByPath {
178
+ ${routeNodes.map(routeNode => {
179
+ return `'${routeNode.routePath}': {
180
+ parentRoute: typeof ${routeNode.parent?.variableName ?? 'root'}Route
181
+ }`;
182
+ }).join('\n')}
217
183
  }
218
-
219
- // Do all of our file system manipulation at the end
220
- await fs__default["default"].mkdir(config.routeGenDirectory, {
221
- recursive: true
222
- });
223
- if (!checkLatest()) return;
224
- await Promise.all(fileQueue.map(async ([filename, content]) => {
225
- await fs__default["default"].ensureDir(path__default["default"].dirname(filename));
226
- const exists = await fs__default["default"].pathExists(filename);
227
- let current = '';
228
- if (exists) {
229
- current = await fs__default["default"].readFile(filename, 'utf-8');
230
- }
231
- if (current !== content) {
232
- await fs__default["default"].writeFile(filename, content);
233
- }
234
- }));
235
- if (!checkLatest()) return;
236
- const allFiles = await getAllFiles(config.routeGenDirectory);
237
- if (!checkLatest()) return;
238
- const removedNodes = [];
239
- nodeCache = nodeCache.filter(d => {
240
- if (d.version !== latestTask) {
241
- removedNodes.push(d);
242
- return false;
243
- }
244
- return true;
184
+ }`;
185
+ const routeOptions = routeNodes.map(routeNode => {
186
+ return `Object.assign(${routeNode.variableName ?? 'root'}Route.options, {
187
+ ${[routeNode.isNonPath ? `id: '${routeNode.cleanedPath}'` : `path: '${routeNode.cleanedPath}'`, `getParentRoute: () => ${routeNode.parent?.variableName ?? 'root'}Route`
188
+ // `\n// ${JSON.stringify(
189
+ // {
190
+ // ...routeNode,
191
+ // parent: undefined,
192
+ // children: undefined,
193
+ // fullPath: undefined,
194
+ // variableName: undefined,
195
+ // },
196
+ // null,
197
+ // 2,
198
+ // )
199
+ // .split('\n')
200
+ // .join('\n// ')}`,
201
+ ].filter(Boolean).join(',')}
202
+ })`;
203
+ }).join('\n\n');
204
+ const routeConfig = `export const routeTree = rootRoute.addChildren([${routeConfigChildrenText}])`;
205
+ const routeConfigFileContent = await prettier__namespace.format([routeImports, routeTypes, routeOptions, routeConfig].join('\n\n'), {
206
+ semi: false,
207
+ parser: 'typescript'
245
208
  });
246
- const newNodes = nodeCache.filter(d => d.new);
247
- const updatedNodes = nodeCache.filter(d => !d.new && d.changed);
248
- const unusedFiles = allFiles.filter(d => {
249
- if (d === path__default["default"].resolve(config.routeGenDirectory, 'routeTree.ts') || d === path__default["default"].resolve(config.routeGenDirectory, 'routeTree.client.ts')) {
250
- return false;
209
+ const routeTreeContent = await fs.readFile(path.resolve(config.generatedRouteTree), 'utf-8').catch(err => {
210
+ if (err.code === 'ENOENT') {
211
+ return undefined;
251
212
  }
252
- let node = nodeCache.find(n => n.genPath === d || path__default["default"].resolve(n.genDir, n.clientFilename) === d || n.importedFiles?.includes(d));
253
- return !node;
213
+ throw err;
254
214
  });
255
- await Promise.all(unusedFiles.map(d => {
256
- fs__default["default"].remove(d);
257
- }));
258
- console.log(`🌲 Processed ${nodeCache.length} routes in ${Date.now() - start}ms`);
259
- if (newNodes.length || updatedNodes.length || removedNodes.length) {
260
- if (newNodes.length) {
261
- console.log(`🥳 Added ${newNodes.length} new routes`);
262
- }
263
- if (updatedNodes.length) {
264
- console.log(`✅ Updated ${updatedNodes.length} routes`);
265
- }
266
- if (removedNodes.length) {
267
- console.log(`🗑 Removed ${removedNodes.length} unused routes`);
268
- }
269
- } else {
270
- console.log(`🎉 No changes were found. Carry on!`);
215
+ if (!checkLatest()) return;
216
+ if (routeTreeContent !== routeConfigFileContent) {
217
+ await fs.ensureDir(path.dirname(path.resolve(config.generatedRouteTree)));
218
+ if (!checkLatest()) return;
219
+ await fs.writeFile(path.resolve(config.generatedRouteTree), routeConfigFileContent);
271
220
  }
272
- }
273
- function getAllFiles(dir) {
274
- return new Promise((resolve, reject) => {
275
- const excludeDirFilter = through2__default["default"].obj(function (item, enc, next) {
276
- if (!item.stats.isDirectory()) this.push(item);
277
- next();
278
- });
279
- const items = [];
280
- klaw__default["default"](dir).pipe(excludeDirFilter).on('data', item => items.push(item.path)).on('error', err => reject(err)).on('end', () => resolve(items));
281
- });
221
+ console.log(`🌲 Processed ${routeNodes.length} routes in ${Date.now() - start}ms`);
282
222
  }
283
223
  function fileToVariable(d) {
284
- return d.split('/').map((d, i) => i > 0 ? capitalize(d) : d).join('').replace(/([^a-zA-Z0-9]|[\.])/gm, '');
224
+ return removeUnderscores(d)?.replace(/\$/g, '')?.split(/[/-]/g).map((d, i) => i > 0 ? capitalize(d) : d).join('').replace(/([^a-zA-Z0-9]|[\.])/gm, '') ?? '';
285
225
  }
286
226
  function removeExt(d) {
287
227
  return d.substring(0, d.lastIndexOf('.')) || d;
@@ -314,10 +254,36 @@ function capitalize(s) {
314
254
  if (typeof s !== 'string') return '';
315
255
  return s.charAt(0).toUpperCase() + s.slice(1);
316
256
  }
257
+ function sanitize(s) {
258
+ return replaceBackslash(s?.replace(/\\index/gi, ''));
259
+ }
260
+ function removeUnderscores(s) {
261
+ return s?.replace(/(^_|_$)/, '').replace(/(\/_|_\/)/, '/');
262
+ }
263
+ function replaceBackslash(s) {
264
+ return s?.replace(/\\/gi, '/');
265
+ }
266
+ function hasParentRoute(routes, routeToCheck) {
267
+ if (!routeToCheck || routeToCheck === '/') {
268
+ return null;
269
+ }
270
+ const sortedNodes = multiSortBy(routes, [d => d.routePath.length * -1, d => d.variableName]).filter(d => d.routePath !== `/${rootPathId}`);
271
+ for (const route of sortedNodes) {
272
+ if (route.routePath === '/') continue;
273
+ if (routeToCheck.startsWith(`${route.routePath}/`) && route.routePath !== routeToCheck) {
274
+ return route;
275
+ }
276
+ }
277
+ const segments = routeToCheck.split('/');
278
+ segments.pop(); // Remove the last segment
279
+ const parentRoute = segments.join('/');
280
+ return hasParentRoute(routes, parentRoute);
281
+ }
317
282
 
283
+ exports.fileRouteRegex = fileRouteRegex;
318
284
  exports.generator = generator;
285
+ exports.hasParentRoute = hasParentRoute;
319
286
  exports.multiSortBy = multiSortBy;
320
287
  exports.removeExt = removeExt;
321
- exports.rootRouteClientName = rootRouteClientName;
322
- exports.rootRouteName = rootRouteName;
288
+ exports.rootPathId = rootPathId;
323
289
  //# sourceMappingURL=generator.js.map