@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 +21 -0
- package/bin/tsr.js +3 -0
- package/build/cjs/config.js +29 -0
- package/build/cjs/config.js.map +1 -0
- package/build/cjs/generate.js +28 -0
- package/build/cjs/generate.js.map +1 -0
- package/build/cjs/generator.js +260 -0
- package/build/cjs/generator.js.map +1 -0
- package/build/cjs/index.js +51 -0
- package/build/cjs/index.js.map +1 -0
- package/build/cjs/watch.js +60 -0
- package/build/cjs/watch.js.map +1 -0
- package/build/types/index.d.ts +13 -0
- package/package.json +64 -0
- package/src/config.ts +15 -0
- package/src/generate.ts +12 -0
- package/src/generator.ts +379 -0
- package/src/index.ts +24 -0
- package/src/watch.ts +48 -0
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,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
|
+
}
|
package/src/generate.ts
ADDED
package/src/generator.ts
ADDED
|
@@ -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
|
+
}
|