@tanstack/router-cli 0.0.1-beta.151 → 0.0.1-beta.153

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.
@@ -14,15 +14,23 @@ Object.defineProperty(exports, '__esModule', { value: true });
14
14
 
15
15
  var path = require('path');
16
16
  var fs = require('fs-extra');
17
+ var zod = require('zod');
17
18
 
18
19
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
19
20
 
20
21
  var path__default = /*#__PURE__*/_interopDefaultLegacy(path);
21
22
  var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
22
23
 
24
+ const configSchema = zod.z.object({
25
+ routeFilePrefix: zod.z.string().optional(),
26
+ routeFileIgnorePrefix: zod.z.string().optional(),
27
+ routesDirectory: zod.z.string(),
28
+ generatedRouteTree: zod.z.string()
29
+ });
23
30
  const configFilePathJson = path__default["default"].resolve(process.cwd(), 'tsr.config.json');
24
31
  async function getConfig() {
25
- return fs__default["default"].readJson(configFilePathJson);
32
+ const config = await fs__default["default"].readJson(configFilePathJson);
33
+ return configSchema.parse(config);
26
34
  }
27
35
 
28
36
  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 generatedRouteTree: 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,SAASA,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() {\n const config = (await fs.readJson(configFilePathJson)) as unknown as Config\n\n return 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,wBAAI,CAACC,OAAO,CAACC,OAAO,CAACC,GAAG,EAAE,EAAE,iBAAiB,CAAC,CAAA;AAElE,eAAeC,SAASA,GAAG;EAChC,MAAMC,MAAM,GAAI,MAAMC,sBAAE,CAACC,QAAQ,CAACR,kBAAkB,CAAuB,CAAA;AAE3E,EAAA,OAAOT,YAAY,CAACkB,KAAK,CAACH,MAAM,CAAC,CAAA;AACnC;;;;"}
@@ -14,44 +14,98 @@ Object.defineProperty(exports, '__esModule', { value: true });
14
14
 
15
15
  var path = require('path');
16
16
  var fs = require('fs-extra');
17
- var crypto = require('crypto');
17
+ var prettier = require('prettier');
18
+ var routerCore = require('@tanstack/router-core');
18
19
 
19
20
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
20
21
 
22
+ function _interopNamespace(e) {
23
+ if (e && e.__esModule) return e;
24
+ var n = Object.create(null);
25
+ if (e) {
26
+ Object.keys(e).forEach(function (k) {
27
+ if (k !== 'default') {
28
+ var d = Object.getOwnPropertyDescriptor(e, k);
29
+ Object.defineProperty(n, k, d.get ? d : {
30
+ enumerable: true,
31
+ get: function () { return e[k]; }
32
+ });
33
+ }
34
+ });
35
+ }
36
+ n["default"] = e;
37
+ return Object.freeze(n);
38
+ }
39
+
21
40
  var path__default = /*#__PURE__*/_interopDefaultLegacy(path);
22
41
  var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
23
- var crypto__default = /*#__PURE__*/_interopDefaultLegacy(crypto);
42
+ var prettier__namespace = /*#__PURE__*/_interopNamespace(prettier);
24
43
 
25
44
  let latestTask = 0;
26
- const rootRouteName = 'root';
27
- let nodeCache = undefined;
28
- async function createRouteNode(config, fileName) {
29
- const filename = path__default["default"].basename(fileName);
30
- const fullPath = path__default["default"].resolve(config.routesDirectory, filename);
31
- const fileNameNoExt = removeExt(filename);
32
- let routePath = fileNameNoExt.split('.').join('/');
45
+ const rootPathId = '__root';
46
+ const fileRouteRegex = /new\s+FileRoute\(([^)]*)\)/g;
47
+ async function getRouteNodes(config) {
48
+ const {
49
+ routeFilePrefix,
50
+ routeFileIgnorePrefix
51
+ } = config;
52
+ let routeNodes = [];
53
+ async function recurse(dir) {
54
+ const fullDir = path__default["default"].resolve(config.routesDirectory, dir);
55
+ let dirList = await fs__default["default"].readdir(fullDir);
56
+ dirList = dirList.filter(d => {
57
+ if (d.startsWith('.') || d.startsWith(routeFileIgnorePrefix)) {
58
+ return false;
59
+ }
60
+ if (routeFilePrefix) {
61
+ return d.startsWith(routeFilePrefix);
62
+ }
63
+ return true;
64
+ });
65
+ await Promise.all(dirList.map(async fileName => {
66
+ const fullPath = path__default["default"].join(fullDir, fileName);
67
+ const relativePath = path__default["default"].join(dir, fileName);
68
+ const stat = await fs__default["default"].stat(fullPath);
69
+ if (stat.isDirectory()) {
70
+ await recurse(relativePath);
71
+ } else {
72
+ const filePath = path__default["default"].join(dir, fileName);
73
+ const filePathNoExt = removeExt(filePath);
74
+ let routePath = routerCore.cleanPath(`/${filePathNoExt.split('.').join('/')}`);
75
+ const variableName = fileToVariable(routePath);
76
+
77
+ // Remove the index from the route path and
78
+ // if the route path is empty, use `/'
79
+ if (routePath.endsWith('/index')) {
80
+ routePath = routePath.replace(/\/index$/, '/');
81
+ } else if (routePath === 'index') {
82
+ routePath = '/';
83
+ }
84
+
85
+ // console.log(filePath)
33
86
 
34
- // Remove the index from the route path and
35
- // if the route path is empty, use `/'
36
- if (routePath.endsWith('/index')) {
37
- routePath = routePath.replace(/\/index$/, '/');
38
- } else if (routePath === 'index') {
39
- routePath = '/';
87
+ routeNodes.push({
88
+ filePath,
89
+ fullPath,
90
+ routePath,
91
+ variableName
92
+ });
93
+ }
94
+ }));
95
+ return routeNodes;
40
96
  }
41
- return {
42
- filename,
43
- fullPath,
44
- routePath,
45
- variableName: fileToVariable(removeExt(filename))
46
- };
97
+ await recurse('./');
98
+ return routeNodes;
47
99
  }
100
+ let first = false;
101
+ let skipMessage = false;
48
102
  async function generator(config) {
49
103
  console.log();
50
- let first = false;
51
- if (!nodeCache) {
52
- first = true;
104
+ if (!first) {
53
105
  console.log('🔄 Generating routes...');
54
- nodeCache = [];
106
+ first = true;
107
+ } else if (skipMessage) {
108
+ skipMessage = false;
55
109
  } else {
56
110
  console.log('♻️ Regenerating routes...');
57
111
  }
@@ -59,167 +113,116 @@ async function generator(config) {
59
113
  latestTask = taskId;
60
114
  const checkLatest = () => {
61
115
  if (latestTask !== taskId) {
62
- console.log(`- Skipping since file changes were made while generating.`);
116
+ skipMessage = true;
63
117
  return false;
64
118
  }
65
119
  return true;
66
120
  };
67
121
  const start = Date.now();
68
- let routeConfigImports = [];
69
- let nodesChanged = false;
70
- const fileQueue = [];
71
- const queueWriteFile = (filename, content) => {
72
- fileQueue.push([filename, content]);
73
- };
74
- let dirList;
75
- try {
76
- dirList = await fs__default["default"].readdir(config.routesDirectory);
77
- } catch (err) {
78
- console.log();
79
- console.error('TSR: Error reading the config.routesDirectory. Does it exist?');
80
- console.log();
81
- throw err;
82
- }
83
- let routeNodes = await Promise.all(dirList.map(async filename => {
84
- return createRouteNode(config, filename);
85
- }));
86
- 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 !== '_root');
122
+ let routeNodes = await getRouteNodes(config);
123
+ 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 !== `/${rootPathId}`);
87
124
  const routeTree = [];
88
125
 
89
126
  // Loop over the flat list of routeNodes and
90
127
  // build up a tree based on the routeNodes' routePath
91
128
  routeNodes.forEach(node => {
92
- const findParent = (existing, node, parentNode) => {
93
- const parent = existing.find(d => {
94
- return node.routePath?.startsWith(d.path);
95
- });
96
- if (parent) {
97
- if (!parent.children) {
98
- parent.children = [];
99
- }
100
- findParent(parent.children, node, parent);
101
- } else {
102
- node.path = node.routePath?.replace(parentNode?.routePath ?? '', '');
103
- node.parent = parentNode;
104
- existing.push(node);
129
+ routeNodes.forEach(existingNode => {
130
+ if (node.routePath?.startsWith(`${existingNode?.routePath ?? ''}/`)
131
+ // node.routePath.length > existingNode.routePath!.length
132
+ ) {
133
+ node.parent = existingNode;
105
134
  }
106
- };
107
- findParent(routeTree, node);
135
+ });
136
+ node.path = node.parent ? node.routePath?.replace(node.parent.routePath, '') || '/' : node.routePath;
137
+ const trimmedPath = routerCore.trimPathLeft(node.path ?? '');
138
+ const split = trimmedPath?.split('/') ?? [];
139
+ let first = split[0] ?? trimmedPath ?? '';
140
+ node.isNonPath = first.startsWith('_');
141
+ node.isNonLayout = first.endsWith('_');
142
+ node.cleanedPath = removeUnderscores(node.path) ?? '';
143
+ if (node.parent) {
144
+ node.parent.children = node.parent.children ?? [];
145
+ node.parent.children.push(node);
146
+ } else {
147
+ routeTree.push(node);
148
+ }
108
149
  });
109
150
  async function buildRouteConfig(nodes, depth = 1) {
110
- const children = nodes.map(async n => {
111
- let node = nodeCache.find(d => d.fullPath === n.fullPath);
112
- if (node) {
113
- node.new = false;
114
- } else {
115
- node = n;
116
- nodeCache.push(node);
117
- if (!first) {
118
- node.new = true;
119
- }
120
- }
121
- node.version = latestTask;
151
+ const children = nodes.map(async node => {
122
152
  const routeCode = await fs__default["default"].readFile(node.fullPath, 'utf-8');
123
- const hashSum = crypto__default["default"].createHash('sha256');
124
- hashSum.update(routeCode);
125
- const hash = hashSum.digest('hex');
126
- node.changed = node.hash !== hash;
127
- if (node.changed) {
128
- nodesChanged = true;
129
- node.hash = hash;
130
- try {
131
- // Ensure the boilerplate for the route exists
132
- const code = await ensureBoilerplate(node, routeCode);
133
- if (code) {
134
- await fs__default["default"].writeFile(node.fullPath, code);
135
- }
136
- } catch (err) {
137
- node.hash = '';
138
- throw err;
139
- }
153
+
154
+ // Ensure the boilerplate for the route exists
155
+ if (node.isRoot) {
156
+ return;
157
+ }
158
+
159
+ // Ensure that new FileRoute(anything?) is replace with FileRoute(${node.routePath})
160
+ const replaced = routeCode.replace(fileRouteRegex, `new FileRoute('${node.routePath}')`);
161
+ if (replaced !== routeCode) {
162
+ await fs__default["default"].writeFile(node.fullPath, replaced);
140
163
  }
141
164
  const route = `${node.variableName}Route`;
142
- routeConfigImports.push(`import { route as ${route} } from './${removeExt(path__default["default"].relative(path__default["default"].dirname(config.generatedRouteTree), path__default["default"].resolve(config.routesDirectory, node.filename)))}'`);
143
165
  if (node.children?.length) {
144
166
  const childConfigs = await buildRouteConfig(node.children, depth + 1);
145
- return `${route}.addChildren([\n${spaces(depth * 4)}${childConfigs}\n${spaces(depth * 2)}])`;
167
+ return `${route}.addChildren([${spaces(depth * 4)}${childConfigs}])`;
146
168
  }
147
169
  return route;
148
170
  });
149
- return (await Promise.all(children)).filter(Boolean).join(`,\n${spaces(depth * 2)}`);
171
+ return (await Promise.all(children)).filter(Boolean).join(`,`);
150
172
  }
151
173
  const routeConfigChildrenText = await buildRouteConfig(routeTree);
152
- routeConfigImports = multiSortBy(routeConfigImports, [d => d.includes(rootRouteName) ? -1 : 1, d => d.split('/').length, d => d.endsWith("index'") ? -1 : 1, d => d]);
153
- const routeConfig = `export const routeTree = rootRoute.addChildren([\n ${routeConfigChildrenText}\n])`;
154
- const routeConfigFileContent = [`import { route as rootRoute } from './${path__default["default"].relative(path__default["default"].dirname(config.generatedRouteTree), path__default["default"].resolve(config.routesDirectory, '_root'))}'`, routeConfigImports.join('\n'), `declare module '@tanstack/react-router' {
174
+ const routeImports = [`import { route as rootRoute } from './${path__default["default"].relative(path__default["default"].dirname(config.generatedRouteTree), path__default["default"].resolve(config.routesDirectory, rootPathId))}'`, ...multiSortBy(routeNodes, [d => d.routePath?.includes(`/${rootPathId}`) ? -1 : 1, d => d.routePath?.split('/').length, d => d.routePath?.endsWith("index'") ? -1 : 1, d => d]).map(node => {
175
+ return `import { route as ${node.variableName}Route } from './${removeExt(path__default["default"].relative(path__default["default"].dirname(config.generatedRouteTree), path__default["default"].resolve(config.routesDirectory, node.filePath)))}'`;
176
+ })].join('\n');
177
+ const routeTypes = `declare module '@tanstack/react-router' {
155
178
  interface FileRoutesByPath {
156
179
  ${routeNodes.map(routeNode => {
157
180
  return `'${routeNode.routePath}': {
158
- parentRoute: typeof ${routeNode.parent?.variableName ?? 'root'}Route
159
- }`;
160
- }).join('\n ')}
181
+ parentRoute: typeof ${routeNode.parent?.variableName ?? 'root'}Route
182
+ }`;
183
+ }).join('\n')}
161
184
  }
162
- }`, routeNodes.map(routeNode => {
185
+ }`;
186
+ const routeOptions = routeNodes.map(routeNode => {
163
187
  return `Object.assign(${routeNode.variableName ?? 'root'}Route.options, {
164
- path: '${routeNode.path}',
165
- getParentRoute: () => ${`${routeNode.parent?.variableName ?? 'root'}Route,
166
- })`}`;
167
- }).join('\n'), routeConfig].join('\n\n');
168
- if (nodesChanged) {
169
- queueWriteFile(path__default["default"].resolve(config.generatedRouteTree), routeConfigFileContent);
170
- }
171
- if (!checkLatest()) return;
172
- await Promise.all(fileQueue.map(async ([filename, content]) => {
173
- await fs__default["default"].ensureDir(path__default["default"].dirname(filename));
174
- const exists = await fs__default["default"].pathExists(filename);
175
- let current = '';
176
- if (exists) {
177
- current = await fs__default["default"].readFile(filename, 'utf-8');
178
- }
179
- if (current !== content) {
180
- await fs__default["default"].writeFile(filename, content);
181
- }
182
- }));
183
- if (!checkLatest()) return;
184
- const removedNodes = [];
185
- nodeCache = nodeCache.filter(d => {
186
- if (d.version !== latestTask) {
187
- removedNodes.push(d);
188
- return false;
189
- }
190
- return true;
188
+ ${[routeNode.isNonPath ? `id: '${routeNode.cleanedPath}'` : `path: '${routeNode.cleanedPath}'`, `getParentRoute: () => ${routeNode.parent?.variableName ?? 'root'}Route`, routeNode.layoutLimit ? `layoutLimit: '${routeNode.layoutLimit}'` : ''
189
+ // `\n// ${JSON.stringify(
190
+ // {
191
+ // ...routeNode,
192
+ // parent: undefined,
193
+ // children: undefined,
194
+ // fullPath: undefined,
195
+ // variableName: undefined,
196
+ // },
197
+ // null,
198
+ // 2,
199
+ // )
200
+ // .split('\n')
201
+ // .join('\n// ')}`,
202
+ ].filter(Boolean).join(',')}
203
+ })`;
204
+ }).join('\n\n');
205
+ const routeConfig = `export const routeTree = rootRoute.addChildren([${routeConfigChildrenText}])`;
206
+ const routeConfigFileContent = await prettier__namespace.format([routeImports, routeTypes, routeOptions, routeConfig].join('\n\n'), {
207
+ semi: false,
208
+ parser: 'typescript'
191
209
  });
192
- const newNodes = nodeCache.filter(d => d.new);
193
- const updatedNodes = nodeCache.filter(d => !d.new && d.changed);
194
- console.log(`🌲 Processed ${nodeCache.length} routes in ${Date.now() - start}ms`);
195
- if (newNodes.length || updatedNodes.length || removedNodes.length) {
196
- if (newNodes.length) {
197
- console.log(`🥳 Added ${newNodes.length} new routes`);
198
- }
199
- if (updatedNodes.length) {
200
- console.log(`✅ Updated ${updatedNodes.length} routes`);
201
- }
202
- if (removedNodes.length) {
203
- console.log(`🗑 Removed ${removedNodes.length} unused routes`);
210
+ const routeTreeContent = await fs__default["default"].readFile(path__default["default"].resolve(config.generatedRouteTree), 'utf-8').catch(err => {
211
+ if (err.code === 'ENOENT') {
212
+ return undefined;
204
213
  }
205
- } else {
206
- console.log(`🎉 No changes were found. Carry on!`);
207
- }
208
- }
209
- async function ensureBoilerplate(node, code) {
210
- if (node.isRoot) {
211
- return;
212
- }
213
-
214
- // Ensure that new FileRoute(anything?) is replace with FileRoute(${node.routePath})
215
- const replaced = code.replace(/new\s+FileRoute\(([^)]*)\)/g, `new FileRoute('${node.routePath}')`);
216
- if (replaced !== code) {
217
- return replaced;
214
+ throw err;
215
+ });
216
+ if (!checkLatest()) return;
217
+ if (routeTreeContent !== routeConfigFileContent) {
218
+ await fs__default["default"].ensureDir(path__default["default"].dirname(path__default["default"].resolve(config.generatedRouteTree)));
219
+ if (!checkLatest()) return;
220
+ await fs__default["default"].writeFile(path__default["default"].resolve(config.generatedRouteTree), routeConfigFileContent);
218
221
  }
219
- return;
222
+ console.log(`🌲 Processed ${routeNodes.length} routes in ${Date.now() - start}ms`);
220
223
  }
221
224
  function fileToVariable(d) {
222
- return d.split('/').map((d, i) => i > 0 ? capitalize(d) : d).join('').replace(/([^a-zA-Z0-9]|[\.])/gm, '');
225
+ return removeUnderscores(d)?.replace(/\$/g, '')?.split(/[/-]/g).map((d, i) => i > 0 ? capitalize(d) : d).join('').replace(/([^a-zA-Z0-9]|[\.])/gm, '') ?? '';
223
226
  }
224
227
  function removeExt(d) {
225
228
  return d.substring(0, d.lastIndexOf('.')) || d;
@@ -252,9 +255,13 @@ function capitalize(s) {
252
255
  if (typeof s !== 'string') return '';
253
256
  return s.charAt(0).toUpperCase() + s.slice(1);
254
257
  }
258
+ function removeUnderscores(s) {
259
+ return s?.replace(/(^_|_$)/, '').replace(/(\/_|_\/)/, '/');
260
+ }
255
261
 
262
+ exports.fileRouteRegex = fileRouteRegex;
256
263
  exports.generator = generator;
257
264
  exports.multiSortBy = multiSortBy;
258
265
  exports.removeExt = removeExt;
259
- exports.rootRouteName = rootRouteName;
266
+ exports.rootPathId = rootPathId;
260
267
  //# sourceMappingURL=generator.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"generator.js","sources":["../../src/generator.ts"],"sourcesContent":["import path from 'path'\nimport fs from 'fs-extra'\nimport crypto from 'crypto'\nimport { Config } from './config'\n\nlet latestTask = 0\nexport const rootRouteName = 'root'\n\nexport type RouteNode = {\n filename: string\n fullPath: string\n variableName: string\n routePath?: string\n path?: string\n id?: string\n hash?: string\n version?: number\n changed?: boolean\n new?: boolean\n isRoot?: boolean\n children?: RouteNode[]\n parent?: RouteNode\n}\n\nlet nodeCache: RouteNode[] = undefined!\n\nasync function createRouteNode(\n config: Config,\n fileName: string,\n): Promise<RouteNode> {\n const filename = path.basename(fileName)\n const fullPath = path.resolve(config.routesDirectory, filename)\n const fileNameNoExt = removeExt(filename)\n let routePath = fileNameNoExt.split('.').join('/')\n\n // Remove the index from the route path and\n // if the route path is empty, use `/'\n if (routePath.endsWith('/index')) {\n routePath = routePath.replace(/\\/index$/, '/')\n } else if (routePath === 'index') {\n routePath = '/'\n }\n\n return {\n filename,\n fullPath,\n routePath,\n variableName: fileToVariable(removeExt(filename)),\n }\n}\n\nexport async function generator(config: Config) {\n console.log()\n\n let first = false\n\n if (!nodeCache) {\n first = true\n console.log('🔄 Generating routes...')\n nodeCache = []\n } else {\n console.log('♻️ Regenerating routes...')\n }\n\n const taskId = latestTask + 1\n latestTask = taskId\n\n const checkLatest = () => {\n if (latestTask !== taskId) {\n console.log(`- Skipping since file changes were made while generating.`)\n return false\n }\n\n return true\n }\n\n const start = Date.now()\n let routeConfigImports: string[] = []\n\n let nodesChanged = false\n const fileQueue: [string, string][] = []\n const queueWriteFile = (filename: string, content: string) => {\n fileQueue.push([filename, content])\n }\n\n let dirList\n\n try {\n dirList = await fs.readdir(config.routesDirectory)\n } catch (err) {\n console.log()\n console.error(\n 'TSR: Error reading the config.routesDirectory. Does it exist?',\n )\n console.log()\n throw err\n }\n\n let routeNodes = await Promise.all(\n dirList.map(async (filename): Promise<RouteNode> => {\n return createRouteNode(config, filename)\n }),\n )\n\n routeNodes = multiSortBy(routeNodes, [\n (d) => (d.routePath === '/' ? -1 : 1),\n (d) => d.routePath?.split('/').length,\n (d) => (d.routePath?.endsWith('/') ? -1 : 1),\n (d) => d.routePath,\n ]).filter((d) => d.routePath !== '_root')\n\n const routeTree: RouteNode[] = []\n\n // Loop over the flat list of routeNodes and\n // build up a tree based on the routeNodes' routePath\n routeNodes.forEach((node) => {\n const findParent = (\n existing: RouteNode[],\n node: RouteNode,\n parentNode?: RouteNode,\n ) => {\n const parent = existing.find((d) => {\n return node.routePath?.startsWith(d.path!)\n })\n\n if (parent) {\n if (!parent.children) {\n parent.children = []\n }\n\n findParent(parent.children, node, parent)\n } else {\n node.path = node.routePath?.replace(parentNode?.routePath ?? '', '')\n node.parent = parentNode\n existing.push(node)\n }\n }\n\n findParent(routeTree, node)\n })\n\n async function buildRouteConfig(\n nodes: RouteNode[],\n depth = 1,\n ): Promise<string> {\n const children = nodes.map(async (n) => {\n let node = nodeCache.find((d) => d.fullPath === n.fullPath)!\n\n if (node) {\n node.new = false\n } else {\n node = n\n nodeCache.push(node)\n if (!first) {\n node.new = true\n }\n }\n\n node.version = latestTask\n\n const routeCode = await fs.readFile(node.fullPath, 'utf-8')\n const hashSum = crypto.createHash('sha256')\n hashSum.update(routeCode)\n const hash = hashSum.digest('hex')\n\n node.changed = node.hash !== hash\n if (node.changed) {\n nodesChanged = true\n node.hash = hash\n\n try {\n // Ensure the boilerplate for the route exists\n const code = await ensureBoilerplate(node, routeCode)\n\n if (code) {\n await fs.writeFile(node.fullPath, code)\n }\n } catch (err) {\n node.hash = ''\n throw err\n }\n }\n\n const route = `${node.variableName}Route`\n\n routeConfigImports.push(\n `import { route as ${route} } from './${removeExt(\n path.relative(\n path.dirname(config.generatedRouteTree),\n path.resolve(config.routesDirectory, node.filename),\n ),\n )}'`,\n )\n\n if (node.children?.length) {\n const childConfigs = await buildRouteConfig(node.children, depth + 1)\n return `${route}.addChildren([\\n${spaces(\n depth * 4,\n )}${childConfigs}\\n${spaces(depth * 2)}])`\n }\n\n return route\n })\n\n return (await Promise.all(children))\n .filter(Boolean)\n .join(`,\\n${spaces(depth * 2)}`)\n }\n\n const routeConfigChildrenText = await buildRouteConfig(routeTree)\n\n routeConfigImports = multiSortBy(routeConfigImports, [\n (d) => (d.includes(rootRouteName) ? -1 : 1),\n (d) => d.split('/').length,\n (d) => (d.endsWith(\"index'\") ? -1 : 1),\n (d) => d,\n ])\n\n const routeConfig = `export const routeTree = rootRoute.addChildren([\\n ${routeConfigChildrenText}\\n])`\n\n const routeConfigFileContent = [\n `import { route as rootRoute } from './${path.relative(\n path.dirname(config.generatedRouteTree),\n path.resolve(config.routesDirectory, '_root'),\n )}'`,\n routeConfigImports.join('\\n'),\n `declare module '@tanstack/react-router' {\n interface FileRoutesByPath {\n ${routeNodes\n .map((routeNode) => {\n return `'${routeNode.routePath}': {\n parentRoute: typeof ${routeNode.parent?.variableName ?? 'root'}Route\n }`\n })\n .join('\\n ')} \n }\n}`,\n routeNodes\n .map((routeNode) => {\n return `Object.assign(${\n routeNode.variableName ?? 'root'\n }Route.options, {\n path: '${routeNode.path}',\n getParentRoute: () => ${`${routeNode.parent?.variableName ?? 'root'}Route,\n})`}`\n })\n .join('\\n'),\n routeConfig,\n ].join('\\n\\n')\n\n if (nodesChanged) {\n queueWriteFile(\n path.resolve(config.generatedRouteTree),\n routeConfigFileContent,\n )\n }\n\n if (!checkLatest()) return\n\n await Promise.all(\n fileQueue.map(async ([filename, content]) => {\n await fs.ensureDir(path.dirname(filename))\n const exists = await fs.pathExists(filename)\n let current = ''\n if (exists) {\n current = await fs.readFile(filename, 'utf-8')\n }\n if (current !== content) {\n await fs.writeFile(filename, content)\n }\n }),\n )\n\n if (!checkLatest()) return\n\n const removedNodes: RouteNode[] = []\n\n nodeCache = nodeCache.filter((d) => {\n if (d.version !== latestTask) {\n removedNodes.push(d)\n return false\n }\n return true\n })\n\n const newNodes = nodeCache.filter((d) => d.new)\n const updatedNodes = nodeCache.filter((d) => !d.new && d.changed)\n\n console.log(\n `🌲 Processed ${nodeCache.length} routes in ${Date.now() - start}ms`,\n )\n\n if (newNodes.length || updatedNodes.length || removedNodes.length) {\n if (newNodes.length) {\n console.log(`🥳 Added ${newNodes.length} new routes`)\n }\n\n if (updatedNodes.length) {\n console.log(`✅ Updated ${updatedNodes.length} routes`)\n }\n\n if (removedNodes.length) {\n console.log(`🗑 Removed ${removedNodes.length} unused routes`)\n }\n } else {\n console.log(`🎉 No changes were found. Carry on!`)\n }\n}\n\nasync function ensureBoilerplate(node: RouteNode, code: string) {\n if (node.isRoot) {\n return\n }\n\n // Ensure that new FileRoute(anything?) is replace with FileRoute(${node.routePath})\n const replaced = code.replace(\n /new\\s+FileRoute\\(([^)]*)\\)/g,\n `new FileRoute('${node.routePath}')`,\n )\n\n if (replaced !== code) {\n return replaced\n }\n\n return\n}\n\nfunction fileToVariable(d: string) {\n return d\n .split('/')\n .map((d, i) => (i > 0 ? capitalize(d) : d))\n .join('')\n .replace(/([^a-zA-Z0-9]|[\\.])/gm, '')\n}\n\nexport function removeExt(d: string) {\n return d.substring(0, d.lastIndexOf('.')) || d\n}\n\nfunction spaces(d: number): string {\n return Array.from({ length: d })\n .map(() => ' ')\n .join('')\n}\n\nexport function multiSortBy<T>(\n arr: T[],\n accessors: ((item: T) => any)[] = [(d) => d],\n): T[] {\n return arr\n .map((d, i) => [d, i] as const)\n .sort(([a, ai], [b, bi]) => {\n for (const accessor of accessors) {\n const ao = accessor(a)\n const bo = accessor(b)\n\n if (typeof ao === 'undefined') {\n if (typeof bo === 'undefined') {\n continue\n }\n return 1\n }\n\n if (ao === bo) {\n continue\n }\n\n return ao > bo ? 1 : -1\n }\n\n return ai - bi\n })\n .map(([d]) => d)\n}\n\nfunction capitalize(s: string) {\n if (typeof s !== 'string') return ''\n return s.charAt(0).toUpperCase() + s.slice(1)\n}\n"],"names":["latestTask","rootRouteName","nodeCache","undefined","createRouteNode","config","fileName","filename","path","basename","fullPath","resolve","routesDirectory","fileNameNoExt","removeExt","routePath","split","join","endsWith","replace","variableName","fileToVariable","generator","console","log","first","taskId","checkLatest","start","Date","now","routeConfigImports","nodesChanged","fileQueue","queueWriteFile","content","push","dirList","fs","readdir","err","error","routeNodes","Promise","all","map","multiSortBy","d","length","filter","routeTree","forEach","node","findParent","existing","parentNode","parent","find","startsWith","children","buildRouteConfig","nodes","depth","n","new","version","routeCode","readFile","hashSum","crypto","createHash","update","hash","digest","changed","code","ensureBoilerplate","writeFile","route","relative","dirname","generatedRouteTree","childConfigs","spaces","Boolean","routeConfigChildrenText","includes","routeConfig","routeConfigFileContent","routeNode","ensureDir","exists","pathExists","current","removedNodes","newNodes","updatedNodes","isRoot","replaced","i","capitalize","substring","lastIndexOf","Array","from","arr","accessors","sort","a","ai","b","bi","accessor","ao","bo","s","charAt","toUpperCase","slice"],"mappings":"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"}
1
+ {"version":3,"file":"generator.js","sources":["../../src/generator.ts"],"sourcesContent":["import path from 'path'\nimport fs from 'fs-extra'\nimport * as prettier from 'prettier'\nimport { Config } from './config'\nimport { cleanPath, trimPathLeft } from '@tanstack/router-core'\n\nlet latestTask = 0\nexport const rootPathId = '__root'\nexport const fileRouteRegex = /new\\s+FileRoute\\(([^)]*)\\)/g\n\nexport type RouteNode = {\n filePath: string\n fullPath: string\n variableName: string\n routePath?: string\n cleanedPath?: string\n path?: string\n isNonPath?: boolean\n isNonLayout?: boolean\n isRoot?: boolean\n children?: RouteNode[]\n parent?: RouteNode\n layoutLimit?: string\n}\n\nasync function getRouteNodes(config: Config) {\n const { routeFilePrefix, routeFileIgnorePrefix } = config\n\n let routeNodes: RouteNode[] = []\n\n async function recurse(dir: string) {\n const fullDir = path.resolve(config.routesDirectory, dir)\n let dirList = await fs.readdir(fullDir)\n\n dirList = dirList.filter((d) => {\n if (d.startsWith('.') || d.startsWith(routeFileIgnorePrefix)) {\n return false\n }\n\n if (routeFilePrefix) {\n return d.startsWith(routeFilePrefix)\n }\n\n return true\n })\n\n await Promise.all(\n dirList.map(async (fileName) => {\n const fullPath = path.join(fullDir, fileName)\n const relativePath = path.join(dir, fileName)\n const stat = await fs.stat(fullPath)\n\n if (stat.isDirectory()) {\n await recurse(relativePath)\n } else {\n const filePath = path.join(dir, fileName)\n const filePathNoExt = removeExt(filePath)\n let routePath = cleanPath(`/${filePathNoExt.split('.').join('/')}`)\n const variableName = fileToVariable(routePath)\n\n // Remove the index from the route path and\n // if the route path is empty, use `/'\n if (routePath.endsWith('/index')) {\n routePath = routePath.replace(/\\/index$/, '/')\n } else if (routePath === 'index') {\n routePath = '/'\n }\n\n // console.log(filePath)\n\n routeNodes.push({\n filePath,\n fullPath,\n routePath,\n variableName,\n })\n }\n }),\n )\n\n return routeNodes\n }\n\n await recurse('./')\n\n return routeNodes\n}\n\nlet first = false\nlet skipMessage = false\n\nexport async function generator(config: Config) {\n console.log()\n\n if (!first) {\n console.log('🔄 Generating routes...')\n first = true\n } else if (skipMessage) {\n skipMessage = false\n } else {\n console.log('♻️ Regenerating routes...')\n }\n\n const taskId = latestTask + 1\n latestTask = taskId\n\n const checkLatest = () => {\n if (latestTask !== taskId) {\n skipMessage = true\n return false\n }\n\n return true\n }\n\n const start = Date.now()\n\n let routeNodes = await getRouteNodes(config)\n\n routeNodes = multiSortBy(routeNodes, [\n (d) => (d.routePath === '/' ? -1 : 1),\n (d) => d.routePath?.split('/').length,\n (d) => (d.routePath?.endsWith('/') ? -1 : 1),\n (d) => d.routePath,\n ]).filter((d) => d.routePath !== `/${rootPathId}`)\n\n const routeTree: RouteNode[] = []\n\n // Loop over the flat list of routeNodes and\n // build up a tree based on the routeNodes' routePath\n routeNodes.forEach((node) => {\n routeNodes.forEach((existingNode) => {\n if (\n node.routePath?.startsWith(`${existingNode?.routePath ?? ''}/`)\n // node.routePath.length > existingNode.routePath!.length\n ) {\n node.parent = existingNode\n }\n })\n\n node.path = node.parent\n ? node.routePath?.replace(node.parent.routePath!, '') || '/'\n : node.routePath\n\n const trimmedPath = trimPathLeft(node.path ?? '')\n\n const split = trimmedPath?.split('/') ?? []\n let first = split[0] ?? trimmedPath ?? ''\n\n node.isNonPath = first.startsWith('_')\n node.isNonLayout = first.endsWith('_')\n\n node.cleanedPath = removeUnderscores(node.path) ?? ''\n\n if (node.parent) {\n node.parent.children = node.parent.children ?? []\n node.parent.children.push(node)\n } else {\n routeTree.push(node)\n }\n })\n\n async function buildRouteConfig(\n nodes: RouteNode[],\n depth = 1,\n ): Promise<string> {\n const children = nodes.map(async (node) => {\n const routeCode = await fs.readFile(node.fullPath, 'utf-8')\n\n // Ensure the boilerplate for the route exists\n if (node.isRoot) {\n return\n }\n\n // Ensure that new FileRoute(anything?) is replace with FileRoute(${node.routePath})\n const replaced = routeCode.replace(\n fileRouteRegex,\n `new FileRoute('${node.routePath}')`,\n )\n\n if (replaced !== routeCode) {\n await fs.writeFile(node.fullPath, replaced)\n }\n\n const route = `${node.variableName}Route`\n\n if (node.children?.length) {\n const childConfigs = await buildRouteConfig(node.children, depth + 1)\n return `${route}.addChildren([${spaces(depth * 4)}${childConfigs}])`\n }\n\n return route\n })\n\n return (await Promise.all(children)).filter(Boolean).join(`,`)\n }\n\n const routeConfigChildrenText = await buildRouteConfig(routeTree)\n\n const routeImports = [\n `import { route as rootRoute } from './${path.relative(\n path.dirname(config.generatedRouteTree),\n path.resolve(config.routesDirectory, rootPathId),\n )}'`,\n ...multiSortBy(routeNodes, [\n (d) => (d.routePath?.includes(`/${rootPathId}`) ? -1 : 1),\n (d) => d.routePath?.split('/').length,\n (d) => (d.routePath?.endsWith(\"index'\") ? -1 : 1),\n (d) => d,\n ]).map((node) => {\n return `import { route as ${node.variableName}Route } from './${removeExt(\n path.relative(\n path.dirname(config.generatedRouteTree),\n path.resolve(config.routesDirectory, node.filePath),\n ),\n )}'`\n }),\n ].join('\\n')\n\n const routeTypes = `declare module '@tanstack/react-router' {\n interface FileRoutesByPath {\n ${routeNodes\n .map((routeNode) => {\n return `'${routeNode.routePath}': {\n parentRoute: typeof ${routeNode.parent?.variableName ?? 'root'}Route\n }`\n })\n .join('\\n')} \n }\n}`\n\n const routeOptions = routeNodes\n .map((routeNode) => {\n return `Object.assign(${routeNode.variableName ?? 'root'}Route.options, {\n ${[\n routeNode.isNonPath\n ? `id: '${routeNode.cleanedPath}'`\n : `path: '${routeNode.cleanedPath}'`,\n `getParentRoute: () => ${\n routeNode.parent?.variableName ?? 'root'\n }Route`,\n routeNode.layoutLimit\n ? `layoutLimit: '${routeNode.layoutLimit}'`\n : '',\n // `\\n// ${JSON.stringify(\n // {\n // ...routeNode,\n // parent: undefined,\n // children: undefined,\n // fullPath: undefined,\n // variableName: undefined,\n // },\n // null,\n // 2,\n // )\n // .split('\\n')\n // .join('\\n// ')}`,\n ]\n .filter(Boolean)\n .join(',')}\n })`\n })\n .join('\\n\\n')\n\n const routeConfig = `export const routeTree = rootRoute.addChildren([${routeConfigChildrenText}])`\n\n const routeConfigFileContent = await prettier.format(\n [routeImports, routeTypes, routeOptions, routeConfig].join('\\n\\n'),\n {\n semi: false,\n parser: 'typescript',\n },\n )\n\n const routeTreeContent = await fs\n .readFile(path.resolve(config.generatedRouteTree), 'utf-8')\n .catch((err: any) => {\n if (err.code === 'ENOENT') {\n return undefined\n }\n throw err\n })\n\n if (!checkLatest()) return\n\n if (routeTreeContent !== routeConfigFileContent) {\n await fs.ensureDir(path.dirname(path.resolve(config.generatedRouteTree)))\n if (!checkLatest()) return\n await fs.writeFile(\n path.resolve(config.generatedRouteTree),\n routeConfigFileContent,\n )\n }\n\n console.log(\n `🌲 Processed ${routeNodes.length} routes in ${Date.now() - start}ms`,\n )\n}\n\nfunction fileToVariable(d: string): string {\n return (\n removeUnderscores(d)\n ?.replace(/\\$/g, '')\n ?.split(/[/-]/g)\n .map((d, i) => (i > 0 ? capitalize(d) : d))\n .join('')\n .replace(/([^a-zA-Z0-9]|[\\.])/gm, '') ?? ''\n )\n}\n\nexport function removeExt(d: string) {\n return d.substring(0, d.lastIndexOf('.')) || d\n}\n\nfunction spaces(d: number): string {\n return Array.from({ length: d })\n .map(() => ' ')\n .join('')\n}\n\nexport function multiSortBy<T>(\n arr: T[],\n accessors: ((item: T) => any)[] = [(d) => d],\n): T[] {\n return arr\n .map((d, i) => [d, i] as const)\n .sort(([a, ai], [b, bi]) => {\n for (const accessor of accessors) {\n const ao = accessor(a)\n const bo = accessor(b)\n\n if (typeof ao === 'undefined') {\n if (typeof bo === 'undefined') {\n continue\n }\n return 1\n }\n\n if (ao === bo) {\n continue\n }\n\n return ao > bo ? 1 : -1\n }\n\n return ai - bi\n })\n .map(([d]) => d)\n}\n\nfunction capitalize(s: string) {\n if (typeof s !== 'string') return ''\n return s.charAt(0).toUpperCase() + s.slice(1)\n}\n\nfunction removeUnderscores(s?: string) {\n return s?.replace(/(^_|_$)/, '').replace(/(\\/_|_\\/)/, '/')\n}\n"],"names":["latestTask","rootPathId","fileRouteRegex","getRouteNodes","config","routeFilePrefix","routeFileIgnorePrefix","routeNodes","recurse","dir","fullDir","path","resolve","routesDirectory","dirList","fs","readdir","filter","d","startsWith","Promise","all","map","fileName","fullPath","join","relativePath","stat","isDirectory","filePath","filePathNoExt","removeExt","routePath","cleanPath","split","variableName","fileToVariable","endsWith","replace","push","first","skipMessage","generator","console","log","taskId","checkLatest","start","Date","now","multiSortBy","length","routeTree","forEach","node","existingNode","parent","trimmedPath","trimPathLeft","isNonPath","isNonLayout","cleanedPath","removeUnderscores","children","buildRouteConfig","nodes","depth","routeCode","readFile","isRoot","replaced","writeFile","route","childConfigs","spaces","Boolean","routeConfigChildrenText","routeImports","relative","dirname","generatedRouteTree","includes","routeTypes","routeNode","routeOptions","layoutLimit","routeConfig","routeConfigFileContent","prettier","format","semi","parser","routeTreeContent","catch","err","code","undefined","ensureDir","i","capitalize","substring","lastIndexOf","Array","from","arr","accessors","sort","a","ai","b","bi","accessor","ao","bo","s","charAt","toUpperCase","slice"],"mappings":"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"}
@@ -24,19 +24,13 @@ var path__default = /*#__PURE__*/_interopDefaultLegacy(path);
24
24
 
25
25
  async function watch() {
26
26
  const configWatcher = chokidar__default["default"].watch(path__default["default"].resolve(process.cwd(), 'tsr.config.js'));
27
- let watcher = new chokidar__default["default"].FSWatcher();
27
+ let watcher = new chokidar__default["default"].FSWatcher({});
28
28
  const generatorWatcher = async () => {
29
29
  const config$1 = await config.getConfig();
30
30
  watcher.close();
31
31
  console.log(`TSR: Watching routes (${config$1.routesDirectory})...`);
32
32
  watcher = chokidar__default["default"].watch(config$1.routesDirectory);
33
33
  watcher.on('ready', async () => {
34
- try {
35
- await generator.generator(config$1);
36
- } catch (err) {
37
- console.error(err);
38
- console.log();
39
- }
40
34
  const handle = async () => {
41
35
  try {
42
36
  await generator.generator(config$1);
@@ -45,11 +39,17 @@ async function watch() {
45
39
  console.log();
46
40
  }
47
41
  };
48
- watcher.on('change', handle);
49
- watcher.on('add', handle);
50
- watcher.on('addDir', handle);
51
- watcher.on('unlink', handle);
52
- watcher.on('unlinkDir', handle);
42
+ await handle();
43
+ let timeout;
44
+ const deduped = file => {
45
+ if (timeout) {
46
+ clearTimeout(timeout);
47
+ }
48
+ timeout = setTimeout(handle, 10);
49
+ };
50
+ watcher.on('change', deduped);
51
+ watcher.on('add', deduped);
52
+ watcher.on('unlink', deduped);
53
53
  });
54
54
  };
55
55
  configWatcher.on('ready', generatorWatcher);
@@ -1 +1 @@
1
- {"version":3,"file":"watch.js","sources":["../../src/watch.ts"],"sourcesContent":["import chokidar from 'chokidar'\nimport path from 'path'\nimport { getConfig } from './config'\nimport { generator } from './generator'\n\nexport async function watch() {\n const configWatcher = chokidar.watch(\n path.resolve(process.cwd(), 'tsr.config.js'),\n )\n\n let watcher = new chokidar.FSWatcher()\n\n const generatorWatcher = async () => {\n const config = await getConfig()\n\n watcher.close()\n\n console.log(`TSR: Watching routes (${config.routesDirectory})...`)\n watcher = chokidar.watch(config.routesDirectory)\n\n watcher.on('ready', async () => {\n try {\n await generator(config)\n } catch (err) {\n console.error(err)\n console.log()\n }\n\n const handle = async () => {\n try {\n await generator(config)\n } catch (err) {\n console.error(err)\n console.log()\n }\n }\n\n watcher.on('change', handle)\n watcher.on('add', handle)\n watcher.on('addDir', handle)\n watcher.on('unlink', handle)\n watcher.on('unlinkDir', handle)\n })\n }\n\n configWatcher.on('ready', generatorWatcher)\n configWatcher.on('change', generatorWatcher)\n}\n"],"names":["watch","configWatcher","chokidar","path","resolve","process","cwd","watcher","FSWatcher","generatorWatcher","config","getConfig","close","console","log","routesDirectory","on","generator","err","error","handle"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAKO,eAAeA,KAAKA,GAAG;AAC5B,EAAA,MAAMC,aAAa,GAAGC,4BAAQ,CAACF,KAAK,CAClCG,wBAAI,CAACC,OAAO,CAACC,OAAO,CAACC,GAAG,EAAE,EAAE,eAAe,CAC7C,CAAC,CAAA;AAED,EAAA,IAAIC,OAAO,GAAG,IAAIL,4BAAQ,CAACM,SAAS,EAAE,CAAA;AAEtC,EAAA,MAAMC,gBAAgB,GAAG,YAAY;AACnC,IAAA,MAAMC,QAAM,GAAG,MAAMC,gBAAS,EAAE,CAAA;IAEhCJ,OAAO,CAACK,KAAK,EAAE,CAAA;IAEfC,OAAO,CAACC,GAAG,CAAE,CAAA,sBAAA,EAAwBJ,QAAM,CAACK,eAAgB,MAAK,CAAC,CAAA;IAClER,OAAO,GAAGL,4BAAQ,CAACF,KAAK,CAACU,QAAM,CAACK,eAAe,CAAC,CAAA;AAEhDR,IAAAA,OAAO,CAACS,EAAE,CAAC,OAAO,EAAE,YAAY;MAC9B,IAAI;QACF,MAAMC,mBAAS,CAACP,QAAM,CAAC,CAAA;OACxB,CAAC,OAAOQ,GAAG,EAAE;AACZL,QAAAA,OAAO,CAACM,KAAK,CAACD,GAAG,CAAC,CAAA;QAClBL,OAAO,CAACC,GAAG,EAAE,CAAA;AACf,OAAA;AAEA,MAAA,MAAMM,MAAM,GAAG,YAAY;QACzB,IAAI;UACF,MAAMH,mBAAS,CAACP,QAAM,CAAC,CAAA;SACxB,CAAC,OAAOQ,GAAG,EAAE;AACZL,UAAAA,OAAO,CAACM,KAAK,CAACD,GAAG,CAAC,CAAA;UAClBL,OAAO,CAACC,GAAG,EAAE,CAAA;AACf,SAAA;OACD,CAAA;AAEDP,MAAAA,OAAO,CAACS,EAAE,CAAC,QAAQ,EAAEI,MAAM,CAAC,CAAA;AAC5Bb,MAAAA,OAAO,CAACS,EAAE,CAAC,KAAK,EAAEI,MAAM,CAAC,CAAA;AACzBb,MAAAA,OAAO,CAACS,EAAE,CAAC,QAAQ,EAAEI,MAAM,CAAC,CAAA;AAC5Bb,MAAAA,OAAO,CAACS,EAAE,CAAC,QAAQ,EAAEI,MAAM,CAAC,CAAA;AAC5Bb,MAAAA,OAAO,CAACS,EAAE,CAAC,WAAW,EAAEI,MAAM,CAAC,CAAA;AACjC,KAAC,CAAC,CAAA;GACH,CAAA;AAEDnB,EAAAA,aAAa,CAACe,EAAE,CAAC,OAAO,EAAEP,gBAAgB,CAAC,CAAA;AAC3CR,EAAAA,aAAa,CAACe,EAAE,CAAC,QAAQ,EAAEP,gBAAgB,CAAC,CAAA;AAC9C;;;;"}
1
+ {"version":3,"file":"watch.js","sources":["../../src/watch.ts"],"sourcesContent":["import chokidar from 'chokidar'\nimport path from 'path'\nimport { getConfig } from './config'\nimport { generator } from './generator'\n\nexport async function watch() {\n const configWatcher = chokidar.watch(\n path.resolve(process.cwd(), 'tsr.config.js'),\n )\n\n let watcher = new chokidar.FSWatcher({})\n\n const generatorWatcher = async () => {\n const config = await getConfig()\n\n watcher.close()\n\n console.log(`TSR: Watching routes (${config.routesDirectory})...`)\n watcher = chokidar.watch(config.routesDirectory)\n\n watcher.on('ready', async () => {\n const handle = async () => {\n try {\n await generator(config)\n } catch (err) {\n console.error(err)\n console.log()\n }\n }\n\n await handle()\n\n let timeout: ReturnType<typeof setTimeout>\n\n const deduped = (file: string) => {\n if (timeout) {\n clearTimeout(timeout)\n }\n\n timeout = setTimeout(handle, 10)\n }\n\n watcher.on('change', deduped)\n watcher.on('add', deduped)\n watcher.on('unlink', deduped)\n })\n }\n\n configWatcher.on('ready', generatorWatcher)\n configWatcher.on('change', generatorWatcher)\n}\n"],"names":["watch","configWatcher","chokidar","path","resolve","process","cwd","watcher","FSWatcher","generatorWatcher","config","getConfig","close","console","log","routesDirectory","on","handle","generator","err","error","timeout","deduped","file","clearTimeout","setTimeout"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAKO,eAAeA,KAAKA,GAAG;AAC5B,EAAA,MAAMC,aAAa,GAAGC,4BAAQ,CAACF,KAAK,CAClCG,wBAAI,CAACC,OAAO,CAACC,OAAO,CAACC,GAAG,EAAE,EAAE,eAAe,CAC7C,CAAC,CAAA;EAED,IAAIC,OAAO,GAAG,IAAIL,4BAAQ,CAACM,SAAS,CAAC,EAAE,CAAC,CAAA;AAExC,EAAA,MAAMC,gBAAgB,GAAG,YAAY;AACnC,IAAA,MAAMC,QAAM,GAAG,MAAMC,gBAAS,EAAE,CAAA;IAEhCJ,OAAO,CAACK,KAAK,EAAE,CAAA;IAEfC,OAAO,CAACC,GAAG,CAAE,CAAA,sBAAA,EAAwBJ,QAAM,CAACK,eAAgB,MAAK,CAAC,CAAA;IAClER,OAAO,GAAGL,4BAAQ,CAACF,KAAK,CAACU,QAAM,CAACK,eAAe,CAAC,CAAA;AAEhDR,IAAAA,OAAO,CAACS,EAAE,CAAC,OAAO,EAAE,YAAY;AAC9B,MAAA,MAAMC,MAAM,GAAG,YAAY;QACzB,IAAI;UACF,MAAMC,mBAAS,CAACR,QAAM,CAAC,CAAA;SACxB,CAAC,OAAOS,GAAG,EAAE;AACZN,UAAAA,OAAO,CAACO,KAAK,CAACD,GAAG,CAAC,CAAA;UAClBN,OAAO,CAACC,GAAG,EAAE,CAAA;AACf,SAAA;OACD,CAAA;MAED,MAAMG,MAAM,EAAE,CAAA;AAEd,MAAA,IAAII,OAAsC,CAAA;MAE1C,MAAMC,OAAO,GAAIC,IAAY,IAAK;AAChC,QAAA,IAAIF,OAAO,EAAE;UACXG,YAAY,CAACH,OAAO,CAAC,CAAA;AACvB,SAAA;AAEAA,QAAAA,OAAO,GAAGI,UAAU,CAACR,MAAM,EAAE,EAAE,CAAC,CAAA;OACjC,CAAA;AAEDV,MAAAA,OAAO,CAACS,EAAE,CAAC,QAAQ,EAAEM,OAAO,CAAC,CAAA;AAC7Bf,MAAAA,OAAO,CAACS,EAAE,CAAC,KAAK,EAAEM,OAAO,CAAC,CAAA;AAC1Bf,MAAAA,OAAO,CAACS,EAAE,CAAC,QAAQ,EAAEM,OAAO,CAAC,CAAA;AAC/B,KAAC,CAAC,CAAA;GACH,CAAA;AAEDrB,EAAAA,aAAa,CAACe,EAAE,CAAC,OAAO,EAAEP,gBAAgB,CAAC,CAAA;AAC3CR,EAAAA,aAAa,CAACe,EAAE,CAAC,QAAQ,EAAEP,gBAAgB,CAAC,CAAA;AAC9C;;;;"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tanstack/router-cli",
3
3
  "author": "Tanner Linsley",
4
- "version": "0.0.1-beta.151",
4
+ "version": "0.0.1-beta.153",
5
5
  "license": "MIT",
6
6
  "repository": "tanstack/router",
7
7
  "homepage": "https://tanstack.com/router",
@@ -46,6 +46,7 @@
46
46
  "@babel/preset-env": "^7.20.2",
47
47
  "@babel/template": "^7.18.10",
48
48
  "@babel/types": "^7.20.2",
49
+ "@tanstack/router-core": "0.0.1-beta.150",
49
50
  "@types/fs-extra": "^9.0.13",
50
51
  "@types/klaw": "^3.0.3",
51
52
  "@types/through2": "^2.0.38",
@@ -55,8 +56,10 @@
55
56
  "fs-extra": "^10.1.0",
56
57
  "klaw": "^4.0.1",
57
58
  "nodemon": "^2.0.20",
59
+ "prettier": "^3.0.2",
58
60
  "through2": "^4.0.2",
59
- "yargs": "^17.6.2"
61
+ "yargs": "^17.6.2",
62
+ "zod": "^3.19.1"
60
63
  },
61
64
  "scripts": {
62
65
  "build": "rollup --config rollup.config.js"
package/src/config.ts CHANGED
@@ -1,15 +1,20 @@
1
1
  import path from 'path'
2
2
  import fs from 'fs-extra'
3
+ import { z } from 'zod'
3
4
 
4
- export type Config = {
5
- rootDirectory: string
6
- sourceDirectory: string
7
- routesDirectory: string
8
- generatedRouteTree: string
9
- }
5
+ const configSchema = z.object({
6
+ routeFilePrefix: z.string().optional(),
7
+ routeFileIgnorePrefix: z.string().optional(),
8
+ routesDirectory: z.string(),
9
+ generatedRouteTree: z.string(),
10
+ })
11
+
12
+ export type Config = z.infer<typeof configSchema>
10
13
 
11
14
  const configFilePathJson = path.resolve(process.cwd(), 'tsr.config.json')
12
15
 
13
16
  export async function getConfig() {
14
- return fs.readJson(configFilePathJson)
17
+ const config = (await fs.readJson(configFilePathJson)) as unknown as Config
18
+
19
+ return configSchema.parse(config)
15
20
  }
package/src/generator.ts CHANGED
@@ -1,63 +1,102 @@
1
1
  import path from 'path'
2
2
  import fs from 'fs-extra'
3
- import crypto from 'crypto'
3
+ import * as prettier from 'prettier'
4
4
  import { Config } from './config'
5
+ import { cleanPath, trimPathLeft } from '@tanstack/router-core'
5
6
 
6
7
  let latestTask = 0
7
- export const rootRouteName = 'root'
8
+ export const rootPathId = '__root'
9
+ export const fileRouteRegex = /new\s+FileRoute\(([^)]*)\)/g
8
10
 
9
11
  export type RouteNode = {
10
- filename: string
12
+ filePath: string
11
13
  fullPath: string
12
14
  variableName: string
13
15
  routePath?: string
16
+ cleanedPath?: string
14
17
  path?: string
15
- id?: string
16
- hash?: string
17
- version?: number
18
- changed?: boolean
19
- new?: boolean
18
+ isNonPath?: boolean
19
+ isNonLayout?: boolean
20
20
  isRoot?: boolean
21
21
  children?: RouteNode[]
22
22
  parent?: RouteNode
23
+ layoutLimit?: string
23
24
  }
24
25
 
25
- let nodeCache: RouteNode[] = undefined!
26
-
27
- async function createRouteNode(
28
- config: Config,
29
- fileName: string,
30
- ): Promise<RouteNode> {
31
- const filename = path.basename(fileName)
32
- const fullPath = path.resolve(config.routesDirectory, filename)
33
- const fileNameNoExt = removeExt(filename)
34
- let routePath = fileNameNoExt.split('.').join('/')
35
-
36
- // Remove the index from the route path and
37
- // if the route path is empty, use `/'
38
- if (routePath.endsWith('/index')) {
39
- routePath = routePath.replace(/\/index$/, '/')
40
- } else if (routePath === 'index') {
41
- routePath = '/'
42
- }
26
+ async function getRouteNodes(config: Config) {
27
+ const { routeFilePrefix, routeFileIgnorePrefix } = config
28
+
29
+ let routeNodes: RouteNode[] = []
30
+
31
+ async function recurse(dir: string) {
32
+ const fullDir = path.resolve(config.routesDirectory, dir)
33
+ let dirList = await fs.readdir(fullDir)
34
+
35
+ dirList = dirList.filter((d) => {
36
+ if (d.startsWith('.') || d.startsWith(routeFileIgnorePrefix)) {
37
+ return false
38
+ }
39
+
40
+ if (routeFilePrefix) {
41
+ return d.startsWith(routeFilePrefix)
42
+ }
43
+
44
+ return true
45
+ })
46
+
47
+ await Promise.all(
48
+ dirList.map(async (fileName) => {
49
+ const fullPath = path.join(fullDir, fileName)
50
+ const relativePath = path.join(dir, fileName)
51
+ const stat = await fs.stat(fullPath)
52
+
53
+ if (stat.isDirectory()) {
54
+ await recurse(relativePath)
55
+ } else {
56
+ const filePath = path.join(dir, fileName)
57
+ const filePathNoExt = removeExt(filePath)
58
+ let routePath = cleanPath(`/${filePathNoExt.split('.').join('/')}`)
59
+ const variableName = fileToVariable(routePath)
60
+
61
+ // Remove the index from the route path and
62
+ // if the route path is empty, use `/'
63
+ if (routePath.endsWith('/index')) {
64
+ routePath = routePath.replace(/\/index$/, '/')
65
+ } else if (routePath === 'index') {
66
+ routePath = '/'
67
+ }
68
+
69
+ // console.log(filePath)
43
70
 
44
- return {
45
- filename,
46
- fullPath,
47
- routePath,
48
- variableName: fileToVariable(removeExt(filename)),
71
+ routeNodes.push({
72
+ filePath,
73
+ fullPath,
74
+ routePath,
75
+ variableName,
76
+ })
77
+ }
78
+ }),
79
+ )
80
+
81
+ return routeNodes
49
82
  }
83
+
84
+ await recurse('./')
85
+
86
+ return routeNodes
50
87
  }
51
88
 
89
+ let first = false
90
+ let skipMessage = false
91
+
52
92
  export async function generator(config: Config) {
53
93
  console.log()
54
94
 
55
- let first = false
56
-
57
- if (!nodeCache) {
58
- first = true
95
+ if (!first) {
59
96
  console.log('🔄 Generating routes...')
60
- nodeCache = []
97
+ first = true
98
+ } else if (skipMessage) {
99
+ skipMessage = false
61
100
  } else {
62
101
  console.log('♻️ Regenerating routes...')
63
102
  }
@@ -67,7 +106,7 @@ export async function generator(config: Config) {
67
106
 
68
107
  const checkLatest = () => {
69
108
  if (latestTask !== taskId) {
70
- console.log(`- Skipping since file changes were made while generating.`)
109
+ skipMessage = true
71
110
  return false
72
111
  }
73
112
 
@@ -75,262 +114,198 @@ export async function generator(config: Config) {
75
114
  }
76
115
 
77
116
  const start = Date.now()
78
- let routeConfigImports: string[] = []
79
117
 
80
- let nodesChanged = false
81
- const fileQueue: [string, string][] = []
82
- const queueWriteFile = (filename: string, content: string) => {
83
- fileQueue.push([filename, content])
84
- }
85
-
86
- let dirList
87
-
88
- try {
89
- dirList = await fs.readdir(config.routesDirectory)
90
- } catch (err) {
91
- console.log()
92
- console.error(
93
- 'TSR: Error reading the config.routesDirectory. Does it exist?',
94
- )
95
- console.log()
96
- throw err
97
- }
98
-
99
- let routeNodes = await Promise.all(
100
- dirList.map(async (filename): Promise<RouteNode> => {
101
- return createRouteNode(config, filename)
102
- }),
103
- )
118
+ let routeNodes = await getRouteNodes(config)
104
119
 
105
120
  routeNodes = multiSortBy(routeNodes, [
106
121
  (d) => (d.routePath === '/' ? -1 : 1),
107
122
  (d) => d.routePath?.split('/').length,
108
123
  (d) => (d.routePath?.endsWith('/') ? -1 : 1),
109
124
  (d) => d.routePath,
110
- ]).filter((d) => d.routePath !== '_root')
125
+ ]).filter((d) => d.routePath !== `/${rootPathId}`)
111
126
 
112
127
  const routeTree: RouteNode[] = []
113
128
 
114
129
  // Loop over the flat list of routeNodes and
115
130
  // build up a tree based on the routeNodes' routePath
116
131
  routeNodes.forEach((node) => {
117
- const findParent = (
118
- existing: RouteNode[],
119
- node: RouteNode,
120
- parentNode?: RouteNode,
121
- ) => {
122
- const parent = existing.find((d) => {
123
- return node.routePath?.startsWith(d.path!)
124
- })
132
+ routeNodes.forEach((existingNode) => {
133
+ if (
134
+ node.routePath?.startsWith(`${existingNode?.routePath ?? ''}/`)
135
+ // node.routePath.length > existingNode.routePath!.length
136
+ ) {
137
+ node.parent = existingNode
138
+ }
139
+ })
125
140
 
126
- if (parent) {
127
- if (!parent.children) {
128
- parent.children = []
129
- }
141
+ node.path = node.parent
142
+ ? node.routePath?.replace(node.parent.routePath!, '') || '/'
143
+ : node.routePath
130
144
 
131
- findParent(parent.children, node, parent)
132
- } else {
133
- node.path = node.routePath?.replace(parentNode?.routePath ?? '', '')
134
- node.parent = parentNode
135
- existing.push(node)
136
- }
137
- }
145
+ const trimmedPath = trimPathLeft(node.path ?? '')
146
+
147
+ const split = trimmedPath?.split('/') ?? []
148
+ let first = split[0] ?? trimmedPath ?? ''
138
149
 
139
- findParent(routeTree, node)
150
+ node.isNonPath = first.startsWith('_')
151
+ node.isNonLayout = first.endsWith('_')
152
+
153
+ node.cleanedPath = removeUnderscores(node.path) ?? ''
154
+
155
+ if (node.parent) {
156
+ node.parent.children = node.parent.children ?? []
157
+ node.parent.children.push(node)
158
+ } else {
159
+ routeTree.push(node)
160
+ }
140
161
  })
141
162
 
142
163
  async function buildRouteConfig(
143
164
  nodes: RouteNode[],
144
165
  depth = 1,
145
166
  ): Promise<string> {
146
- const children = nodes.map(async (n) => {
147
- let node = nodeCache.find((d) => d.fullPath === n.fullPath)!
148
-
149
- if (node) {
150
- node.new = false
151
- } else {
152
- node = n
153
- nodeCache.push(node)
154
- if (!first) {
155
- node.new = true
156
- }
157
- }
158
-
159
- node.version = latestTask
160
-
167
+ const children = nodes.map(async (node) => {
161
168
  const routeCode = await fs.readFile(node.fullPath, 'utf-8')
162
- const hashSum = crypto.createHash('sha256')
163
- hashSum.update(routeCode)
164
- const hash = hashSum.digest('hex')
165
169
 
166
- node.changed = node.hash !== hash
167
- if (node.changed) {
168
- nodesChanged = true
169
- node.hash = hash
170
+ // Ensure the boilerplate for the route exists
171
+ if (node.isRoot) {
172
+ return
173
+ }
170
174
 
171
- try {
172
- // Ensure the boilerplate for the route exists
173
- const code = await ensureBoilerplate(node, routeCode)
175
+ // Ensure that new FileRoute(anything?) is replace with FileRoute(${node.routePath})
176
+ const replaced = routeCode.replace(
177
+ fileRouteRegex,
178
+ `new FileRoute('${node.routePath}')`,
179
+ )
174
180
 
175
- if (code) {
176
- await fs.writeFile(node.fullPath, code)
177
- }
178
- } catch (err) {
179
- node.hash = ''
180
- throw err
181
- }
181
+ if (replaced !== routeCode) {
182
+ await fs.writeFile(node.fullPath, replaced)
182
183
  }
183
184
 
184
185
  const route = `${node.variableName}Route`
185
186
 
186
- routeConfigImports.push(
187
- `import { route as ${route} } from './${removeExt(
188
- path.relative(
189
- path.dirname(config.generatedRouteTree),
190
- path.resolve(config.routesDirectory, node.filename),
191
- ),
192
- )}'`,
193
- )
194
-
195
187
  if (node.children?.length) {
196
188
  const childConfigs = await buildRouteConfig(node.children, depth + 1)
197
- return `${route}.addChildren([\n${spaces(
198
- depth * 4,
199
- )}${childConfigs}\n${spaces(depth * 2)}])`
189
+ return `${route}.addChildren([${spaces(depth * 4)}${childConfigs}])`
200
190
  }
201
191
 
202
192
  return route
203
193
  })
204
194
 
205
- return (await Promise.all(children))
206
- .filter(Boolean)
207
- .join(`,\n${spaces(depth * 2)}`)
195
+ return (await Promise.all(children)).filter(Boolean).join(`,`)
208
196
  }
209
197
 
210
198
  const routeConfigChildrenText = await buildRouteConfig(routeTree)
211
199
 
212
- routeConfigImports = multiSortBy(routeConfigImports, [
213
- (d) => (d.includes(rootRouteName) ? -1 : 1),
214
- (d) => d.split('/').length,
215
- (d) => (d.endsWith("index'") ? -1 : 1),
216
- (d) => d,
217
- ])
218
-
219
- const routeConfig = `export const routeTree = rootRoute.addChildren([\n ${routeConfigChildrenText}\n])`
220
-
221
- const routeConfigFileContent = [
200
+ const routeImports = [
222
201
  `import { route as rootRoute } from './${path.relative(
223
202
  path.dirname(config.generatedRouteTree),
224
- path.resolve(config.routesDirectory, '_root'),
203
+ path.resolve(config.routesDirectory, rootPathId),
225
204
  )}'`,
226
- routeConfigImports.join('\n'),
227
- `declare module '@tanstack/react-router' {
205
+ ...multiSortBy(routeNodes, [
206
+ (d) => (d.routePath?.includes(`/${rootPathId}`) ? -1 : 1),
207
+ (d) => d.routePath?.split('/').length,
208
+ (d) => (d.routePath?.endsWith("index'") ? -1 : 1),
209
+ (d) => d,
210
+ ]).map((node) => {
211
+ return `import { route as ${node.variableName}Route } from './${removeExt(
212
+ path.relative(
213
+ path.dirname(config.generatedRouteTree),
214
+ path.resolve(config.routesDirectory, node.filePath),
215
+ ),
216
+ )}'`
217
+ }),
218
+ ].join('\n')
219
+
220
+ const routeTypes = `declare module '@tanstack/react-router' {
228
221
  interface FileRoutesByPath {
229
222
  ${routeNodes
230
223
  .map((routeNode) => {
231
224
  return `'${routeNode.routePath}': {
232
- parentRoute: typeof ${routeNode.parent?.variableName ?? 'root'}Route
233
- }`
225
+ parentRoute: typeof ${routeNode.parent?.variableName ?? 'root'}Route
226
+ }`
234
227
  })
235
- .join('\n ')}
228
+ .join('\n')}
236
229
  }
237
- }`,
238
- routeNodes
239
- .map((routeNode) => {
240
- return `Object.assign(${
241
- routeNode.variableName ?? 'root'
242
- }Route.options, {
243
- path: '${routeNode.path}',
244
- getParentRoute: () => ${`${routeNode.parent?.variableName ?? 'root'}Route,
245
- })`}`
246
- })
247
- .join('\n'),
248
- routeConfig,
249
- ].join('\n\n')
230
+ }`
231
+
232
+ const routeOptions = routeNodes
233
+ .map((routeNode) => {
234
+ return `Object.assign(${routeNode.variableName ?? 'root'}Route.options, {
235
+ ${[
236
+ routeNode.isNonPath
237
+ ? `id: '${routeNode.cleanedPath}'`
238
+ : `path: '${routeNode.cleanedPath}'`,
239
+ `getParentRoute: () => ${
240
+ routeNode.parent?.variableName ?? 'root'
241
+ }Route`,
242
+ routeNode.layoutLimit
243
+ ? `layoutLimit: '${routeNode.layoutLimit}'`
244
+ : '',
245
+ // `\n// ${JSON.stringify(
246
+ // {
247
+ // ...routeNode,
248
+ // parent: undefined,
249
+ // children: undefined,
250
+ // fullPath: undefined,
251
+ // variableName: undefined,
252
+ // },
253
+ // null,
254
+ // 2,
255
+ // )
256
+ // .split('\n')
257
+ // .join('\n// ')}`,
258
+ ]
259
+ .filter(Boolean)
260
+ .join(',')}
261
+ })`
262
+ })
263
+ .join('\n\n')
250
264
 
251
- if (nodesChanged) {
252
- queueWriteFile(
253
- path.resolve(config.generatedRouteTree),
254
- routeConfigFileContent,
255
- )
256
- }
265
+ const routeConfig = `export const routeTree = rootRoute.addChildren([${routeConfigChildrenText}])`
257
266
 
258
- if (!checkLatest()) return
267
+ const routeConfigFileContent = await prettier.format(
268
+ [routeImports, routeTypes, routeOptions, routeConfig].join('\n\n'),
269
+ {
270
+ semi: false,
271
+ parser: 'typescript',
272
+ },
273
+ )
259
274
 
260
- await Promise.all(
261
- fileQueue.map(async ([filename, content]) => {
262
- await fs.ensureDir(path.dirname(filename))
263
- const exists = await fs.pathExists(filename)
264
- let current = ''
265
- if (exists) {
266
- current = await fs.readFile(filename, 'utf-8')
275
+ const routeTreeContent = await fs
276
+ .readFile(path.resolve(config.generatedRouteTree), 'utf-8')
277
+ .catch((err: any) => {
278
+ if (err.code === 'ENOENT') {
279
+ return undefined
267
280
  }
268
- if (current !== content) {
269
- await fs.writeFile(filename, content)
270
- }
271
- }),
272
- )
281
+ throw err
282
+ })
273
283
 
274
284
  if (!checkLatest()) return
275
285
 
276
- const removedNodes: RouteNode[] = []
277
-
278
- nodeCache = nodeCache.filter((d) => {
279
- if (d.version !== latestTask) {
280
- removedNodes.push(d)
281
- return false
282
- }
283
- return true
284
- })
285
-
286
- const newNodes = nodeCache.filter((d) => d.new)
287
- const updatedNodes = nodeCache.filter((d) => !d.new && d.changed)
286
+ if (routeTreeContent !== routeConfigFileContent) {
287
+ await fs.ensureDir(path.dirname(path.resolve(config.generatedRouteTree)))
288
+ if (!checkLatest()) return
289
+ await fs.writeFile(
290
+ path.resolve(config.generatedRouteTree),
291
+ routeConfigFileContent,
292
+ )
293
+ }
288
294
 
289
295
  console.log(
290
- `🌲 Processed ${nodeCache.length} routes in ${Date.now() - start}ms`,
296
+ `🌲 Processed ${routeNodes.length} routes in ${Date.now() - start}ms`,
291
297
  )
292
-
293
- if (newNodes.length || updatedNodes.length || removedNodes.length) {
294
- if (newNodes.length) {
295
- console.log(`🥳 Added ${newNodes.length} new routes`)
296
- }
297
-
298
- if (updatedNodes.length) {
299
- console.log(`✅ Updated ${updatedNodes.length} routes`)
300
- }
301
-
302
- if (removedNodes.length) {
303
- console.log(`🗑 Removed ${removedNodes.length} unused routes`)
304
- }
305
- } else {
306
- console.log(`🎉 No changes were found. Carry on!`)
307
- }
308
298
  }
309
299
 
310
- async function ensureBoilerplate(node: RouteNode, code: string) {
311
- if (node.isRoot) {
312
- return
313
- }
314
-
315
- // Ensure that new FileRoute(anything?) is replace with FileRoute(${node.routePath})
316
- const replaced = code.replace(
317
- /new\s+FileRoute\(([^)]*)\)/g,
318
- `new FileRoute('${node.routePath}')`,
300
+ function fileToVariable(d: string): string {
301
+ return (
302
+ removeUnderscores(d)
303
+ ?.replace(/\$/g, '')
304
+ ?.split(/[/-]/g)
305
+ .map((d, i) => (i > 0 ? capitalize(d) : d))
306
+ .join('')
307
+ .replace(/([^a-zA-Z0-9]|[\.])/gm, '') ?? ''
319
308
  )
320
-
321
- if (replaced !== code) {
322
- return replaced
323
- }
324
-
325
- return
326
- }
327
-
328
- function fileToVariable(d: string) {
329
- return d
330
- .split('/')
331
- .map((d, i) => (i > 0 ? capitalize(d) : d))
332
- .join('')
333
- .replace(/([^a-zA-Z0-9]|[\.])/gm, '')
334
309
  }
335
310
 
336
311
  export function removeExt(d: string) {
@@ -377,3 +352,7 @@ function capitalize(s: string) {
377
352
  if (typeof s !== 'string') return ''
378
353
  return s.charAt(0).toUpperCase() + s.slice(1)
379
354
  }
355
+
356
+ function removeUnderscores(s?: string) {
357
+ return s?.replace(/(^_|_$)/, '').replace(/(\/_|_\/)/, '/')
358
+ }
package/src/watch.ts CHANGED
@@ -8,7 +8,7 @@ export async function watch() {
8
8
  path.resolve(process.cwd(), 'tsr.config.js'),
9
9
  )
10
10
 
11
- let watcher = new chokidar.FSWatcher()
11
+ let watcher = new chokidar.FSWatcher({})
12
12
 
13
13
  const generatorWatcher = async () => {
14
14
  const config = await getConfig()
@@ -19,13 +19,6 @@ export async function watch() {
19
19
  watcher = chokidar.watch(config.routesDirectory)
20
20
 
21
21
  watcher.on('ready', async () => {
22
- try {
23
- await generator(config)
24
- } catch (err) {
25
- console.error(err)
26
- console.log()
27
- }
28
-
29
22
  const handle = async () => {
30
23
  try {
31
24
  await generator(config)
@@ -35,11 +28,21 @@ export async function watch() {
35
28
  }
36
29
  }
37
30
 
38
- watcher.on('change', handle)
39
- watcher.on('add', handle)
40
- watcher.on('addDir', handle)
41
- watcher.on('unlink', handle)
42
- watcher.on('unlinkDir', handle)
31
+ await handle()
32
+
33
+ let timeout: ReturnType<typeof setTimeout>
34
+
35
+ const deduped = (file: string) => {
36
+ if (timeout) {
37
+ clearTimeout(timeout)
38
+ }
39
+
40
+ timeout = setTimeout(handle, 10)
41
+ }
42
+
43
+ watcher.on('change', deduped)
44
+ watcher.on('add', deduped)
45
+ watcher.on('unlink', deduped)
43
46
  })
44
47
  }
45
48