@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.
@@ -1,903 +0,0 @@
1
- /**
2
- * router-cli
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
- import * as yargs from 'yargs';
12
- import path from 'path';
13
- import fs from 'fs-extra';
14
- import klaw from 'klaw';
15
- import through2 from 'through2';
16
- import crypto from 'crypto';
17
- import * as babel from '@babel/core';
18
- import * as t from '@babel/types';
19
- import syntaxTS from '@babel/plugin-syntax-typescript';
20
- import chokidar from 'chokidar';
21
-
22
- const configFilePathJson = path.resolve(process.cwd(), 'tsr.config.json');
23
- async function getConfig() {
24
- return fs.readJson(configFilePathJson);
25
- }
26
-
27
- const isolatedProperties = ['component', 'errorComponent', 'pendingComponent'];
28
- const getBasePlugins = () => [[syntaxTS, {
29
- isTSX: true
30
- }]];
31
- async function ensureBoilerplate(node, code) {
32
- const relativeImportPath = path.relative(node.fullDir, node.genPathNoExt);
33
- const originalFile = await babel.transformAsync(code, {
34
- configFile: false,
35
- babelrc: false,
36
- plugins: [...getBasePlugins()]
37
- });
38
- const file = await babel.transformAsync(code, {
39
- configFile: false,
40
- babelrc: false,
41
- plugins: [...getBasePlugins(), {
42
- visitor: {
43
- Program: {
44
- enter(programPath) {
45
- // Remove all properties except for our isolated one
46
- if (node.isRoot) {
47
- let foundImport = false;
48
- programPath.traverse({
49
- ImportSpecifier(importPath) {
50
- if (t.isIdentifier(importPath.node.imported) && importPath.node.imported.name === 'Route') {
51
- foundImport = true;
52
- }
53
- }
54
- });
55
- if (!foundImport) {
56
- programPath.node.body.unshift(babel.template.statement(`import { Route } from '@tanstack/react-router'`)());
57
- }
58
- } else {
59
- let foundImport = false;
60
- programPath.traverse({
61
- ImportSpecifier(importPath) {
62
- if (t.isIdentifier(importPath.node.imported) && importPath.node.imported.name === 'route') {
63
- foundImport = true;
64
- if (t.isImportDeclaration(importPath.parentPath.node)) {
65
- if (importPath.parentPath.node.source.value !== relativeImportPath) {
66
- importPath.parentPath.node.source.value = relativeImportPath;
67
- }
68
- }
69
- }
70
- }
71
- });
72
- if (!foundImport) {
73
- programPath.node.body.unshift(babel.template.statement(`import { Route } from '${relativeImportPath.replace(/\\/gi, '/')}'`)());
74
- }
75
- }
76
- }
77
- }
78
- }
79
- }]
80
- });
81
- const separator = 'new Route(';
82
- if (!originalFile?.code) {
83
- return `${file?.code}\n\n${separator}{\n\n})`;
84
- }
85
- const originalHead = originalFile?.code?.substring(0, originalFile?.code?.indexOf(separator));
86
- const generatedHead = file?.code?.substring(0, file?.code?.indexOf(separator));
87
- if (originalHead !== generatedHead) {
88
- return `${generatedHead}\n\n${originalFile?.code?.substring(originalFile?.code?.indexOf(separator))}`;
89
- }
90
- return;
91
- }
92
- async function isolateOptionToExport(node, code, opts) {
93
- return (await babel.transformAsync(code, {
94
- configFile: false,
95
- babelrc: false,
96
- plugins: [...getBasePlugins(), plugin()],
97
- ast: true
98
- }))?.code;
99
- function plugin() {
100
- return {
101
- visitor: {
102
- Program: {
103
- enter(programPath, state) {
104
- // If we're the root, handle things a bit differently
105
- if (node.isRoot) {
106
- programPath.traverse({
107
- Identifier(path) {
108
- if (path.node.name === 'Route' && t.isCallExpression(path.parentPath.node)) {
109
- const options = getRouteOptions(path);
110
- if (options) {
111
- const property = options.properties.find(property => {
112
- return t.isObjectProperty(property) && t.isIdentifier(property.key) && property.key.name === opts.isolate;
113
- });
114
- if (t.isObjectProperty(property)) {
115
- const program = path.findParent(d => d.isProgram());
116
- if (program?.isProgram()) {
117
- program.node.body.push(babel.template.statement(`export const ${opts.isolate} = $VAR`)({
118
- $VAR: property.value
119
- }));
120
- }
121
- }
122
- path.findParent(d => d.isExpressionStatement())?.remove();
123
- }
124
- }
125
- }
126
- });
127
- }
128
-
129
- // We're not in the root, handle things normally
130
- if (!node.isRoot) {
131
- // Remove all properties except for our isolated one
132
- programPath.traverse({
133
- Identifier(path) {
134
- if (path.node.name === 'generate') {
135
- const options = getRouteConfigGenerateOptions(path);
136
- if (options) {
137
- const property = options.properties.find(property => {
138
- return t.isObjectProperty(property) && t.isIdentifier(property.key) && property.key.name === opts.isolate;
139
- });
140
- if (t.isObjectProperty(property) && t.isIdentifier(property.key)) {
141
- if (property.key.name === opts.isolate) {
142
- const program = path.findParent(d => d.isProgram());
143
- if (program?.isProgram()) {
144
- program.node.body.push(babel.template.statement(`export const ${opts.isolate} = $VAR`)({
145
- $VAR: property.value
146
- }));
147
- }
148
- }
149
- }
150
- path.findParent(d => d.isExpressionStatement())?.remove();
151
- }
152
- }
153
- }
154
- });
155
- }
156
- cleanUnusedCode(programPath, state, [opts.isolate]);
157
- }
158
- }
159
- }
160
- };
161
- }
162
- }
163
- async function detectExports(code) {
164
- let exported = [];
165
-
166
- // try {
167
- await babel.transformAsync(code, {
168
- configFile: false,
169
- babelrc: false,
170
- plugins: [...getBasePlugins(), {
171
- visitor: {
172
- ExportNamedDeclaration(path) {
173
- if (t.isVariableDeclaration(path.node.declaration)) {
174
- if (t.isVariableDeclarator(path.node.declaration.declarations?.[0])) {
175
- if (t.isIdentifier(path.node.declaration.declarations[0].id)) {
176
- exported.push(path.node.declaration.declarations[0].id.name);
177
- }
178
- }
179
- }
180
- }
181
- }
182
- }],
183
- ast: true
184
- });
185
- return exported;
186
- }
187
- async function generateRouteConfig(node, routeCode, imports, clientOnly) {
188
- const relativeParentRoutePath = clientOnly ? node.parent ? removeExt(path.relative(node.genDir, path.resolve(node.parent?.genDir, node.parent?.clientFilename))) : `./${rootRouteClientName}` : node.parent ? removeExt(path.relative(node.genDir, path.resolve(node.parent?.genDir, node.parent?.filename))) : `./${rootRouteName}`;
189
- const pathName = node.isRoot ? undefined : node.fileNameNoExt.startsWith('__') ? undefined : node.fileNameNoExt === 'index' ? '/' : node.fileNameNoExt;
190
- const routeId = node.isRoot ? undefined : node.fileNameNoExt;
191
- function plugin() {
192
- return {
193
- visitor: {
194
- Program: {
195
- enter(programPath, state) {
196
- // Remove all of the isolated import properties from the config
197
- programPath.traverse({
198
- ImportSpecifier(path) {
199
- if (t.isIdentifier(path.node.imported)) {
200
- if (!node.isRoot) {
201
- if (path.node.imported.name === 'route') {
202
- path.parentPath.remove();
203
- const program = path.findParent(d => d.isProgram());
204
- if (program?.isProgram()) {
205
- program.node.body.unshift(babel.template.statement(`import { route as parentRoute } from '$IMPORT'`)({
206
- $IMPORT: relativeParentRoutePath
207
- }));
208
- }
209
- }
210
- }
211
- }
212
- },
213
- Identifier(iPath) {
214
- let options;
215
- if (node.isRoot) {
216
- if (iPath.node.name === 'Route') {
217
- if (t.isCallExpression(iPath.parentPath.node)) {
218
- if (t.isExpressionStatement(iPath.parentPath.parentPath?.node)) {
219
- iPath.parentPath.parentPath?.replaceWith(t.variableDeclaration('const', [t.variableDeclarator(t.identifier('route'), iPath.parentPath.node)]));
220
- }
221
- }
222
- }
223
- } else {
224
- if (iPath.node.name === 'generate') {
225
- if (t.isMemberExpression(iPath.parentPath.node)) {
226
- if (t.isIdentifier(iPath.parentPath.node.object)) {
227
- iPath.node.name = 'Route';
228
- iPath.parentPath.node.object.name = 'parentRoute';
229
- options = getRouteConfigGenerateOptions(iPath);
230
- }
231
- }
232
- }
233
- }
234
- if (options) {
235
- options.properties = [...(pathName ? [t.objectProperty(t.identifier('path'), t.stringLiteral(pathName))] : routeId ? [t.objectProperty(t.identifier('id'), t.stringLiteral(routeId))] : []), ...options.properties.map(property => {
236
- if (t.isObjectProperty(property) && t.isIdentifier(property.key) && isolatedProperties.includes(property.key.name)) {
237
- const key = property.key.name;
238
- if (key === 'loader') {
239
- if (clientOnly) {
240
- return t.objectProperty(t.identifier('loader'), t.tSAsExpression(t.booleanLiteral(true), t.tsAnyKeyword()));
241
- }
242
- return t.objectProperty(t.identifier(key), babel.template.expression(`(...args) => import('./${path.relative(node.genDir, node.genPathNoExt)}-${key}').then(d => d.${key}.apply(d.${key}, (args as any)))`, {
243
- plugins: ['typescript']
244
- })({}));
245
- }
246
- if (key === 'action') {
247
- if (clientOnly) {
248
- return t.objectProperty(t.identifier('action'), t.tSAsExpression(t.booleanLiteral(true), t.tSAnyKeyword()));
249
- }
250
- return t.objectProperty(t.identifier(key), babel.template.expression(`(...payload: Parameters<typeof import('./${path.relative(node.genDir, node.genPathNoExt)}-${key}').action>) => import('./${path.relative(node.genDir, node.genPathNoExt)}-${key}').then(d => d.${key}.apply(d.${key}, (payload as any)))`, {
251
- plugins: ['typescript']
252
- })({}));
253
- }
254
- if (clientOnly) {
255
- return t.objectProperty(t.identifier(key), babel.template.expression(`
256
- lazy(() => import('./${path.relative(node.genDir, node.genPathNoExt)}-${key}').then(d => ({ default: d.${key} }) ))`)());
257
- }
258
- return t.objectProperty(t.identifier(key), property.value);
259
- }
260
- return property;
261
- })];
262
- const program = iPath.findParent(d => d.isProgram());
263
- if (program?.isProgram() && options) {
264
- const index = program.node.body.findIndex(d => d.start === iPath.parentPath.parentPath?.node.start);
265
- if (node.isRoot) {
266
- program.node.body[index] = babel.template.statement(`const route = new Route(
267
- $OPTIONS
268
- )`)({
269
- $OPTIONS: options
270
- });
271
- } else {
272
- program.node.body[index] = babel.template.statement(`const route = new Route(
273
- $OPTIONS
274
- )`)({
275
- $OPTIONS: options
276
- });
277
- }
278
- }
279
- }
280
- }
281
- });
282
- programPath.node.body.unshift(babel.template.statement(`import { lazy } from '@tanstack/react-router'`)());
283
-
284
- // Add the route exports
285
- programPath.node.body.push(babel.template.statement(clientOnly ? `export { route, route as ${node.variable}Route }` : `export { route }`)());
286
- cleanUnusedCode(programPath, state, ['route', `${node.variable}Route`]);
287
- }
288
- }
289
- }
290
- };
291
- }
292
- const code = (await babel.transformAsync(routeCode, {
293
- configFile: false,
294
- babelrc: false,
295
- plugins: [...getBasePlugins(), plugin()],
296
- ast: true
297
- }))?.code;
298
- if (!code) {
299
- throw new Error('Error while generating a route file!');
300
- }
301
- return code;
302
- }
303
- function getIdentifier(path) {
304
- const parentPath = path.parentPath;
305
- if (parentPath.type === 'VariableDeclarator') {
306
- const pp = parentPath;
307
- const name = pp.get('id');
308
- return name.node.type === 'Identifier' ? name : null;
309
- }
310
- if (parentPath.type === 'AssignmentExpression') {
311
- const pp = parentPath;
312
- const name = pp.get('left');
313
- return name.node.type === 'Identifier' ? name : null;
314
- }
315
- if (path.node.type === 'ArrowFunctionExpression') {
316
- return null;
317
- }
318
- return path.node.id && path.node.id.type === 'Identifier' ? path.get('id') : null;
319
- }
320
- function isIdentifierReferenced(ident) {
321
- const b = ident.scope.getBinding(ident.node.name);
322
- if (b && b.referenced) {
323
- if (b.path.type === 'FunctionDeclaration') {
324
- return !b.constantViolations.concat(b.referencePaths).every(ref => ref.findParent(p => p === b.path));
325
- }
326
- return true;
327
- }
328
- return false;
329
- }
330
- function markFunction(path, state) {
331
- const ident = getIdentifier(path);
332
- if (ident && ident.node && isIdentifierReferenced(ident)) {
333
- state.refs.add(ident);
334
- }
335
- }
336
- function markImport(path, state) {
337
- const local = path.get('local');
338
- if (isIdentifierReferenced(local)) {
339
- state.refs.add(local);
340
- }
341
- }
342
- function getRouteConfigGenerateOptions(path) {
343
- const tryOptions = node => {
344
- if (t.isIdentifier(node)) {
345
- const initNode = path.scope.getBinding(node.name)?.path.node;
346
- if (t.isVariableDeclarator(initNode)) {
347
- return tryOptions(initNode.init);
348
- }
349
- } else if (t.isObjectExpression(node)) {
350
- return node;
351
- }
352
- return;
353
- };
354
- if (t.isMemberExpression(path.parentPath.node) && t.isCallExpression(path.parentPath.parentPath?.node)) {
355
- const options = path.parentPath.parentPath?.node.arguments[0];
356
- return tryOptions(options);
357
- }
358
- }
359
- function getRouteOptions(path) {
360
- const tryOptions = node => {
361
- if (t.isIdentifier(node)) {
362
- const initNode = path.scope.getBinding(node.name)?.path.node;
363
- if (t.isVariableDeclarator(initNode)) {
364
- return tryOptions(initNode.init);
365
- }
366
- } else if (t.isObjectExpression(node)) {
367
- return node;
368
- }
369
- return;
370
- };
371
- if (t.isCallExpression(path.parentPath?.node)) {
372
- const options = path.parentPath?.node.arguments[0];
373
- return tryOptions(options);
374
- }
375
- }
376
-
377
- // All credit for this amazing function goes to the Next.js team
378
- // (and the Solid.js team for their derivative work).
379
- // https://github.com/vercel/next.js/blob/canary/packages/next/build/babel/plugins/next-ssg-transform.ts
380
- // https://github.com/solidjs/solid-start/blob/main/packages/start/server/routeData.js
381
-
382
- function cleanUnusedCode(programPath, state, keepExports = []) {
383
- state.refs = new Set();
384
- state.done = false;
385
- function markVariable(variablePath, variableState) {
386
- if (variablePath.node.id.type === 'Identifier') {
387
- const local = variablePath.get('id');
388
- if (isIdentifierReferenced(local)) {
389
- variableState.refs.add(local);
390
- }
391
- } else if (variablePath.node.id.type === 'ObjectPattern') {
392
- const pattern = variablePath.get('id');
393
- const properties = pattern.get('properties');
394
- properties.forEach(p => {
395
- const local = p.get(p.node.type === 'ObjectProperty' ? 'value' : p.node.type === 'RestElement' ? 'argument' : function () {
396
- throw new Error('invariant');
397
- }());
398
- if (isIdentifierReferenced(local)) {
399
- variableState.refs.add(local);
400
- }
401
- });
402
- } else if (variablePath.node.id.type === 'ArrayPattern') {
403
- const pattern = variablePath.get('id');
404
- const elements = pattern.get('elements');
405
- elements.forEach(e => {
406
- let local;
407
- if (e.node && e.node.type === 'Identifier') {
408
- local = e;
409
- } else if (e.node && e.node.type === 'RestElement') {
410
- local = e.get('argument');
411
- } else {
412
- return;
413
- }
414
- if (isIdentifierReferenced(local)) {
415
- variableState.refs.add(local);
416
- }
417
- });
418
- }
419
- }
420
-
421
- // Mark all variables and functions if used
422
- programPath.traverse({
423
- VariableDeclarator: markVariable,
424
- FunctionDeclaration: markFunction,
425
- FunctionExpression: markFunction,
426
- ArrowFunctionExpression: markFunction,
427
- ImportSpecifier: markImport,
428
- ImportDefaultSpecifier: markImport,
429
- ImportNamespaceSpecifier: markImport,
430
- ExportDefaultDeclaration: markImport,
431
- // ExportNamedDeclaration(path, state) {
432
- // if (t.isVariableDeclaration(path.node.declaration)) {
433
- // if (t.isVariableDeclarator(path.node.declaration.declarations?.[0])) {
434
- // if (t.isIdentifier(path.node.declaration.declarations[0].id)) {
435
- // if (
436
- // keepExports.includes(
437
- // path.node.declaration.declarations[0].id.name,
438
- // )
439
- // ) {
440
- // return
441
- // }
442
- // }
443
- // path.replaceWith(path.node.declaration.declarations[0])
444
- // return
445
- // }
446
- // }
447
- // path.remove()
448
- // },
449
- ImportDeclaration: path => {
450
- if (path.node.source.value.endsWith('.css')) {
451
- path.remove();
452
- }
453
- }
454
- }, state);
455
-
456
- // Sweet all of the remaining references and remove unused ones
457
- const refs = state.refs;
458
- let count;
459
- function sweepFunction(sweepPath) {
460
- const ident = getIdentifier(sweepPath);
461
- if (ident && ident.node && refs.has(ident) && !isIdentifierReferenced(ident)) {
462
- ++count;
463
- if (t.isAssignmentExpression(sweepPath.parentPath) || t.isVariableDeclarator(sweepPath.parentPath)) {
464
- sweepPath.parentPath.remove();
465
- } else {
466
- sweepPath.remove();
467
- }
468
- }
469
- }
470
- function sweepImport(sweepPath) {
471
- const local = sweepPath.get('local');
472
- if (refs.has(local) && !isIdentifierReferenced(local)) {
473
- ++count;
474
- sweepPath.remove();
475
- if (sweepPath.parent.specifiers.length === 0) {
476
- sweepPath.parentPath.remove();
477
- }
478
- }
479
- }
480
- do {
481
- programPath.scope.crawl();
482
- count = 0;
483
- programPath.traverse({
484
- VariableDeclarator(variablePath) {
485
- if (variablePath.node.id.type === 'Identifier') {
486
- const local = variablePath.get('id');
487
- if (refs.has(local) && !isIdentifierReferenced(local)) {
488
- ++count;
489
- variablePath.remove();
490
- }
491
- } else if (variablePath.node.id.type === 'ObjectPattern') {
492
- const pattern = variablePath.get('id');
493
- const beforeCount = count;
494
- const properties = pattern.get('properties');
495
- properties.forEach(p => {
496
- const local = p.get(p.node.type === 'ObjectProperty' ? 'value' : p.node.type === 'RestElement' ? 'argument' : function () {
497
- throw new Error('invariant');
498
- }());
499
- if (refs.has(local) && !isIdentifierReferenced(local)) {
500
- ++count;
501
- p.remove();
502
- }
503
- });
504
- if (beforeCount !== count && pattern.get('properties').length < 1) {
505
- variablePath.remove();
506
- }
507
- } else if (variablePath.node.id.type === 'ArrayPattern') {
508
- const pattern = variablePath.get('id');
509
- const beforeCount = count;
510
- const elements = pattern.get('elements');
511
- elements.forEach(e => {
512
- let local;
513
- if (e.node && e.node.type === 'Identifier') {
514
- local = e;
515
- } else if (e.node && e.node.type === 'RestElement') {
516
- local = e.get('argument');
517
- } else {
518
- return;
519
- }
520
- if (refs.has(local) && !isIdentifierReferenced(local)) {
521
- ++count;
522
- e.remove();
523
- }
524
- });
525
- if (beforeCount !== count && pattern.get('elements').length < 1) {
526
- variablePath.remove();
527
- }
528
- }
529
- },
530
- FunctionDeclaration: sweepFunction,
531
- FunctionExpression: sweepFunction,
532
- ArrowFunctionExpression: sweepFunction,
533
- ImportSpecifier: sweepImport,
534
- ImportDefaultSpecifier: sweepImport,
535
- ImportNamespaceSpecifier: sweepImport
536
- });
537
- } while (count);
538
-
539
- // Do we need the * import for react?
540
- let hasReact = false;
541
-
542
- // Mark react elements as having react
543
- programPath.traverse({
544
- JSXElement(path) {
545
- hasReact = true;
546
- }
547
- });
548
- if (!hasReact) {
549
- // Mark all variables and functions if used
550
- programPath.traverse({
551
- ImportDeclaration(path) {
552
- if (t.isStringLiteral(path.node.source) && path.node.source.value === 'react' && t.isImportNamespaceSpecifier(path.node.specifiers[0])) {
553
- path.remove();
554
- }
555
- }
556
- });
557
- }
558
- }
559
-
560
- let latestTask = 0;
561
- const rootRouteName = '__root';
562
- const rootRouteClientName = '__root.client';
563
- let nodeCache = undefined;
564
- async function generator(config) {
565
- console.log();
566
- let first = false;
567
- if (!nodeCache) {
568
- first = true;
569
- console.log('🔄 Generating routes...');
570
- nodeCache = [];
571
- } else {
572
- console.log('♻️ Regenerating routes...');
573
- }
574
- const taskId = latestTask + 1;
575
- latestTask = taskId;
576
- const checkLatest = () => {
577
- if (latestTask !== taskId) {
578
- console.log(`- Skipping since file changes were made while generating.`);
579
- return false;
580
- }
581
- return true;
582
- };
583
- const start = Date.now();
584
- let routeConfigImports = [];
585
- let routeConfigClientImports = [];
586
- let nodesChanged = false;
587
- const fileQueue = [];
588
- const queueWriteFile = (filename, content) => {
589
- fileQueue.push([filename, content]);
590
- };
591
- async function reparent(dir) {
592
- let dirList;
593
- try {
594
- dirList = await fs.readdir(dir);
595
- } catch (err) {
596
- console.log();
597
- console.error('TSR: Error reading the config.routesDirectory. Does it exist?');
598
- console.log();
599
- throw err;
600
- }
601
- const dirListCombo = multiSortBy(await Promise.all(dirList.map(async filename => {
602
- const fullPath = path.resolve(dir, filename);
603
- const stat = await fs.lstat(fullPath);
604
- const ext = path.extname(filename);
605
- const clientFilename = filename.replace(ext, `.client${ext}`);
606
- const pathFromRoutes = path.relative(config.routesDirectory, fullPath);
607
- const genPath = path.resolve(config.routeGenDirectory, pathFromRoutes);
608
- const genPathNoExt = removeExt(genPath);
609
- const genDir = path.resolve(genPath, '..');
610
- const fileNameNoExt = removeExt(filename);
611
- return {
612
- filename,
613
- clientFilename,
614
- fileNameNoExt,
615
- fullPath,
616
- fullDir: dir,
617
- genPath,
618
- genDir,
619
- genPathNoExt,
620
- variable: fileToVariable(removeExt(pathFromRoutes)),
621
- isDirectory: stat.isDirectory(),
622
- isIndex: fileNameNoExt === 'index'
623
- };
624
- })), [d => d.fileNameNoExt === 'index' ? -1 : 1, d => d.fileNameNoExt, d => d.isDirectory ? 1 : -1]);
625
- const reparented = [];
626
- dirListCombo.forEach(async (d, i) => {
627
- if (d.isDirectory) {
628
- const parent = reparented.find(dd => !dd.isDirectory && dd.fileNameNoExt === d.filename);
629
- if (parent) {
630
- parent.childRoutesDir = d.fullPath;
631
- } else {
632
- reparented.push(d);
633
- }
634
- } else {
635
- reparented.push(d);
636
- }
637
- });
638
- return Promise.all(reparented.map(async d => {
639
- if (d.childRoutesDir) {
640
- const children = await reparent(d.childRoutesDir);
641
- d = {
642
- ...d,
643
- children
644
- };
645
- children.forEach(child => child.parent = d);
646
- return d;
647
- }
648
- return d;
649
- }));
650
- }
651
- const reparented = await reparent(config.routesDirectory);
652
- async function buildRouteConfig(nodes, depth = 1) {
653
- const children = nodes.map(async n => {
654
- let node = nodeCache.find(d => d.fullPath === n.fullPath);
655
- if (node) {
656
- node.new = false;
657
- } else {
658
- node = n;
659
- nodeCache.push(node);
660
- if (!first) {
661
- node.new = true;
662
- }
663
- }
664
- node.version = latestTask;
665
- if (node.fileNameNoExt === '__root') {
666
- node.isRoot = true;
667
- }
668
- const routeCode = await fs.readFile(node.fullPath, 'utf-8');
669
- const hashSum = crypto.createHash('sha256');
670
- hashSum.update(routeCode);
671
- const hash = hashSum.digest('hex');
672
- node.changed = node.hash !== hash;
673
- if (node.changed) {
674
- nodesChanged = true;
675
- node.hash = hash;
676
- try {
677
- // Ensure the boilerplate for the route exists
678
- const code = await ensureBoilerplate(node, routeCode);
679
- if (code) {
680
- await fs.writeFile(node.fullPath, code);
681
- }
682
- let imports = [];
683
- if (!node.isRoot) {
684
- // Generate the isolated files
685
- const transforms = await Promise.all(isolatedProperties.map(async key => {
686
- let exported = false;
687
- let exports = [];
688
- const transformed = await isolateOptionToExport(node, routeCode, {
689
- isolate: key
690
- });
691
- if (transformed) {
692
- exports = await detectExports(transformed);
693
- if (exports.includes(key)) {
694
- exported = true;
695
- }
696
- }
697
- return {
698
- key,
699
- exported,
700
- code: transformed
701
- };
702
- }));
703
- imports = transforms.filter(({
704
- exported
705
- }) => exported);
706
- node.importedFiles = await Promise.all(imports.map(({
707
- key,
708
- code
709
- }) => {
710
- const importFilename = `${node.genPathNoExt}-${key}.tsx`;
711
- queueWriteFile(importFilename, code);
712
- return importFilename;
713
- }));
714
- }
715
- const routeConfigCode = await generateRouteConfig(node, routeCode, imports, false);
716
- const clientRouteConfigCode = await generateRouteConfig(node, routeCode, imports, true);
717
- queueWriteFile(node.genPath, routeConfigCode);
718
- queueWriteFile(path.resolve(node.genDir, node.clientFilename), clientRouteConfigCode);
719
- } catch (err) {
720
- node.hash = '';
721
- }
722
- }
723
- routeConfigImports.push(`import { route as ${node.variable}Route } from './${removeExt(path.relative(config.routeGenDirectory, node.genPath).replace(/\\/gi, '/'))}'`);
724
- routeConfigClientImports.push(`import { route as ${node.variable}Route } from './${removeExt(path.relative(config.routeGenDirectory, path.resolve(node.genDir, node.clientFilename)).replace(/\\/gi, '/'))}'`);
725
- if (node.isRoot) {
726
- return undefined;
727
- }
728
- const route = `${node.variable}Route`;
729
- if (node.children?.length) {
730
- const childConfigs = await buildRouteConfig(node.children, depth + 1);
731
- return `${route}.addChildren([\n${spaces(depth * 4)}${childConfigs}\n${spaces(depth * 2)}])`;
732
- }
733
- return route;
734
- });
735
- return (await Promise.all(children)).filter(Boolean).join(`,\n${spaces(depth * 2)}`);
736
- }
737
- const routeConfigChildrenText = await buildRouteConfig(reparented);
738
- routeConfigImports = multiSortBy(routeConfigImports, [d => d.includes('__root') ? -1 : 1, d => d.split('/').length, d => d.endsWith("index'") ? -1 : 1, d => d]);
739
- routeConfigClientImports = multiSortBy(routeConfigClientImports, [d => d.includes('__root') ? -1 : 1, d => d.split('/').length, d => d.endsWith("index.client'") ? -1 : 1, d => d]);
740
- const routeConfig = `export const routeTree = rootRoute.addChildren([\n ${routeConfigChildrenText}\n])\nexport type __GeneratedRouteConfig = typeof routeTree`;
741
- const routeConfigClient = `export const routeTreeClient = rootRoute.addChildren([\n ${routeConfigChildrenText}\n]) as __GeneratedRouteConfig`;
742
- const routeConfigFileContent = [routeConfigImports.join('\n'), routeConfig].join('\n\n');
743
- const routeConfigClientFileContent = [`import type { __GeneratedRouteConfig } from './routeTree'`, routeConfigClientImports.join('\n'), routeConfigClient].join('\n\n');
744
- if (nodesChanged) {
745
- queueWriteFile(path.resolve(config.routeGenDirectory, 'routeTree.ts'), routeConfigFileContent);
746
- queueWriteFile(path.resolve(config.routeGenDirectory, 'routeTree.client.ts'), routeConfigClientFileContent);
747
- }
748
-
749
- // Do all of our file system manipulation at the end
750
- await fs.mkdir(config.routeGenDirectory, {
751
- recursive: true
752
- });
753
- if (!checkLatest()) return;
754
- await Promise.all(fileQueue.map(async ([filename, content]) => {
755
- await fs.ensureDir(path.dirname(filename));
756
- const exists = await fs.pathExists(filename);
757
- let current = '';
758
- if (exists) {
759
- current = await fs.readFile(filename, 'utf-8');
760
- }
761
- if (current !== content) {
762
- await fs.writeFile(filename, content);
763
- }
764
- }));
765
- if (!checkLatest()) return;
766
- const allFiles = await getAllFiles(config.routeGenDirectory);
767
- if (!checkLatest()) return;
768
- const removedNodes = [];
769
- nodeCache = nodeCache.filter(d => {
770
- if (d.version !== latestTask) {
771
- removedNodes.push(d);
772
- return false;
773
- }
774
- return true;
775
- });
776
- const newNodes = nodeCache.filter(d => d.new);
777
- const updatedNodes = nodeCache.filter(d => !d.new && d.changed);
778
- const unusedFiles = allFiles.filter(d => {
779
- if (d === path.resolve(config.routeGenDirectory, 'routeTree.ts') || d === path.resolve(config.routeGenDirectory, 'routeTree.client.ts')) {
780
- return false;
781
- }
782
- let node = nodeCache.find(n => n.genPath === d || path.resolve(n.genDir, n.clientFilename) === d || n.importedFiles?.includes(d));
783
- return !node;
784
- });
785
- await Promise.all(unusedFiles.map(d => {
786
- fs.remove(d);
787
- }));
788
- console.log(`🌲 Processed ${nodeCache.length} routes in ${Date.now() - start}ms`);
789
- if (newNodes.length || updatedNodes.length || removedNodes.length) {
790
- if (newNodes.length) {
791
- console.log(`🥳 Added ${newNodes.length} new routes`);
792
- }
793
- if (updatedNodes.length) {
794
- console.log(`✅ Updated ${updatedNodes.length} routes`);
795
- }
796
- if (removedNodes.length) {
797
- console.log(`🗑 Removed ${removedNodes.length} unused routes`);
798
- }
799
- } else {
800
- console.log(`🎉 No changes were found. Carry on!`);
801
- }
802
- }
803
- function getAllFiles(dir) {
804
- return new Promise((resolve, reject) => {
805
- const excludeDirFilter = through2.obj(function (item, enc, next) {
806
- if (!item.stats.isDirectory()) this.push(item);
807
- next();
808
- });
809
- const items = [];
810
- klaw(dir).pipe(excludeDirFilter).on('data', item => items.push(item.path)).on('error', err => reject(err)).on('end', () => resolve(items));
811
- });
812
- }
813
- function fileToVariable(d) {
814
- return d.split('/').map((d, i) => i > 0 ? capitalize(d) : d).join('').replace(/([^a-zA-Z0-9]|[\.])/gm, '');
815
- }
816
- function removeExt(d) {
817
- return d.substring(0, d.lastIndexOf('.')) || d;
818
- }
819
- function spaces(d) {
820
- return Array.from({
821
- length: d
822
- }).map(() => ' ').join('');
823
- }
824
- function multiSortBy(arr, accessors = [d => d]) {
825
- return arr.map((d, i) => [d, i]).sort(([a, ai], [b, bi]) => {
826
- for (const accessor of accessors) {
827
- const ao = accessor(a);
828
- const bo = accessor(b);
829
- if (typeof ao === 'undefined') {
830
- if (typeof bo === 'undefined') {
831
- continue;
832
- }
833
- return 1;
834
- }
835
- if (ao === bo) {
836
- continue;
837
- }
838
- return ao > bo ? 1 : -1;
839
- }
840
- return ai - bi;
841
- }).map(([d]) => d);
842
- }
843
- function capitalize(s) {
844
- if (typeof s !== 'string') return '';
845
- return s.charAt(0).toUpperCase() + s.slice(1);
846
- }
847
-
848
- async function generate(config) {
849
- try {
850
- await generator(config);
851
- process.exit(0);
852
- } catch (err) {
853
- console.error(err);
854
- process.exit(1);
855
- }
856
- }
857
-
858
- async function watch() {
859
- const configWatcher = chokidar.watch(path.resolve(process.cwd(), 'tsr.config.js'));
860
- let watcher = new chokidar.FSWatcher();
861
- const generatorWatcher = async () => {
862
- const config = await getConfig();
863
- watcher.close();
864
- console.log(`TSR: Watching routes (${config.routesDirectory})...`);
865
- watcher = chokidar.watch(config.routesDirectory);
866
- watcher.on('ready', async () => {
867
- try {
868
- await generator(config);
869
- } catch (err) {
870
- console.error(err);
871
- console.log();
872
- }
873
- const handle = async () => {
874
- try {
875
- await generator(config);
876
- } catch (err) {
877
- console.error(err);
878
- console.log();
879
- }
880
- };
881
- watcher.on('change', handle);
882
- watcher.on('add', handle);
883
- watcher.on('addDir', handle);
884
- watcher.on('unlink', handle);
885
- watcher.on('unlinkDir', handle);
886
- });
887
- };
888
- configWatcher.on('ready', generatorWatcher);
889
- configWatcher.on('change', generatorWatcher);
890
- }
891
-
892
- main();
893
- function main() {
894
- yargs.scriptName('tsr').usage('$0 <cmd> [args]').command('generate', 'Generate the routes for a project', async argv => {
895
- const config = await getConfig();
896
- await generate(config);
897
- }).command('watch', 'Continuously watch and generate the routes for a project', async argv => {
898
- watch();
899
- }).help().argv;
900
- }
901
-
902
- export { main };
903
- //# sourceMappingURL=index.js.map