@tanstack/router-cli 0.0.1-beta.29

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