@tanstack/router-cli 0.0.1-beta.146

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021 Tanner Linsley
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/bin/tsr.js ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+
3
+ require('../build/cjs/index.js')
@@ -0,0 +1,29 @@
1
+ /**
2
+ * @tanstack/router-cli/src/index.ts
3
+ *
4
+ * Copyright (c) TanStack
5
+ *
6
+ * This source code is licensed under the MIT license found in the
7
+ * LICENSE.md file in the root directory of this source tree.
8
+ *
9
+ * @license MIT
10
+ */
11
+ 'use strict';
12
+
13
+ Object.defineProperty(exports, '__esModule', { value: true });
14
+
15
+ var path = require('path');
16
+ var fs = require('fs-extra');
17
+
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');
24
+ async function getConfig() {
25
+ return fs__default["default"].readJson(configFilePathJson);
26
+ }
27
+
28
+ exports.getConfig = getConfig;
29
+ //# sourceMappingURL=config.js.map
@@ -0,0 +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;;;;"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * @tanstack/router-cli/src/index.ts
3
+ *
4
+ * Copyright (c) TanStack
5
+ *
6
+ * This source code is licensed under the MIT license found in the
7
+ * LICENSE.md file in the root directory of this source tree.
8
+ *
9
+ * @license MIT
10
+ */
11
+ 'use strict';
12
+
13
+ Object.defineProperty(exports, '__esModule', { value: true });
14
+
15
+ var generator = require('./generator.js');
16
+
17
+ async function generate(config) {
18
+ try {
19
+ await generator.generator(config);
20
+ process.exit(0);
21
+ } catch (err) {
22
+ console.error(err);
23
+ process.exit(1);
24
+ }
25
+ }
26
+
27
+ exports.generate = generate;
28
+ //# sourceMappingURL=generate.js.map
@@ -0,0 +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,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;;;;"}
@@ -0,0 +1,260 @@
1
+ /**
2
+ * @tanstack/router-cli/src/index.ts
3
+ *
4
+ * Copyright (c) TanStack
5
+ *
6
+ * This source code is licensed under the MIT license found in the
7
+ * LICENSE.md file in the root directory of this source tree.
8
+ *
9
+ * @license MIT
10
+ */
11
+ 'use strict';
12
+
13
+ Object.defineProperty(exports, '__esModule', { value: true });
14
+
15
+ var path = require('path');
16
+ var fs = require('fs-extra');
17
+ var crypto = require('crypto');
18
+
19
+ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
20
+
21
+ var path__default = /*#__PURE__*/_interopDefaultLegacy(path);
22
+ var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
23
+ var crypto__default = /*#__PURE__*/_interopDefaultLegacy(crypto);
24
+
25
+ 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('/');
33
+
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 = '/';
40
+ }
41
+ return {
42
+ filename,
43
+ fullPath,
44
+ routePath,
45
+ variableName: fileToVariable(removeExt(filename))
46
+ };
47
+ }
48
+ async function generator(config) {
49
+ console.log();
50
+ let first = false;
51
+ if (!nodeCache) {
52
+ first = true;
53
+ console.log('🔄 Generating routes...');
54
+ nodeCache = [];
55
+ } else {
56
+ console.log('♻️ Regenerating routes...');
57
+ }
58
+ const taskId = latestTask + 1;
59
+ latestTask = taskId;
60
+ const checkLatest = () => {
61
+ if (latestTask !== taskId) {
62
+ console.log(`- Skipping since file changes were made while generating.`);
63
+ return false;
64
+ }
65
+ return true;
66
+ };
67
+ 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');
87
+ const routeTree = [];
88
+
89
+ // Loop over the flat list of routeNodes and
90
+ // build up a tree based on the routeNodes' routePath
91
+ 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);
105
+ }
106
+ };
107
+ findParent(routeTree, node);
108
+ });
109
+ 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;
122
+ 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
+ }
140
+ }
141
+ 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
+ if (node.children?.length) {
144
+ const childConfigs = await buildRouteConfig(node.children, depth + 1);
145
+ return `${route}.addChildren([\n${spaces(depth * 4)}${childConfigs}\n${spaces(depth * 2)}])`;
146
+ }
147
+ return route;
148
+ });
149
+ return (await Promise.all(children)).filter(Boolean).join(`,\n${spaces(depth * 2)}`);
150
+ }
151
+ 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' {
155
+ interface FileRoutesByPath {
156
+ ${routeNodes.map(routeNode => {
157
+ return `'${routeNode.routePath}': {
158
+ parentRoute: typeof ${routeNode.parent?.variableName ?? 'root'}Route
159
+ }`;
160
+ }).join('\n ')}
161
+ }
162
+ }`, routeNodes.map(routeNode => {
163
+ 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;
191
+ });
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`);
204
+ }
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;
218
+ }
219
+ return;
220
+ }
221
+ function fileToVariable(d) {
222
+ return d.split('/').map((d, i) => i > 0 ? capitalize(d) : d).join('').replace(/([^a-zA-Z0-9]|[\.])/gm, '');
223
+ }
224
+ function removeExt(d) {
225
+ return d.substring(0, d.lastIndexOf('.')) || d;
226
+ }
227
+ function spaces(d) {
228
+ return Array.from({
229
+ length: d
230
+ }).map(() => ' ').join('');
231
+ }
232
+ function multiSortBy(arr, accessors = [d => d]) {
233
+ return arr.map((d, i) => [d, i]).sort(([a, ai], [b, bi]) => {
234
+ for (const accessor of accessors) {
235
+ const ao = accessor(a);
236
+ const bo = accessor(b);
237
+ if (typeof ao === 'undefined') {
238
+ if (typeof bo === 'undefined') {
239
+ continue;
240
+ }
241
+ return 1;
242
+ }
243
+ if (ao === bo) {
244
+ continue;
245
+ }
246
+ return ao > bo ? 1 : -1;
247
+ }
248
+ return ai - bi;
249
+ }).map(([d]) => d);
250
+ }
251
+ function capitalize(s) {
252
+ if (typeof s !== 'string') return '';
253
+ return s.charAt(0).toUpperCase() + s.slice(1);
254
+ }
255
+
256
+ exports.generator = generator;
257
+ exports.multiSortBy = multiSortBy;
258
+ exports.removeExt = removeExt;
259
+ exports.rootRouteName = rootRouteName;
260
+ //# sourceMappingURL=generator.js.map
@@ -0,0 +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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"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * @tanstack/router-cli/src/index.ts
3
+ *
4
+ * Copyright (c) TanStack
5
+ *
6
+ * This source code is licensed under the MIT license found in the
7
+ * LICENSE.md file in the root directory of this source tree.
8
+ *
9
+ * @license MIT
10
+ */
11
+ 'use strict';
12
+
13
+ Object.defineProperty(exports, '__esModule', { value: true });
14
+
15
+ var yargs = require('yargs');
16
+ var config = require('./config.js');
17
+ var generate = require('./generate.js');
18
+ var watch = require('./watch.js');
19
+
20
+ function _interopNamespace(e) {
21
+ if (e && e.__esModule) return e;
22
+ var n = Object.create(null);
23
+ if (e) {
24
+ Object.keys(e).forEach(function (k) {
25
+ if (k !== 'default') {
26
+ var d = Object.getOwnPropertyDescriptor(e, k);
27
+ Object.defineProperty(n, k, d.get ? d : {
28
+ enumerable: true,
29
+ get: function () { return e[k]; }
30
+ });
31
+ }
32
+ });
33
+ }
34
+ n["default"] = e;
35
+ return Object.freeze(n);
36
+ }
37
+
38
+ var yargs__namespace = /*#__PURE__*/_interopNamespace(yargs);
39
+
40
+ main();
41
+ function main() {
42
+ yargs__namespace.scriptName('tsr').usage('$0 <cmd> [args]').command('generate', 'Generate the routes for a project', async argv => {
43
+ const config$1 = await config.getConfig();
44
+ await generate.generate(config$1);
45
+ }).command('watch', 'Continuously watch and generate the routes for a project', async argv => {
46
+ watch.watch();
47
+ }).help().argv;
48
+ }
49
+
50
+ exports.main = main;
51
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../../src/index.ts"],"sourcesContent":["import * as yargs from 'yargs'\nimport { getConfig } from './config'\nimport { generate } from './generate'\nimport { watch } from './watch'\n\nmain()\n\nexport function main() {\n yargs\n .scriptName('tsr')\n .usage('$0 <cmd> [args]')\n .command('generate', 'Generate the routes for a project', async (argv) => {\n const config = await getConfig()\n await generate(config)\n })\n .command(\n 'watch',\n 'Continuously watch and generate the routes for a project',\n async (argv) => {\n watch()\n },\n )\n .help().argv\n}\n"],"names":["main","yargs","scriptName","usage","command","argv","config","getConfig","generate","watch","help"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAKAA,IAAI,EAAE,CAAA;AAEC,SAASA,IAAIA,GAAG;AACrBC,EAAAA,gBAAK,CACFC,UAAU,CAAC,KAAK,CAAC,CACjBC,KAAK,CAAC,iBAAiB,CAAC,CACxBC,OAAO,CAAC,UAAU,EAAE,mCAAmC,EAAE,MAAOC,IAAI,IAAK;AACxE,IAAA,MAAMC,QAAM,GAAG,MAAMC,gBAAS,EAAE,CAAA;IAChC,MAAMC,iBAAQ,CAACF,QAAM,CAAC,CAAA;GACvB,CAAC,CACDF,OAAO,CACN,OAAO,EACP,0DAA0D,EAC1D,MAAOC,IAAI,IAAK;AACdI,IAAAA,WAAK,EAAE,CAAA;AACT,GACF,CAAC,CACAC,IAAI,EAAE,CAACL,IAAI,CAAA;AAChB;;;;"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * @tanstack/router-cli/src/index.ts
3
+ *
4
+ * Copyright (c) TanStack
5
+ *
6
+ * This source code is licensed under the MIT license found in the
7
+ * LICENSE.md file in the root directory of this source tree.
8
+ *
9
+ * @license MIT
10
+ */
11
+ 'use strict';
12
+
13
+ Object.defineProperty(exports, '__esModule', { value: true });
14
+
15
+ var chokidar = require('chokidar');
16
+ var path = require('path');
17
+ var config = require('./config.js');
18
+ var generator = require('./generator.js');
19
+
20
+ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
21
+
22
+ var chokidar__default = /*#__PURE__*/_interopDefaultLegacy(chokidar);
23
+ var path__default = /*#__PURE__*/_interopDefaultLegacy(path);
24
+
25
+ async function watch() {
26
+ const configWatcher = chokidar__default["default"].watch(path__default["default"].resolve(process.cwd(), 'tsr.config.js'));
27
+ let watcher = new chokidar__default["default"].FSWatcher();
28
+ const generatorWatcher = async () => {
29
+ const config$1 = await config.getConfig();
30
+ watcher.close();
31
+ console.log(`TSR: Watching routes (${config$1.routesDirectory})...`);
32
+ watcher = chokidar__default["default"].watch(config$1.routesDirectory);
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
+ const handle = async () => {
41
+ try {
42
+ await generator.generator(config$1);
43
+ } catch (err) {
44
+ console.error(err);
45
+ console.log();
46
+ }
47
+ };
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);
53
+ });
54
+ };
55
+ configWatcher.on('ready', generatorWatcher);
56
+ configWatcher.on('change', generatorWatcher);
57
+ }
58
+
59
+ exports.watch = watch;
60
+ //# sourceMappingURL=watch.js.map
@@ -0,0 +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;;;;"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @tanstack/router-cli/src/index.ts
3
+ *
4
+ * Copyright (c) TanStack
5
+ *
6
+ * This source code is licensed under the MIT license found in the
7
+ * LICENSE.md file in the root directory of this source tree.
8
+ *
9
+ * @license MIT
10
+ */
11
+ declare function main(): void;
12
+
13
+ export { main };
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "@tanstack/router-cli",
3
+ "author": "Tanner Linsley",
4
+ "version": "0.0.1-beta.146",
5
+ "license": "MIT",
6
+ "repository": "tanstack/router",
7
+ "homepage": "https://tanstack.com/router",
8
+ "description": "",
9
+ "publishConfig": {
10
+ "registry": "https://registry.npmjs.org/"
11
+ },
12
+ "keywords": [
13
+ "react",
14
+ "location",
15
+ "router",
16
+ "routing",
17
+ "async",
18
+ "async router",
19
+ "typescript"
20
+ ],
21
+ "funding": {
22
+ "type": "github",
23
+ "url": "https://github.com/sponsors/tannerlinsley"
24
+ },
25
+ "type": "commonjs",
26
+ "bin": {
27
+ "tsr": "bin/tsr.js"
28
+ },
29
+ "engines": {
30
+ "node": ">=12"
31
+ },
32
+ "files": [
33
+ "build/**",
34
+ "src/**",
35
+ "bin/**"
36
+ ],
37
+ "sideEffects": false,
38
+ "module": "bin/tsr.js",
39
+ "main": "bin/tsr.js",
40
+ "browser": "bin/tsr.js",
41
+ "types": "build/types/index.d.ts",
42
+ "dependencies": {
43
+ "@babel/core": "^7.20.2",
44
+ "@babel/plugin-syntax-jsx": "^7.18.6",
45
+ "@babel/plugin-syntax-typescript": "^7.20.0",
46
+ "@babel/preset-env": "^7.20.2",
47
+ "@babel/template": "^7.18.10",
48
+ "@babel/types": "^7.20.2",
49
+ "@types/fs-extra": "^9.0.13",
50
+ "@types/klaw": "^3.0.3",
51
+ "@types/through2": "^2.0.38",
52
+ "@types/yargs": "^17.0.15",
53
+ "chokidar": "^3.5.3",
54
+ "dedent": "^0.7.0",
55
+ "fs-extra": "^10.1.0",
56
+ "klaw": "^4.0.1",
57
+ "nodemon": "^2.0.20",
58
+ "through2": "^4.0.2",
59
+ "yargs": "^17.6.2"
60
+ },
61
+ "scripts": {
62
+ "build": "rollup --config rollup.config.js"
63
+ }
64
+ }
package/src/config.ts ADDED
@@ -0,0 +1,15 @@
1
+ import path from 'path'
2
+ import fs from 'fs-extra'
3
+
4
+ export type Config = {
5
+ rootDirectory: string
6
+ sourceDirectory: string
7
+ routesDirectory: string
8
+ generatedRouteTree: string
9
+ }
10
+
11
+ const configFilePathJson = path.resolve(process.cwd(), 'tsr.config.json')
12
+
13
+ export async function getConfig() {
14
+ return fs.readJson(configFilePathJson)
15
+ }
@@ -0,0 +1,12 @@
1
+ import { generator } from './generator'
2
+ import { Config } from './config'
3
+
4
+ export async function generate(config: Config) {
5
+ try {
6
+ await generator(config)
7
+ process.exit(0)
8
+ } catch (err) {
9
+ console.error(err)
10
+ process.exit(1)
11
+ }
12
+ }
@@ -0,0 +1,379 @@
1
+ import path from 'path'
2
+ import fs from 'fs-extra'
3
+ import crypto from 'crypto'
4
+ import { Config } from './config'
5
+
6
+ let latestTask = 0
7
+ export const rootRouteName = 'root'
8
+
9
+ export type RouteNode = {
10
+ filename: string
11
+ fullPath: string
12
+ variableName: string
13
+ routePath?: string
14
+ path?: string
15
+ id?: string
16
+ hash?: string
17
+ version?: number
18
+ changed?: boolean
19
+ new?: boolean
20
+ isRoot?: boolean
21
+ children?: RouteNode[]
22
+ parent?: RouteNode
23
+ }
24
+
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
+ }
43
+
44
+ return {
45
+ filename,
46
+ fullPath,
47
+ routePath,
48
+ variableName: fileToVariable(removeExt(filename)),
49
+ }
50
+ }
51
+
52
+ export async function generator(config: Config) {
53
+ console.log()
54
+
55
+ let first = false
56
+
57
+ if (!nodeCache) {
58
+ first = true
59
+ console.log('🔄 Generating routes...')
60
+ nodeCache = []
61
+ } else {
62
+ console.log('♻️ Regenerating routes...')
63
+ }
64
+
65
+ const taskId = latestTask + 1
66
+ latestTask = taskId
67
+
68
+ const checkLatest = () => {
69
+ if (latestTask !== taskId) {
70
+ console.log(`- Skipping since file changes were made while generating.`)
71
+ return false
72
+ }
73
+
74
+ return true
75
+ }
76
+
77
+ const start = Date.now()
78
+ let routeConfigImports: string[] = []
79
+
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
+ )
104
+
105
+ routeNodes = multiSortBy(routeNodes, [
106
+ (d) => (d.routePath === '/' ? -1 : 1),
107
+ (d) => d.routePath?.split('/').length,
108
+ (d) => (d.routePath?.endsWith('/') ? -1 : 1),
109
+ (d) => d.routePath,
110
+ ]).filter((d) => d.routePath !== '_root')
111
+
112
+ const routeTree: RouteNode[] = []
113
+
114
+ // Loop over the flat list of routeNodes and
115
+ // build up a tree based on the routeNodes' routePath
116
+ 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
+ })
125
+
126
+ if (parent) {
127
+ if (!parent.children) {
128
+ parent.children = []
129
+ }
130
+
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
+ }
138
+
139
+ findParent(routeTree, node)
140
+ })
141
+
142
+ async function buildRouteConfig(
143
+ nodes: RouteNode[],
144
+ depth = 1,
145
+ ): 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
+
161
+ 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
+
166
+ node.changed = node.hash !== hash
167
+ if (node.changed) {
168
+ nodesChanged = true
169
+ node.hash = hash
170
+
171
+ try {
172
+ // Ensure the boilerplate for the route exists
173
+ const code = await ensureBoilerplate(node, routeCode)
174
+
175
+ if (code) {
176
+ await fs.writeFile(node.fullPath, code)
177
+ }
178
+ } catch (err) {
179
+ node.hash = ''
180
+ throw err
181
+ }
182
+ }
183
+
184
+ const route = `${node.variableName}Route`
185
+
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
+ if (node.children?.length) {
196
+ const childConfigs = await buildRouteConfig(node.children, depth + 1)
197
+ return `${route}.addChildren([\n${spaces(
198
+ depth * 4,
199
+ )}${childConfigs}\n${spaces(depth * 2)}])`
200
+ }
201
+
202
+ return route
203
+ })
204
+
205
+ return (await Promise.all(children))
206
+ .filter(Boolean)
207
+ .join(`,\n${spaces(depth * 2)}`)
208
+ }
209
+
210
+ const routeConfigChildrenText = await buildRouteConfig(routeTree)
211
+
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 = [
222
+ `import { route as rootRoute } from './${path.relative(
223
+ path.dirname(config.generatedRouteTree),
224
+ path.resolve(config.routesDirectory, '_root'),
225
+ )}'`,
226
+ routeConfigImports.join('\n'),
227
+ `declare module '@tanstack/react-router' {
228
+ interface FileRoutesByPath {
229
+ ${routeNodes
230
+ .map((routeNode) => {
231
+ return `'${routeNode.routePath}': {
232
+ parentRoute: typeof ${routeNode.parent?.variableName ?? 'root'}Route
233
+ }`
234
+ })
235
+ .join('\n ')}
236
+ }
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')
250
+
251
+ if (nodesChanged) {
252
+ queueWriteFile(
253
+ path.resolve(config.generatedRouteTree),
254
+ routeConfigFileContent,
255
+ )
256
+ }
257
+
258
+ if (!checkLatest()) return
259
+
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')
267
+ }
268
+ if (current !== content) {
269
+ await fs.writeFile(filename, content)
270
+ }
271
+ }),
272
+ )
273
+
274
+ if (!checkLatest()) return
275
+
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)
288
+
289
+ console.log(
290
+ `🌲 Processed ${nodeCache.length} routes in ${Date.now() - start}ms`,
291
+ )
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
+ }
309
+
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}')`,
319
+ )
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
+ }
335
+
336
+ export function removeExt(d: string) {
337
+ return d.substring(0, d.lastIndexOf('.')) || d
338
+ }
339
+
340
+ function spaces(d: number): string {
341
+ return Array.from({ length: d })
342
+ .map(() => ' ')
343
+ .join('')
344
+ }
345
+
346
+ export function multiSortBy<T>(
347
+ arr: T[],
348
+ accessors: ((item: T) => any)[] = [(d) => d],
349
+ ): T[] {
350
+ return arr
351
+ .map((d, i) => [d, i] as const)
352
+ .sort(([a, ai], [b, bi]) => {
353
+ for (const accessor of accessors) {
354
+ const ao = accessor(a)
355
+ const bo = accessor(b)
356
+
357
+ if (typeof ao === 'undefined') {
358
+ if (typeof bo === 'undefined') {
359
+ continue
360
+ }
361
+ return 1
362
+ }
363
+
364
+ if (ao === bo) {
365
+ continue
366
+ }
367
+
368
+ return ao > bo ? 1 : -1
369
+ }
370
+
371
+ return ai - bi
372
+ })
373
+ .map(([d]) => d)
374
+ }
375
+
376
+ function capitalize(s: string) {
377
+ if (typeof s !== 'string') return ''
378
+ return s.charAt(0).toUpperCase() + s.slice(1)
379
+ }
package/src/index.ts ADDED
@@ -0,0 +1,24 @@
1
+ import * as yargs from 'yargs'
2
+ import { getConfig } from './config'
3
+ import { generate } from './generate'
4
+ import { watch } from './watch'
5
+
6
+ main()
7
+
8
+ export function main() {
9
+ yargs
10
+ .scriptName('tsr')
11
+ .usage('$0 <cmd> [args]')
12
+ .command('generate', 'Generate the routes for a project', async (argv) => {
13
+ const config = await getConfig()
14
+ await generate(config)
15
+ })
16
+ .command(
17
+ 'watch',
18
+ 'Continuously watch and generate the routes for a project',
19
+ async (argv) => {
20
+ watch()
21
+ },
22
+ )
23
+ .help().argv
24
+ }
package/src/watch.ts ADDED
@@ -0,0 +1,48 @@
1
+ import chokidar from 'chokidar'
2
+ import path from 'path'
3
+ import { getConfig } from './config'
4
+ import { generator } from './generator'
5
+
6
+ export async function watch() {
7
+ const configWatcher = chokidar.watch(
8
+ path.resolve(process.cwd(), 'tsr.config.js'),
9
+ )
10
+
11
+ let watcher = new chokidar.FSWatcher()
12
+
13
+ const generatorWatcher = async () => {
14
+ const config = await getConfig()
15
+
16
+ watcher.close()
17
+
18
+ console.log(`TSR: Watching routes (${config.routesDirectory})...`)
19
+ watcher = chokidar.watch(config.routesDirectory)
20
+
21
+ watcher.on('ready', async () => {
22
+ try {
23
+ await generator(config)
24
+ } catch (err) {
25
+ console.error(err)
26
+ console.log()
27
+ }
28
+
29
+ const handle = async () => {
30
+ try {
31
+ await generator(config)
32
+ } catch (err) {
33
+ console.error(err)
34
+ console.log()
35
+ }
36
+ }
37
+
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)
43
+ })
44
+ }
45
+
46
+ configWatcher.on('ready', generatorWatcher)
47
+ configWatcher.on('change', generatorWatcher)
48
+ }