@@ -17,14 +17,27 @@ import type { PackageManager } from './utils/getPackageManager.js'
17
17
18
18
const program = new Command ( )
19
19
20
+ const CODE_ROUTER = 'code-router'
21
+ const FILE_ROUTER = 'file-router'
22
+
20
23
interface Options {
21
24
typescript: boolean
22
25
tailwind: boolean
23
26
packageManager: PackageManager
27
+ mode: typeof CODE_ROUTER | typeof FILE_ROUTER
28
+ }
29
+
30
+ function sortObject ( obj : Record < string , string > ) : Record < string , string > {
31
+ return Object . keys ( obj )
32
+ . sort ( )
33
+ . reduce < Record < string , string >> ( ( acc , key ) => {
34
+ acc [ key ] = obj [ key ]
35
+ return acc
36
+ } , { } )
24
37
}
25
38
26
- const createCopyFile = ( templateDir : string , targetDir : string ) =>
27
- async function copyFiles ( files : Array < string > ) {
39
+ function createCopyFile ( targetDir : string ) {
40
+ return async function copyFiles ( templateDir : string , files : Array < string > ) {
28
41
for ( const file of files ) {
29
42
const targetFileName = file . replace ( '.tw' , '' )
30
43
await copyFile (
@@ -33,33 +46,41 @@ const createCopyFile = (templateDir: string, targetDir: string) =>
33
46
)
34
47
}
35
48
}
49
+ }
36
50
37
- const createTemplateFile = (
51
+ function createTemplateFile (
38
52
projectName : string ,
39
53
options : Required < Options > ,
40
- templateDir: string,
41
54
targetDir : string ,
42
- ) =>
43
- async function templateFile ( file : string , targetFileName ?: string ) {
55
+ ) {
56
+ return async function templateFile (
57
+ templateDir : string ,
58
+ file : string ,
59
+ targetFileName ?: string ,
60
+ ) {
44
61
const templateValues = {
45
62
packageManager : options . packageManager ,
46
63
projectName : projectName ,
47
64
typescript : options . typescript ,
48
65
tailwind : options . tailwind ,
49
66
js : options . typescript ? 'ts' : 'js' ,
50
67
jsx : options . typescript ? 'tsx' : 'jsx' ,
68
+ fileRouter : options . mode === FILE_ROUTER ,
69
+ codeRouter : options . mode === CODE_ROUTER ,
51
70
}
52
71
53
72
const template = await readFile ( resolve ( templateDir , file ) , 'utf-8' )
54
73
const content = render ( template , templateValues )
55
74
const target = targetFileName ?? file . replace ( '.ejs' , '' )
56
75
await writeFile ( resolve ( targetDir , target ) , content )
57
76
}
77
+ }
58
78
59
79
async function createPackageJSON (
60
80
projectName : string ,
61
81
options : Required < Options > ,
62
82
templateDir : string ,
83
+ routerDir : string ,
63
84
targetDir : string ,
64
85
) {
65
86
let packageJSON = JSON . parse (
@@ -90,25 +111,41 @@ async function createPackageJSON(
90
111
} ,
91
112
}
92
113
}
114
+ if ( options . mode === FILE_ROUTER ) {
115
+ const frPackageJSON = JSON . parse (
116
+ await readFile ( resolve ( routerDir , 'package.fr.json' ) , 'utf8' ) ,
117
+ )
118
+ packageJSON = {
119
+ ...packageJSON ,
120
+ dependencies : {
121
+ ...packageJSON . dependencies ,
122
+ ...frPackageJSON . dependencies ,
123
+ } ,
124
+ }
125
+ }
126
+ packageJSON . dependencies = sortObject (
127
+ packageJSON . dependencies as Record < string , string > ,
128
+ )
129
+ packageJSON . devDependencies = sortObject (
130
+ packageJSON . devDependencies as Record < string , string > ,
131
+ )
93
132
await writeFile (
94
133
resolve ( targetDir , 'package.json' ) ,
95
134
JSON . stringify ( packageJSON , null , 2 ) ,
96
135
)
97
136
}
98
137
99
138
async function createApp ( projectName : string , options : Required < Options > ) {
100
- const templateDir = fileURLToPath (
101
- new URL ( '../project-template' , import . meta. url ) ,
139
+ const templateDirBase = fileURLToPath (
140
+ new URL ( '../templates/base' , import . meta. url ) ,
141
+ )
142
+ const templateDirRouter = fileURLToPath (
143
+ new URL ( `../templates/${ options . mode } ` , import . meta. url ) ,
102
144
)
103
145
const targetDir = resolve ( process . cwd ( ) , projectName )
104
146
105
- const copyFiles = createCopyFile ( templateDir , targetDir )
106
- const templateFile = createTemplateFile (
107
- projectName ,
108
- options ,
109
- templateDir ,
110
- targetDir ,
111
- )
147
+ const copyFiles = createCopyFile ( targetDir )
148
+ const templateFile = createTemplateFile ( projectName , options , targetDir )
112
149
113
150
intro ( `Creating a new TanStack app in ${ targetDir } ...` )
114
151
@@ -118,13 +155,13 @@ async function createApp(projectName: string, options: Required<Options>) {
118
155
// Setup the .vscode directory
119
156
await mkdir ( resolve ( targetDir , '.vscode' ) , { recursive : true } )
120
157
await copyFile (
121
- resolve ( templateDir , '.vscode/settings.json' ) ,
158
+ resolve ( templateDirBase , '.vscode/settings.json' ) ,
122
159
resolve ( targetDir , '.vscode/settings.json' ) ,
123
160
)
124
161
125
162
// Fill the public directory
126
163
await mkdir ( resolve ( targetDir , 'public' ) , { recursive : true } )
127
- copyFiles ( [
164
+ copyFiles ( templateDirBase , [
128
165
'./public/robots.txt' ,
129
166
'./public/favicon.ico' ,
130
167
'./public/manifest.json' ,
@@ -134,55 +171,85 @@ async function createApp(projectName: string, options: Required<Options>) {
134
171
135
172
// Make the src directory
136
173
await mkdir ( resolve ( targetDir , 'src' ) , { recursive : true } )
174
+ if ( options . mode === FILE_ROUTER ) {
175
+ await mkdir ( resolve ( targetDir , 'src/routes' ) , { recursive : true } )
176
+ }
137
177
138
178
// Copy in Vite and Tailwind config and CSS
139
179
if ( ! options . tailwind ) {
140
- await copyFiles ( [ './src/App.css' ] )
180
+ await copyFiles ( templateDirBase , [ '. / src / App . css '] )
141
181
}
142
- await templateFile ( '. / vite . config . js . ejs ')
143
- await templateFile ( '. / src / styles . css . ejs ')
182
+ await templateFile ( templateDirBase , '. / vite . config . js . ejs ')
183
+ await templateFile ( templateDirBase , '. / src / styles . css . ejs ')
144
184
145
- copyFiles ( [ '. / src / logo . svg '] )
185
+ copyFiles ( templateDirBase , [ '. / src / logo . svg '] )
146
186
147
187
// Setup the app component. There are four variations, typescript/javascript and tailwind/non-tailwind.
148
- await templateFile (
149
- './src/App.tsx.ejs' ,
150
- options . typescript ? undefined : './src/App.jsx' ,
151
- )
152
- await templateFile (
153
- './src/App.test.tsx.ejs' ,
154
- options . typescript ? undefined : './src/App.test.jsx' ,
155
- )
188
+ if ( options . mode === FILE_ROUTER ) {
189
+ copyFiles ( templateDirRouter , [ '. / src / routes / __root . tsx '] )
190
+ await templateFile (
191
+ templateDirBase ,
192
+ './src/App.tsx.ejs' ,
193
+ './src/routes/index.tsx' ,
194
+ )
195
+ } else {
196
+ await templateFile (
197
+ templateDirBase ,
198
+ './src/App.tsx.ejs' ,
199
+ options . typescript ? undefined : './src/App.jsx' ,
200
+ )
201
+ await templateFile (
202
+ templateDirBase ,
203
+ './src/App.test.tsx.ejs' ,
204
+ options . typescript ? undefined : './src/App.test.jsx' ,
205
+ )
206
+ }
207
+
208
+ // Create the main entry point
209
+ if ( options . typescript ) {
210
+ await templateFile ( templateDirRouter , './src/main.tsx.ejs' )
211
+ } else {
212
+ await templateFile (
213
+ templateDirRouter ,
214
+ './src/main.tsx.ejs' ,
215
+ './src/main.jsx' ,
216
+ )
217
+ }
156
218
157
219
// Setup the main, reportWebVitals and index.html files
158
220
if ( options . typescript ) {
159
- await templateFile ( './src/main.tsx.ejs' )
160
- await templateFile ( './src/reportWebVitals.ts.ejs' )
221
+ await templateFile ( templateDirBase , './src/reportWebVitals.ts.ejs' )
161
222
} else {
162
- await templateFile ( './src/main.tsx.ejs' , './src/main.jsx' )
163
223
await templateFile (
224
+ templateDirBase ,
164
225
'./src/reportWebVitals.ts.ejs' ,
165
226
'./src/reportWebVitals.js' ,
166
227
)
167
228
}
168
- await templateFile ( './index.html.ejs' )
229
+ await templateFile ( templateDirBase , './index.html.ejs' )
169
230
170
231
// Setup tsconfig
171
232
if ( options . typescript ) {
172
- await copyFiles ( [ './tsconfig.json' , './tsconfig.dev.json' ] )
233
+ await copyFiles ( templateDirBase , [ './tsconfig.json' , './tsconfig.dev.json' ] )
173
234
}
174
235
175
236
// Setup the package.json file, optionally with typescript and tailwind
176
- await createPackageJSON ( projectName , options , templateDir , targetDir )
237
+ await createPackageJSON (
238
+ projectName ,
239
+ options ,
240
+ templateDirBase ,
241
+ templateDirRouter ,
242
+ targetDir ,
243
+ )
177
244
178
245
// Add .gitignore
179
246
await copyFile (
180
- resolve ( templateDir , 'gitignore' ) ,
247
+ resolve ( templateDirBase , 'gitignore' ) ,
181
248
resolve ( targetDir , '.gitignore' ) ,
182
249
)
183
250
184
251
// Create the README.md
185
- await templateFile ( 'README.md.ejs' )
252
+ await templateFile ( templateDirBase , 'README.md.ejs' )
186
253
187
254
// Install dependencies
188
255
const s = spinner ( )
@@ -197,13 +264,17 @@ program
197
264
. name ( 'create-tsrouter-app' )
198
265
. description ( 'CLI to create a new TanStack application' )
199
266
. argument ( '<project-name>' , 'name of the project' )
200
- . option < 'typescript' | 'javascript' > (
267
+ . option < 'typescript' | 'javascript' | 'file-router' > (
201
268
'--template <type>' ,
202
- 'project template (typescript/ javascript)' ,
269
+ 'project template (typescript, javascript, file-router )' ,
203
270
( value ) => {
204
- if ( value !== 'typescript' && value !== 'javascript' ) {
271
+ if (
272
+ value !== 'typescript' &&
273
+ value !== 'javascript' &&
274
+ value !== 'file-router'
275
+ ) {
205
276
throw new InvalidArgumentError (
206
- `Invalid template: ${ value } . Only the following are allowed: typescript, javascript` ,
277
+ `Invalid template: ${ value } . Only the following are allowed: typescript, javascript, file-router ` ,
207
278
)
208
279
}
209
280
return value
@@ -230,17 +301,19 @@ program
230
301
(
231
302
projectName : string ,
232
303
options : {
233
- template : string
304
+ template : 'typescript' | 'javascript' | 'file-router'
234
305
tailwind : boolean
235
306
packageManager : PackageManager
236
307
} ,
237
308
) => {
238
- const typescript = options . template === 'typescript'
309
+ const typescript =
310
+ options . template === 'typescript' || options . template === 'file-router'
239
311
240
312
createApp ( projectName , {
241
313
typescript,
242
314
tailwind : options . tailwind ,
243
315
packageManager : options . packageManager ,
316
+ mode : options . template === 'file-router' ? FILE_ROUTER : CODE_ROUTER ,
244
317
} )
245
318
} ,
246
319
)
0 commit comments