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