@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.
- package/bin/tsr.js +3 -0
- package/build/cjs/_virtual/_rollupPluginBabelHelpers.js +31 -0
- package/build/cjs/_virtual/_rollupPluginBabelHelpers.js.map +1 -0
- package/build/cjs/config.js +29 -0
- package/build/cjs/config.js.map +1 -0
- package/build/cjs/generate.js +28 -0
- package/build/cjs/generate.js.map +1 -0
- package/build/cjs/generator.js +343 -0
- package/build/cjs/generator.js.map +1 -0
- package/build/cjs/index.js +51 -0
- package/build/cjs/index.js.map +1 -0
- package/build/cjs/transformCode.js +598 -0
- package/build/cjs/transformCode.js.map +1 -0
- package/build/cjs/watch.js +60 -0
- package/build/cjs/watch.js.map +1 -0
- package/build/esm/index.js +951 -0
- package/build/esm/index.js.map +1 -0
- package/build/types/index.d.ts +13 -0
- package/package.json +61 -0
- package/src/config.ts +15 -0
- package/src/generate.ts +12 -0
- package/src/generator.ts +509 -0
- package/src/index.ts +24 -0
- package/src/transformCode.ts +857 -0
- package/src/watch.ts +48 -0
package/src/generator.ts
ADDED
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
import klaw from 'klaw'
|
|
2
|
+
import through2 from 'through2'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
import fs from 'fs-extra'
|
|
5
|
+
import crypto from 'crypto'
|
|
6
|
+
import {
|
|
7
|
+
detectExports,
|
|
8
|
+
ensureBoilerplate,
|
|
9
|
+
generateRouteConfig,
|
|
10
|
+
isolatedProperties,
|
|
11
|
+
isolateOptionToExport,
|
|
12
|
+
} from './transformCode'
|
|
13
|
+
import { Config } from './config'
|
|
14
|
+
|
|
15
|
+
let latestTask = 0
|
|
16
|
+
export const rootRouteName = '__root'
|
|
17
|
+
export const rootRouteClientName = '__root.client'
|
|
18
|
+
|
|
19
|
+
export type RouteNode = {
|
|
20
|
+
filename: string
|
|
21
|
+
clientFilename: string
|
|
22
|
+
fileNameNoExt: string
|
|
23
|
+
fullPath: string
|
|
24
|
+
fullDir: string
|
|
25
|
+
isDirectory: boolean
|
|
26
|
+
isIndex: boolean
|
|
27
|
+
variable: string
|
|
28
|
+
childRoutesDir?: string
|
|
29
|
+
genPath: string
|
|
30
|
+
genDir: string
|
|
31
|
+
genPathNoExt: string
|
|
32
|
+
parent?: RouteNode
|
|
33
|
+
hash?: string
|
|
34
|
+
importedFiles?: string[]
|
|
35
|
+
version?: number
|
|
36
|
+
changed?: boolean
|
|
37
|
+
new?: boolean
|
|
38
|
+
isRoot?: boolean
|
|
39
|
+
children?: RouteNode[]
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type IsolatedExport = {
|
|
43
|
+
key: string
|
|
44
|
+
exported: boolean
|
|
45
|
+
code?: string | null
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let nodeCache: RouteNode[] = undefined!
|
|
49
|
+
|
|
50
|
+
export async function generator(config: Config) {
|
|
51
|
+
console.log()
|
|
52
|
+
|
|
53
|
+
let first = false
|
|
54
|
+
|
|
55
|
+
if (!nodeCache) {
|
|
56
|
+
first = true
|
|
57
|
+
console.log('🔄 Generating routes...')
|
|
58
|
+
nodeCache = []
|
|
59
|
+
} else {
|
|
60
|
+
console.log('♻️ Regenerating routes...')
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const taskId = latestTask + 1
|
|
64
|
+
latestTask = taskId
|
|
65
|
+
|
|
66
|
+
const checkLatest = () => {
|
|
67
|
+
if (latestTask !== taskId) {
|
|
68
|
+
console.log(`- Skipping since file changes were made while generating.`)
|
|
69
|
+
return false
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return true
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const start = Date.now()
|
|
76
|
+
let routeConfigImports: string[] = []
|
|
77
|
+
let routeConfigClientImports: string[] = []
|
|
78
|
+
|
|
79
|
+
let nodesChanged = false
|
|
80
|
+
const fileQueue: [string, string][] = []
|
|
81
|
+
const queueWriteFile = (filename: string, content: string) => {
|
|
82
|
+
fileQueue.push([filename, content])
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function reparent(dir: string): Promise<RouteNode[]> {
|
|
86
|
+
let dirList
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
dirList = await fs.readdir(dir)
|
|
90
|
+
} catch (err) {
|
|
91
|
+
console.log()
|
|
92
|
+
console.error(
|
|
93
|
+
'TSR: Error reading the config.routesDirectory. Does it exist?',
|
|
94
|
+
)
|
|
95
|
+
console.log()
|
|
96
|
+
throw err
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const dirListCombo = multiSortBy(
|
|
100
|
+
await Promise.all(
|
|
101
|
+
dirList.map(async (filename): Promise<RouteNode> => {
|
|
102
|
+
const fullPath = path.resolve(dir, filename)
|
|
103
|
+
const stat = await fs.lstat(fullPath)
|
|
104
|
+
const ext = path.extname(filename)
|
|
105
|
+
|
|
106
|
+
const clientFilename = filename.replace(ext, `.client${ext}`)
|
|
107
|
+
|
|
108
|
+
const pathFromRoutes = path.relative(config.routesDirectory, fullPath)
|
|
109
|
+
const genPath = path.resolve(config.routeGenDirectory, pathFromRoutes)
|
|
110
|
+
|
|
111
|
+
const genPathNoExt = removeExt(genPath)
|
|
112
|
+
const genDir = path.resolve(genPath, '..')
|
|
113
|
+
|
|
114
|
+
const fileNameNoExt = removeExt(filename)
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
filename,
|
|
118
|
+
clientFilename,
|
|
119
|
+
fileNameNoExt,
|
|
120
|
+
fullPath,
|
|
121
|
+
fullDir: dir,
|
|
122
|
+
genPath,
|
|
123
|
+
genDir,
|
|
124
|
+
genPathNoExt,
|
|
125
|
+
variable: fileToVariable(removeExt(pathFromRoutes)),
|
|
126
|
+
isDirectory: stat.isDirectory(),
|
|
127
|
+
isIndex: fileNameNoExt === 'index',
|
|
128
|
+
}
|
|
129
|
+
}),
|
|
130
|
+
),
|
|
131
|
+
[
|
|
132
|
+
(d) => (d.fileNameNoExt === 'index' ? -1 : 1),
|
|
133
|
+
(d) => d.fileNameNoExt,
|
|
134
|
+
(d) => (d.isDirectory ? 1 : -1),
|
|
135
|
+
],
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
const reparented: typeof dirListCombo = []
|
|
139
|
+
|
|
140
|
+
dirListCombo.forEach(async (d, i) => {
|
|
141
|
+
if (d.isDirectory) {
|
|
142
|
+
const parent = reparented.find(
|
|
143
|
+
(dd) => !dd.isDirectory && dd.fileNameNoExt === d.filename,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
if (parent) {
|
|
147
|
+
parent.childRoutesDir = d.fullPath
|
|
148
|
+
} else {
|
|
149
|
+
reparented.push(d)
|
|
150
|
+
}
|
|
151
|
+
} else {
|
|
152
|
+
reparented.push(d)
|
|
153
|
+
}
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
return Promise.all(
|
|
157
|
+
reparented.map(async (d) => {
|
|
158
|
+
if (d.childRoutesDir) {
|
|
159
|
+
const children = await reparent(d.childRoutesDir)
|
|
160
|
+
|
|
161
|
+
d = {
|
|
162
|
+
...d,
|
|
163
|
+
children,
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
children.forEach((child) => (child.parent = d))
|
|
167
|
+
|
|
168
|
+
return d
|
|
169
|
+
}
|
|
170
|
+
return d
|
|
171
|
+
}),
|
|
172
|
+
)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const reparented = await reparent(config.routesDirectory)
|
|
176
|
+
|
|
177
|
+
async function buildRouteConfig(
|
|
178
|
+
nodes: RouteNode[],
|
|
179
|
+
depth = 1,
|
|
180
|
+
): Promise<string> {
|
|
181
|
+
const children = nodes.map(async (n) => {
|
|
182
|
+
let node = nodeCache.find((d) => d.fullPath === n.fullPath)!
|
|
183
|
+
|
|
184
|
+
if (node) {
|
|
185
|
+
node.new = false
|
|
186
|
+
} else {
|
|
187
|
+
node = n
|
|
188
|
+
nodeCache.push(node)
|
|
189
|
+
if (!first) {
|
|
190
|
+
node.new = true
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
node.version = latestTask
|
|
195
|
+
if (node.fileNameNoExt === '__root') {
|
|
196
|
+
node.isRoot = true
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const routeCode = await fs.readFile(node.fullPath, 'utf-8')
|
|
200
|
+
|
|
201
|
+
const hashSum = crypto.createHash('sha256')
|
|
202
|
+
hashSum.update(routeCode)
|
|
203
|
+
const hash = hashSum.digest('hex')
|
|
204
|
+
|
|
205
|
+
node.changed = node.hash !== hash
|
|
206
|
+
if (node.changed) {
|
|
207
|
+
nodesChanged = true
|
|
208
|
+
node.hash = hash
|
|
209
|
+
|
|
210
|
+
try {
|
|
211
|
+
// Ensure the boilerplate for the route exists
|
|
212
|
+
const code = await ensureBoilerplate(node, routeCode)
|
|
213
|
+
|
|
214
|
+
if (code) {
|
|
215
|
+
await fs.writeFile(node.fullPath, code)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
let imports: IsolatedExport[] = []
|
|
219
|
+
|
|
220
|
+
if (!node.isRoot) {
|
|
221
|
+
// Generate the isolated files
|
|
222
|
+
const transforms = await Promise.all(
|
|
223
|
+
isolatedProperties.map(async (key): Promise<IsolatedExport> => {
|
|
224
|
+
let exported = false
|
|
225
|
+
let exports: string[] = []
|
|
226
|
+
|
|
227
|
+
const transformed = await isolateOptionToExport(
|
|
228
|
+
node,
|
|
229
|
+
routeCode,
|
|
230
|
+
{
|
|
231
|
+
isolate: key,
|
|
232
|
+
},
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
if (transformed) {
|
|
236
|
+
exports = await detectExports(transformed)
|
|
237
|
+
if (exports.includes(key)) {
|
|
238
|
+
exported = true
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return { key, exported, code: transformed }
|
|
243
|
+
}),
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
imports = transforms.filter(({ exported }) => exported)
|
|
247
|
+
|
|
248
|
+
node.importedFiles = await Promise.all(
|
|
249
|
+
imports.map(({ key, code }) => {
|
|
250
|
+
const importFilename = `${node.genPathNoExt}-${key}.tsx`
|
|
251
|
+
queueWriteFile(importFilename, code!)
|
|
252
|
+
return importFilename
|
|
253
|
+
}),
|
|
254
|
+
)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const routeConfigCode = await generateRouteConfig(
|
|
258
|
+
node,
|
|
259
|
+
routeCode,
|
|
260
|
+
imports,
|
|
261
|
+
false,
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
const clientRouteConfigCode = await generateRouteConfig(
|
|
265
|
+
node,
|
|
266
|
+
routeCode,
|
|
267
|
+
imports,
|
|
268
|
+
true,
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
queueWriteFile(node.genPath, routeConfigCode)
|
|
272
|
+
queueWriteFile(
|
|
273
|
+
path.resolve(node.genDir, node.clientFilename),
|
|
274
|
+
clientRouteConfigCode,
|
|
275
|
+
)
|
|
276
|
+
} catch (err) {
|
|
277
|
+
node.hash = ''
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
routeConfigImports.push(
|
|
282
|
+
`import { routeConfig as ${node.variable}Route } from './${removeExt(
|
|
283
|
+
path.relative(config.routeGenDirectory, node.genPath),
|
|
284
|
+
)}'`,
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
routeConfigClientImports.push(
|
|
288
|
+
`import { routeConfig as ${node.variable}Route } from './${removeExt(
|
|
289
|
+
path.relative(
|
|
290
|
+
config.routeGenDirectory,
|
|
291
|
+
path.resolve(node.genDir, node.clientFilename),
|
|
292
|
+
),
|
|
293
|
+
)}'`,
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
if (node.isRoot) {
|
|
297
|
+
return undefined
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const route = `${node.variable}Route`
|
|
301
|
+
|
|
302
|
+
if (node.children?.length) {
|
|
303
|
+
const childConfigs = await buildRouteConfig(node.children, depth + 1)
|
|
304
|
+
return `${route}.addChildren([\n${spaces(
|
|
305
|
+
depth * 4,
|
|
306
|
+
)}${childConfigs}\n${spaces(depth * 2)}])`
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return route
|
|
310
|
+
})
|
|
311
|
+
|
|
312
|
+
return (await Promise.all(children))
|
|
313
|
+
.filter(Boolean)
|
|
314
|
+
.join(`,\n${spaces(depth * 2)}`)
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const routeConfigChildrenText = await buildRouteConfig(reparented)
|
|
318
|
+
|
|
319
|
+
routeConfigImports = multiSortBy(routeConfigImports, [
|
|
320
|
+
(d) => (d.includes('__root') ? -1 : 1),
|
|
321
|
+
(d) => d.split('/').length,
|
|
322
|
+
(d) => (d.endsWith("index'") ? -1 : 1),
|
|
323
|
+
(d) => d,
|
|
324
|
+
])
|
|
325
|
+
|
|
326
|
+
routeConfigClientImports = multiSortBy(routeConfigClientImports, [
|
|
327
|
+
(d) => (d.includes('__root') ? -1 : 1),
|
|
328
|
+
(d) => d.split('/').length,
|
|
329
|
+
(d) => (d.endsWith("index.client'") ? -1 : 1),
|
|
330
|
+
(d) => d,
|
|
331
|
+
])
|
|
332
|
+
|
|
333
|
+
const routeConfig = `export const routeConfig = rootRoute.addChildren([\n ${routeConfigChildrenText}\n])\nexport type __GeneratedRouteConfig = typeof routeConfig`
|
|
334
|
+
const routeConfigClient = `export const routeConfigClient = rootRoute.addChildren([\n ${routeConfigChildrenText}\n]) as __GeneratedRouteConfig`
|
|
335
|
+
|
|
336
|
+
const routeConfigFileContent = [
|
|
337
|
+
routeConfigImports.join('\n'),
|
|
338
|
+
routeConfig,
|
|
339
|
+
].join('\n\n')
|
|
340
|
+
|
|
341
|
+
const routeConfigClientFileContent = [
|
|
342
|
+
`import type { __GeneratedRouteConfig } from './routeConfig'`,
|
|
343
|
+
routeConfigClientImports.join('\n'),
|
|
344
|
+
routeConfigClient,
|
|
345
|
+
].join('\n\n')
|
|
346
|
+
|
|
347
|
+
if (nodesChanged) {
|
|
348
|
+
queueWriteFile(
|
|
349
|
+
path.resolve(config.routeGenDirectory, 'routeConfig.ts'),
|
|
350
|
+
routeConfigFileContent,
|
|
351
|
+
)
|
|
352
|
+
queueWriteFile(
|
|
353
|
+
path.resolve(config.routeGenDirectory, 'routeConfig.client.ts'),
|
|
354
|
+
routeConfigClientFileContent,
|
|
355
|
+
)
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Do all of our file system manipulation at the end
|
|
359
|
+
await fs.mkdir(config.routeGenDirectory, { recursive: true })
|
|
360
|
+
|
|
361
|
+
if (!checkLatest()) return
|
|
362
|
+
|
|
363
|
+
await Promise.all(
|
|
364
|
+
fileQueue.map(async ([filename, content]) => {
|
|
365
|
+
await fs.ensureDir(path.dirname(filename))
|
|
366
|
+
const exists = await fs.pathExists(filename)
|
|
367
|
+
let current = ''
|
|
368
|
+
if (exists) {
|
|
369
|
+
current = await fs.readFile(filename, 'utf-8')
|
|
370
|
+
}
|
|
371
|
+
if (current !== content) {
|
|
372
|
+
await fs.writeFile(filename, content)
|
|
373
|
+
}
|
|
374
|
+
}),
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
if (!checkLatest()) return
|
|
378
|
+
|
|
379
|
+
const allFiles = await getAllFiles(config.routeGenDirectory)
|
|
380
|
+
|
|
381
|
+
if (!checkLatest()) return
|
|
382
|
+
|
|
383
|
+
const removedNodes: RouteNode[] = []
|
|
384
|
+
|
|
385
|
+
nodeCache = nodeCache.filter((d) => {
|
|
386
|
+
if (d.version !== latestTask) {
|
|
387
|
+
removedNodes.push(d)
|
|
388
|
+
return false
|
|
389
|
+
}
|
|
390
|
+
return true
|
|
391
|
+
})
|
|
392
|
+
|
|
393
|
+
const newNodes = nodeCache.filter((d) => d.new)
|
|
394
|
+
const updatedNodes = nodeCache.filter((d) => !d.new && d.changed)
|
|
395
|
+
|
|
396
|
+
const unusedFiles = allFiles.filter((d) => {
|
|
397
|
+
if (
|
|
398
|
+
d === path.resolve(config.routeGenDirectory, 'routeConfig.ts') ||
|
|
399
|
+
d === path.resolve(config.routeGenDirectory, 'routeConfig.client.ts')
|
|
400
|
+
) {
|
|
401
|
+
return false
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
let node = nodeCache.find(
|
|
405
|
+
(n) =>
|
|
406
|
+
n.genPath === d ||
|
|
407
|
+
path.resolve(n.genDir, n.clientFilename) === d ||
|
|
408
|
+
n.importedFiles?.includes(d),
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
return !node
|
|
412
|
+
})
|
|
413
|
+
|
|
414
|
+
await Promise.all(
|
|
415
|
+
unusedFiles.map((d) => {
|
|
416
|
+
fs.remove(d)
|
|
417
|
+
}),
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
console.log(
|
|
421
|
+
`🌲 Processed ${nodeCache.length} routes in ${Date.now() - start}ms`,
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
if (newNodes.length || updatedNodes.length || removedNodes.length) {
|
|
425
|
+
if (newNodes.length) {
|
|
426
|
+
console.log(`🥳 Added ${newNodes.length} new routes`)
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
if (updatedNodes.length) {
|
|
430
|
+
console.log(`✅ Updated ${updatedNodes.length} routes`)
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (removedNodes.length) {
|
|
434
|
+
console.log(`🗑 Removed ${removedNodes.length} unused routes`)
|
|
435
|
+
}
|
|
436
|
+
} else {
|
|
437
|
+
console.log(`🎉 No changes were found. Carry on!`)
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
function getAllFiles(dir: string): Promise<string[]> {
|
|
442
|
+
return new Promise((resolve, reject) => {
|
|
443
|
+
const excludeDirFilter = through2.obj(function (item, enc, next) {
|
|
444
|
+
if (!item.stats.isDirectory()) this.push(item)
|
|
445
|
+
next()
|
|
446
|
+
})
|
|
447
|
+
|
|
448
|
+
const items: string[] = []
|
|
449
|
+
|
|
450
|
+
klaw(dir)
|
|
451
|
+
.pipe(excludeDirFilter)
|
|
452
|
+
.on('data', (item) => items.push(item.path))
|
|
453
|
+
.on('error', (err) => reject(err))
|
|
454
|
+
.on('end', () => resolve(items))
|
|
455
|
+
})
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function fileToVariable(d: string) {
|
|
459
|
+
return d
|
|
460
|
+
.split('/')
|
|
461
|
+
.map((d, i) => (i > 0 ? capitalize(d) : d))
|
|
462
|
+
.join('')
|
|
463
|
+
.replace(/([^a-zA-Z0-9]|[\.])/gm, '')
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
export function removeExt(d: string) {
|
|
467
|
+
return d.substring(0, d.lastIndexOf('.')) || d
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function spaces(d: number): string {
|
|
471
|
+
return Array.from({ length: d })
|
|
472
|
+
.map(() => ' ')
|
|
473
|
+
.join('')
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
export function multiSortBy<T>(
|
|
477
|
+
arr: T[],
|
|
478
|
+
accessors: ((item: T) => any)[] = [(d) => d],
|
|
479
|
+
): T[] {
|
|
480
|
+
return arr
|
|
481
|
+
.map((d, i) => [d, i] as const)
|
|
482
|
+
.sort(([a, ai], [b, bi]) => {
|
|
483
|
+
for (const accessor of accessors) {
|
|
484
|
+
const ao = accessor(a)
|
|
485
|
+
const bo = accessor(b)
|
|
486
|
+
|
|
487
|
+
if (typeof ao === 'undefined') {
|
|
488
|
+
if (typeof bo === 'undefined') {
|
|
489
|
+
continue
|
|
490
|
+
}
|
|
491
|
+
return 1
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
if (ao === bo) {
|
|
495
|
+
continue
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
return ao > bo ? 1 : -1
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
return ai - bi
|
|
502
|
+
})
|
|
503
|
+
.map(([d]) => d)
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function capitalize(s: string) {
|
|
507
|
+
if (typeof s !== 'string') return ''
|
|
508
|
+
return s.charAt(0).toUpperCase() + s.slice(1)
|
|
509
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as yargs from 'yargs'
|
|
2
|
+
import { getConfig } from './config'
|
|
3
|
+
import { generate } from './generate'
|
|
4
|
+
import { watch } from './watch'
|
|
5
|
+
|
|
6
|
+
main()
|
|
7
|
+
|
|
8
|
+
export function main() {
|
|
9
|
+
yargs
|
|
10
|
+
.scriptName('tsr')
|
|
11
|
+
.usage('$0 <cmd> [args]')
|
|
12
|
+
.command('generate', 'Generate the routes for a project', async (argv) => {
|
|
13
|
+
const config = await getConfig()
|
|
14
|
+
await generate(config)
|
|
15
|
+
})
|
|
16
|
+
.command(
|
|
17
|
+
'watch',
|
|
18
|
+
'Continuously watch and generate the routes for a project',
|
|
19
|
+
async (argv) => {
|
|
20
|
+
watch()
|
|
21
|
+
},
|
|
22
|
+
)
|
|
23
|
+
.help().argv
|
|
24
|
+
}
|