create-start-app 0.3.1 → 0.4.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.
Files changed (166) hide show
  1. package/README.md +53 -9
  2. package/dist/add-ons.js +69 -0
  3. package/dist/cli.js +78 -0
  4. package/dist/constants.js +4 -0
  5. package/dist/create-app.js +371 -0
  6. package/dist/index.js +2 -347
  7. package/dist/mcp.js +169 -0
  8. package/dist/options.js +261 -0
  9. package/dist/{utils/getPackageManager.js → package-manager.js} +1 -0
  10. package/dist/types.js +1 -0
  11. package/images/mcp-configuration.png +0 -0
  12. package/package.json +8 -4
  13. package/src/add-ons.ts +156 -0
  14. package/src/cli.ts +114 -0
  15. package/src/constants.ts +7 -0
  16. package/src/create-app.ts +582 -0
  17. package/src/index.ts +2 -507
  18. package/src/mcp.ts +205 -0
  19. package/src/options.ts +309 -0
  20. package/src/{utils/getPackageManager.ts → package-manager.ts} +1 -0
  21. package/src/types.ts +30 -0
  22. package/templates/react/add-on/clerk/README.md +3 -0
  23. package/templates/react/add-on/clerk/assets/_dot_env.local.append +2 -0
  24. package/templates/react/add-on/clerk/assets/src/integrations/clerk/header-user.tsx +19 -0
  25. package/templates/react/add-on/clerk/assets/src/integrations/clerk/provider.tsx +18 -0
  26. package/templates/react/add-on/clerk/assets/src/routes/demo.clerk.tsx +20 -0
  27. package/templates/react/add-on/clerk/info.json +13 -0
  28. package/templates/react/add-on/clerk/package.json +5 -0
  29. package/templates/react/add-on/convex/README.md +4 -0
  30. package/templates/react/add-on/convex/assets/_dot_cursorrules.append +93 -0
  31. package/templates/react/add-on/convex/assets/_dot_env.local.append +3 -0
  32. package/templates/react/add-on/convex/assets/convex/products.ts +8 -0
  33. package/templates/react/add-on/convex/assets/convex/schema.ts +10 -0
  34. package/templates/react/add-on/convex/assets/src/integrations/convex/provider.tsx +20 -0
  35. package/templates/react/add-on/convex/assets/src/routes/demo.convex.tsx +33 -0
  36. package/templates/react/add-on/convex/info.json +13 -0
  37. package/templates/react/add-on/convex/package.json +6 -0
  38. package/templates/react/add-on/form/assets/src/routes/demo.form.tsx.ejs +62 -0
  39. package/templates/react/add-on/form/info.json +13 -0
  40. package/templates/react/add-on/form/package.json +5 -0
  41. package/templates/react/add-on/module-federation/assets/module-federation.config.js.ejs +31 -0
  42. package/templates/react/add-on/module-federation/assets/src/demo-mf-component.tsx +3 -0
  43. package/templates/react/add-on/module-federation/assets/src/demo-mf-self-contained.tsx +11 -0
  44. package/templates/react/add-on/module-federation/info.json +7 -0
  45. package/templates/react/add-on/module-federation/package.json +5 -0
  46. package/templates/react/add-on/netlify/README.md +11 -0
  47. package/templates/react/add-on/netlify/info.json +7 -0
  48. package/templates/react/add-on/sentry/assets/_dot_cursorrules.append +22 -0
  49. package/templates/react/add-on/sentry/assets/_dot_env.local.append +2 -0
  50. package/templates/react/add-on/sentry/assets/src/app/global-middleware.ts +25 -0
  51. package/templates/react/add-on/sentry/assets/src/routes/demo.sentry.testing.tsx +480 -0
  52. package/templates/react/add-on/sentry/info.json +14 -0
  53. package/templates/react/add-on/sentry/package.json +7 -0
  54. package/templates/react/add-on/shadcn/README.md +7 -0
  55. package/templates/react/add-on/shadcn/assets/_dot_cursorrules.append +7 -0
  56. package/templates/react/add-on/shadcn/info.json +11 -0
  57. package/templates/react/add-on/start/assets/app.config.ts +16 -0
  58. package/templates/react/add-on/start/assets/postcss.config.ts +5 -0
  59. package/templates/react/add-on/start/assets/src/api.ts +6 -0
  60. package/templates/react/add-on/start/assets/src/client.tsx +10 -0
  61. package/templates/react/add-on/start/assets/src/router.tsx.ejs +51 -0
  62. package/templates/react/add-on/start/assets/src/routes/api.demo-names.ts +11 -0
  63. package/templates/react/add-on/start/assets/src/routes/demo.start.api-request.tsx.ejs +33 -0
  64. package/templates/react/add-on/start/assets/src/routes/demo.start.server-funcs.tsx +49 -0
  65. package/templates/react/add-on/start/assets/src/ssr.tsx +12 -0
  66. package/templates/react/add-on/start/info.json +19 -0
  67. package/templates/react/add-on/start/package.json +14 -0
  68. package/templates/react/add-on/store/assets/src/lib/demo-store.ts +13 -0
  69. package/templates/react/add-on/store/assets/src/routes/demo.store.tsx.ejs +75 -0
  70. package/templates/react/add-on/store/info.json +13 -0
  71. package/templates/react/add-on/store/package.json +6 -0
  72. package/templates/react/add-on/tanstack-query/assets/src/integrations/tanstack-query/layout.tsx +5 -0
  73. package/templates/react/add-on/tanstack-query/assets/src/integrations/tanstack-query/provider.tsx +9 -0
  74. package/templates/react/add-on/tanstack-query/assets/src/routes/demo.tanstack-query.tsx.ejs +38 -0
  75. package/templates/react/add-on/tanstack-query/info.json +13 -0
  76. package/templates/react/add-on/tanstack-query/package.json +6 -0
  77. package/templates/{base → react/base}/README.md.ejs +10 -1
  78. package/templates/react/base/src/components/Header.tsx.ejs +25 -0
  79. package/templates/{base/tsconfig.json → react/base/tsconfig.json.ejs} +5 -1
  80. package/templates/react/base/vite.config.js.ejs +24 -0
  81. package/templates/{code-router → react/code-router}/src/main.tsx.ejs +18 -1
  82. package/templates/react/example/tanchat/README.md +37 -0
  83. package/templates/react/example/tanchat/assets/_dot_env.local.append +2 -0
  84. package/templates/react/example/tanchat/assets/src/components/demo.SettingsDialog.tsx +148 -0
  85. package/templates/react/example/tanchat/assets/src/demo.index.css +220 -0
  86. package/templates/react/example/tanchat/assets/src/routes/example.chat.tsx.ejs +375 -0
  87. package/templates/react/example/tanchat/assets/src/store/demo.hooks.ts +21 -0
  88. package/templates/react/example/tanchat/assets/src/store/demo.store.ts +133 -0
  89. package/templates/react/example/tanchat/assets/src/utils/demo.ai.ts +108 -0
  90. package/templates/react/example/tanchat/info.json +15 -0
  91. package/templates/react/example/tanchat/package.json +10 -0
  92. package/templates/{file-router → react/file-router}/src/main.tsx.ejs +1 -1
  93. package/templates/react/file-router/src/routes/__root.tsx.ejs +71 -0
  94. package/templates/solid/add-on/form/assets/src/routes/demo.form.tsx.ejs +148 -0
  95. package/templates/solid/add-on/form/info.json +13 -0
  96. package/templates/solid/add-on/form/package.json +5 -0
  97. package/templates/solid/add-on/module-federation/assets/module-federation.config.js.ejs +27 -0
  98. package/templates/solid/add-on/module-federation/assets/src/demo-mf-component.tsx +3 -0
  99. package/templates/solid/add-on/module-federation/assets/src/demo-mf-self-contained.tsx +9 -0
  100. package/templates/solid/add-on/module-federation/info.json +7 -0
  101. package/templates/solid/add-on/module-federation/package.json +5 -0
  102. package/templates/solid/add-on/sentry/assets/_dot_cursorrules.append +22 -0
  103. package/templates/solid/add-on/sentry/assets/_dot_env.local.append +2 -0
  104. package/templates/solid/add-on/sentry/assets/src/routes/demo.sentry.bad-event-handler.tsx +20 -0
  105. package/templates/solid/add-on/sentry/info.json +13 -0
  106. package/templates/solid/add-on/sentry/package.json +5 -0
  107. package/templates/solid/add-on/solid-ui/README.md +9 -0
  108. package/templates/solid/add-on/solid-ui/assets/src/lib/utils.ts +6 -0
  109. package/templates/solid/add-on/solid-ui/assets/src/styles.css +138 -0
  110. package/templates/solid/add-on/solid-ui/assets/ui.config.json +13 -0
  111. package/templates/solid/add-on/solid-ui/info.json +11 -0
  112. package/templates/solid/add-on/solid-ui/package.json +9 -0
  113. package/templates/solid/add-on/store/assets/src/lib/demo-store.ts +13 -0
  114. package/templates/solid/add-on/store/assets/src/routes/demo.store.tsx.ejs +77 -0
  115. package/templates/solid/add-on/store/info.json +13 -0
  116. package/templates/solid/add-on/store/package.json +6 -0
  117. package/templates/solid/add-on/tanstack-query/assets/src/integrations/tanstack-query/header-user.tsx +5 -0
  118. package/templates/solid/add-on/tanstack-query/assets/src/integrations/tanstack-query/provider.tsx +15 -0
  119. package/templates/solid/add-on/tanstack-query/assets/src/routes/demo.tanstack-query.tsx +30 -0
  120. package/templates/solid/add-on/tanstack-query/info.json +13 -0
  121. package/templates/solid/add-on/tanstack-query/package.json +6 -0
  122. package/templates/solid/base/README.md.ejs +200 -0
  123. package/templates/solid/base/_dot_cursorrules.append +35 -0
  124. package/templates/solid/base/_dot_gitignore +5 -0
  125. package/templates/solid/base/_dot_vscode/settings.json +11 -0
  126. package/templates/solid/base/index.html.ejs +20 -0
  127. package/templates/solid/base/package.json +22 -0
  128. package/templates/solid/base/package.ts.json +5 -0
  129. package/templates/solid/base/package.tw.json +6 -0
  130. package/templates/solid/base/public/favicon.ico +0 -0
  131. package/templates/solid/base/public/logo192.png +0 -0
  132. package/templates/solid/base/public/logo512.png +0 -0
  133. package/templates/solid/base/public/manifest.json +25 -0
  134. package/templates/solid/base/public/robots.txt +3 -0
  135. package/templates/solid/base/src/App.css +0 -0
  136. package/templates/solid/base/src/App.tsx.ejs +47 -0
  137. package/templates/solid/base/src/components/Header.tsx.ejs +26 -0
  138. package/templates/solid/base/src/logo.svg +120 -0
  139. package/templates/solid/base/src/styles.css.ejs +15 -0
  140. package/templates/solid/base/tsconfig.json.ejs +30 -0
  141. package/templates/solid/base/vite.config.js.ejs +22 -0
  142. package/templates/solid/code-router/src/main.tsx.ejs +69 -0
  143. package/templates/solid/file-router/package.fr.json +5 -0
  144. package/templates/solid/file-router/src/main.tsx.ejs +44 -0
  145. package/templates/solid/file-router/src/routes/__root.tsx.ejs +41 -0
  146. package/templates/solid/file-router/src/routes/index.tsx +43 -0
  147. package/templates/base/vite.config.js.ejs +0 -15
  148. package/templates/file-router/src/routes/__root.tsx +0 -11
  149. /package/templates/{base → react/base}/_dot_gitignore +0 -0
  150. /package/templates/{base → react/base}/_dot_vscode/settings.json +0 -0
  151. /package/templates/{base → react/base}/index.html.ejs +0 -0
  152. /package/templates/{base → react/base}/package.json +0 -0
  153. /package/templates/{base → react/base}/package.ts.json +0 -0
  154. /package/templates/{base → react/base}/package.tw.json +0 -0
  155. /package/templates/{base → react/base}/public/favicon.ico +0 -0
  156. /package/templates/{base → react/base}/public/logo192.png +0 -0
  157. /package/templates/{base → react/base}/public/logo512.png +0 -0
  158. /package/templates/{base → react/base}/public/manifest.json +0 -0
  159. /package/templates/{base → react/base}/public/robots.txt +0 -0
  160. /package/templates/{base → react/base}/src/App.css +0 -0
  161. /package/templates/{base → react/base}/src/App.test.tsx.ejs +0 -0
  162. /package/templates/{base → react/base}/src/App.tsx.ejs +0 -0
  163. /package/templates/{base → react/base}/src/logo.svg +0 -0
  164. /package/templates/{base → react/base}/src/reportWebVitals.ts.ejs +0 -0
  165. /package/templates/{base → react/base}/src/styles.css.ejs +0 -0
  166. /package/templates/{file-router → react/file-router}/package.fr.json +0 -0
@@ -0,0 +1,582 @@
1
+ #!/usr/bin/env node
2
+
3
+ import {
4
+ appendFile,
5
+ copyFile,
6
+ mkdir,
7
+ readFile,
8
+ writeFile,
9
+ } from 'node:fs/promises'
10
+ import { existsSync, readdirSync, statSync } from 'node:fs'
11
+ import { basename, dirname, resolve } from 'node:path'
12
+ import { fileURLToPath } from 'node:url'
13
+ import { log, outro, spinner } from '@clack/prompts'
14
+ import { execa } from 'execa'
15
+ import { render } from 'ejs'
16
+ import { format } from 'prettier'
17
+ import chalk from 'chalk'
18
+
19
+ import { CODE_ROUTER, FILE_ROUTER } from './constants.js'
20
+
21
+ import type { Options } from './types.js'
22
+
23
+ function sortObject(obj: Record<string, string>): Record<string, string> {
24
+ return Object.keys(obj)
25
+ .sort()
26
+ .reduce<Record<string, string>>((acc, key) => {
27
+ acc[key] = obj[key]
28
+ return acc
29
+ }, {})
30
+ }
31
+
32
+ function createCopyFiles(targetDir: string) {
33
+ return async function copyFiles(templateDir: string, files: Array<string>) {
34
+ for (const file of files) {
35
+ const targetFileName = file.replace('.tw', '')
36
+ await copyFile(
37
+ resolve(templateDir, file),
38
+ resolve(targetDir, targetFileName),
39
+ )
40
+ }
41
+ }
42
+ }
43
+
44
+ function jsSafeName(name: string) {
45
+ return name
46
+ .split(/[^a-zA-Z0-9]/)
47
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
48
+ .join('')
49
+ }
50
+
51
+ function createTemplateFile(
52
+ projectName: string,
53
+ options: Required<Options>,
54
+ targetDir: string,
55
+ ) {
56
+ return async function templateFile(
57
+ templateDir: string,
58
+ file: string,
59
+ targetFileName?: string,
60
+ extraTemplateValues?: Record<string, any>,
61
+ ) {
62
+ const templateValues = {
63
+ packageManager: options.packageManager,
64
+ projectName: projectName,
65
+ typescript: options.typescript,
66
+ tailwind: options.tailwind,
67
+ js: options.typescript ? 'ts' : 'js',
68
+ jsx: options.typescript ? 'tsx' : 'jsx',
69
+ fileRouter: options.mode === FILE_ROUTER,
70
+ codeRouter: options.mode === CODE_ROUTER,
71
+ addOnEnabled: options.chosenAddOns.reduce<Record<string, boolean>>(
72
+ (acc, addOn) => {
73
+ acc[addOn.id] = true
74
+ return acc
75
+ },
76
+ {},
77
+ ),
78
+ addOns: options.chosenAddOns,
79
+ ...extraTemplateValues,
80
+ }
81
+
82
+ const template = await readFile(resolve(templateDir, file), 'utf-8')
83
+ let content = ''
84
+ try {
85
+ content = render(template, templateValues)
86
+ } catch (error) {
87
+ console.error(chalk.red(`EJS error in file ${file}`))
88
+ console.error(error)
89
+ process.exit(1)
90
+ }
91
+ const target = targetFileName ?? file.replace('.ejs', '')
92
+
93
+ if (target.endsWith('.ts') || target.endsWith('.tsx')) {
94
+ content = await format(content, {
95
+ semi: false,
96
+ singleQuote: true,
97
+ trailingComma: 'all',
98
+ parser: 'typescript',
99
+ })
100
+ }
101
+
102
+ await mkdir(dirname(resolve(targetDir, target)), {
103
+ recursive: true,
104
+ })
105
+
106
+ await writeFile(resolve(targetDir, target), content)
107
+ }
108
+ }
109
+
110
+ async function createPackageJSON(
111
+ projectName: string,
112
+ options: Required<Options>,
113
+ templateDir: string,
114
+ routerDir: string,
115
+ targetDir: string,
116
+ addOns: Array<{
117
+ dependencies?: Record<string, string>
118
+ devDependencies?: Record<string, string>
119
+ scripts?: Record<string, string>
120
+ }>,
121
+ ) {
122
+ let packageJSON = JSON.parse(
123
+ await readFile(resolve(templateDir, 'package.json'), 'utf8'),
124
+ )
125
+ packageJSON.name = projectName
126
+ if (options.typescript) {
127
+ const tsPackageJSON = JSON.parse(
128
+ await readFile(resolve(templateDir, 'package.ts.json'), 'utf8'),
129
+ )
130
+ packageJSON = {
131
+ ...packageJSON,
132
+ devDependencies: {
133
+ ...packageJSON.devDependencies,
134
+ ...tsPackageJSON.devDependencies,
135
+ },
136
+ }
137
+ }
138
+ if (options.tailwind) {
139
+ const twPackageJSON = JSON.parse(
140
+ await readFile(resolve(templateDir, 'package.tw.json'), 'utf8'),
141
+ )
142
+ packageJSON = {
143
+ ...packageJSON,
144
+ dependencies: {
145
+ ...packageJSON.dependencies,
146
+ ...twPackageJSON.dependencies,
147
+ },
148
+ }
149
+ }
150
+ if (options.mode === FILE_ROUTER) {
151
+ const frPackageJSON = JSON.parse(
152
+ await readFile(resolve(routerDir, 'package.fr.json'), 'utf8'),
153
+ )
154
+ packageJSON = {
155
+ ...packageJSON,
156
+ dependencies: {
157
+ ...packageJSON.dependencies,
158
+ ...frPackageJSON.dependencies,
159
+ },
160
+ }
161
+ }
162
+
163
+ for (const addOn of addOns) {
164
+ packageJSON = {
165
+ ...packageJSON,
166
+ dependencies: {
167
+ ...packageJSON.dependencies,
168
+ ...addOn.dependencies,
169
+ },
170
+ devDependencies: {
171
+ ...packageJSON.devDependencies,
172
+ ...addOn.devDependencies,
173
+ },
174
+ scripts: {
175
+ ...packageJSON.scripts,
176
+ ...addOn.scripts,
177
+ },
178
+ }
179
+ }
180
+
181
+ packageJSON.dependencies = sortObject(
182
+ packageJSON.dependencies as Record<string, string>,
183
+ )
184
+ packageJSON.devDependencies = sortObject(
185
+ packageJSON.devDependencies as Record<string, string>,
186
+ )
187
+
188
+ await writeFile(
189
+ resolve(targetDir, 'package.json'),
190
+ JSON.stringify(packageJSON, null, 2),
191
+ )
192
+ }
193
+
194
+ async function copyFilesRecursively(
195
+ source: string,
196
+ target: string,
197
+ copyFile: (source: string, target: string) => Promise<void>,
198
+ templateFile: (file: string, targetFileName?: string) => Promise<void>,
199
+ ) {
200
+ const sourceStat = statSync(source)
201
+ if (sourceStat.isDirectory()) {
202
+ const files = readdirSync(source)
203
+ for (const file of files) {
204
+ const sourceChild = resolve(source, file)
205
+ const targetChild = resolve(target, file)
206
+ await copyFilesRecursively(
207
+ sourceChild,
208
+ targetChild,
209
+ copyFile,
210
+ templateFile,
211
+ )
212
+ }
213
+ } else {
214
+ let targetFile = basename(target).replace(/_dot_/, '.')
215
+ let isTemplate = false
216
+ if (targetFile.endsWith('.ejs')) {
217
+ targetFile = targetFile.replace('.ejs', '')
218
+ isTemplate = true
219
+ }
220
+ let isAppend = false
221
+ if (targetFile.endsWith('.append')) {
222
+ targetFile = targetFile.replace('.append', '')
223
+ isAppend = true
224
+ }
225
+
226
+ const targetPath = resolve(dirname(target), targetFile)
227
+
228
+ await mkdir(dirname(targetPath), {
229
+ recursive: true,
230
+ })
231
+
232
+ if (isTemplate) {
233
+ await templateFile(source, targetPath)
234
+ } else {
235
+ if (isAppend) {
236
+ await appendFile(targetPath, (await readFile(source)).toString())
237
+ } else {
238
+ await copyFile(source, targetPath)
239
+ }
240
+ }
241
+ }
242
+ }
243
+
244
+ export async function createApp(
245
+ options: Required<Options>,
246
+ {
247
+ silent = false,
248
+ }: {
249
+ silent?: boolean
250
+ } = {},
251
+ ) {
252
+ const templateDirBase = fileURLToPath(
253
+ new URL(`../templates/${options.framework}/base`, import.meta.url),
254
+ )
255
+ const templateDirRouter = fileURLToPath(
256
+ new URL(
257
+ `../templates/${options.framework}/${options.mode}`,
258
+ import.meta.url,
259
+ ),
260
+ )
261
+ const targetDir = resolve(process.cwd(), options.projectName)
262
+
263
+ if (existsSync(targetDir)) {
264
+ if (!silent) {
265
+ log.error(`Directory "${options.projectName}" already exists`)
266
+ }
267
+ return
268
+ }
269
+
270
+ const copyFiles = createCopyFiles(targetDir)
271
+ const templateFile = createTemplateFile(
272
+ options.projectName,
273
+ options,
274
+ targetDir,
275
+ )
276
+
277
+ const isAddOnEnabled = (id: string) =>
278
+ options.chosenAddOns.find((a) => a.id === id)
279
+
280
+ // Make the root directory
281
+ await mkdir(targetDir, { recursive: true })
282
+
283
+ // Setup the .vscode directory
284
+ await mkdir(resolve(targetDir, '.vscode'), { recursive: true })
285
+ await copyFile(
286
+ resolve(templateDirBase, '_dot_vscode/settings.json'),
287
+ resolve(targetDir, '.vscode/settings.json'),
288
+ )
289
+
290
+ // Fill the public directory
291
+ await mkdir(resolve(targetDir, 'public'), { recursive: true })
292
+ copyFiles(templateDirBase, [
293
+ './public/robots.txt',
294
+ './public/favicon.ico',
295
+ './public/manifest.json',
296
+ './public/logo192.png',
297
+ './public/logo512.png',
298
+ ])
299
+
300
+ // Make the src directory
301
+ await mkdir(resolve(targetDir, 'src'), { recursive: true })
302
+ if (options.mode === FILE_ROUTER) {
303
+ await mkdir(resolve(targetDir, 'src/routes'), { recursive: true })
304
+ await mkdir(resolve(targetDir, 'src/components'), { recursive: true })
305
+ }
306
+
307
+ // Check for a .cursorrules file
308
+ if (existsSync(resolve(templateDirBase, '.cursorrules'))) {
309
+ await copyFile(
310
+ resolve(templateDirBase, '.cursorrules'),
311
+ resolve(targetDir, '.cursorrules'),
312
+ )
313
+ }
314
+
315
+ // Copy in Vite and Tailwind config and CSS
316
+ if (!options.tailwind) {
317
+ await copyFiles(templateDirBase, ['./src/App.css'])
318
+ }
319
+ await templateFile(templateDirBase, './vite.config.js.ejs')
320
+ await templateFile(templateDirBase, './src/styles.css.ejs')
321
+
322
+ copyFiles(templateDirBase, ['./src/logo.svg'])
323
+
324
+ // Setup the main, reportWebVitals and index.html files
325
+ if (!isAddOnEnabled('start') && options.framework === 'react') {
326
+ if (options.typescript) {
327
+ await templateFile(templateDirBase, './src/reportWebVitals.ts.ejs')
328
+ } else {
329
+ await templateFile(
330
+ templateDirBase,
331
+ './src/reportWebVitals.ts.ejs',
332
+ './src/reportWebVitals.js',
333
+ )
334
+ }
335
+ }
336
+ if (!isAddOnEnabled('start')) {
337
+ await templateFile(templateDirBase, './index.html.ejs')
338
+ }
339
+
340
+ // Setup tsconfig
341
+ if (options.typescript) {
342
+ await templateFile(
343
+ templateDirBase,
344
+ './tsconfig.json.ejs',
345
+ './tsconfig.json',
346
+ )
347
+ }
348
+
349
+ // Setup the package.json file, optionally with typescript and tailwind
350
+ await createPackageJSON(
351
+ options.projectName,
352
+ options,
353
+ templateDirBase,
354
+ templateDirRouter,
355
+ targetDir,
356
+ options.chosenAddOns.map((addOn) => addOn.packageAdditions),
357
+ )
358
+
359
+ // Copy all the asset files from the addons
360
+ const s = silent ? null : spinner()
361
+ for (const phase of ['setup', 'add-on', 'example']) {
362
+ for (const addOn of options.chosenAddOns.filter(
363
+ (addOn) => addOn.phase === phase,
364
+ )) {
365
+ s?.start(`Setting up ${addOn.name}...`)
366
+ const addOnDir = resolve(addOn.directory, 'assets')
367
+ if (existsSync(addOnDir)) {
368
+ await copyFilesRecursively(
369
+ addOnDir,
370
+ targetDir,
371
+ copyFile,
372
+ async (file: string, targetFileName?: string) =>
373
+ templateFile(addOnDir, file, targetFileName),
374
+ )
375
+ }
376
+
377
+ if (addOn.command) {
378
+ await execa(addOn.command.command, addOn.command.args || [], {
379
+ cwd: targetDir,
380
+ })
381
+ }
382
+ s?.stop(`${addOn.name} setup complete`)
383
+ }
384
+ }
385
+
386
+ if (isAddOnEnabled('shadcn')) {
387
+ const shadcnComponents = new Set<string>()
388
+ for (const addOn of options.chosenAddOns) {
389
+ if (addOn.shadcnComponents) {
390
+ for (const component of addOn.shadcnComponents) {
391
+ shadcnComponents.add(component)
392
+ }
393
+ }
394
+ }
395
+
396
+ if (shadcnComponents.size > 0) {
397
+ s?.start(
398
+ `Installing shadcn components (${Array.from(shadcnComponents).join(', ')})...`,
399
+ )
400
+ await execa('npx', ['shadcn@canary', 'add', ...shadcnComponents], {
401
+ cwd: targetDir,
402
+ })
403
+ s?.stop(`Installed shadcn components`)
404
+ }
405
+ }
406
+
407
+ const integrations: Array<{
408
+ type: 'layout' | 'provider' | 'header-user'
409
+ name: string
410
+ path: string
411
+ }> = []
412
+ if (existsSync(resolve(targetDir, 'src/integrations'))) {
413
+ for (const integration of readdirSync(
414
+ resolve(targetDir, 'src/integrations'),
415
+ )) {
416
+ const integrationName = jsSafeName(integration)
417
+ if (
418
+ existsSync(
419
+ resolve(targetDir, 'src/integrations', integration, 'layout.tsx'),
420
+ )
421
+ ) {
422
+ integrations.push({
423
+ type: 'layout',
424
+ name: `${integrationName}Layout`,
425
+ path: `integrations/${integration}/layout`,
426
+ })
427
+ }
428
+ if (
429
+ existsSync(
430
+ resolve(targetDir, 'src/integrations', integration, 'provider.tsx'),
431
+ )
432
+ ) {
433
+ integrations.push({
434
+ type: 'provider',
435
+ name: `${integrationName}Provider`,
436
+ path: `integrations/${integration}/provider`,
437
+ })
438
+ }
439
+ if (
440
+ existsSync(
441
+ resolve(
442
+ targetDir,
443
+ 'src/integrations',
444
+ integration,
445
+ 'header-user.tsx',
446
+ ),
447
+ )
448
+ ) {
449
+ integrations.push({
450
+ type: 'header-user',
451
+ name: `${integrationName}Header`,
452
+ path: `integrations/${integration}/header-user`,
453
+ })
454
+ }
455
+ }
456
+ }
457
+
458
+ const routes: Array<{
459
+ path: string
460
+ name: string
461
+ }> = []
462
+ if (existsSync(resolve(targetDir, 'src/routes'))) {
463
+ for (const file of readdirSync(resolve(targetDir, 'src/routes'))) {
464
+ const name = file.replace(/\.tsx?|\.jsx?/, '')
465
+ const safeRouteName = jsSafeName(name)
466
+ routes.push({
467
+ path: `./routes/${name}`,
468
+ name: safeRouteName,
469
+ })
470
+ }
471
+ }
472
+
473
+ // Create the main entry point
474
+ if (!isAddOnEnabled('start')) {
475
+ if (options.typescript) {
476
+ await templateFile(
477
+ templateDirRouter,
478
+ './src/main.tsx.ejs',
479
+ './src/main.tsx',
480
+ {
481
+ routes,
482
+ integrations,
483
+ },
484
+ )
485
+ } else {
486
+ await templateFile(
487
+ templateDirRouter,
488
+ './src/main.tsx.ejs',
489
+ './src/main.jsx',
490
+ {
491
+ routes,
492
+ integrations,
493
+ },
494
+ )
495
+ }
496
+ }
497
+
498
+ // Setup the app component. There are four variations, typescript/javascript and tailwind/non-tailwind.
499
+ if (options.mode === FILE_ROUTER) {
500
+ await templateFile(
501
+ templateDirRouter,
502
+ './src/routes/__root.tsx.ejs',
503
+ './src/routes/__root.tsx',
504
+ {
505
+ integrations,
506
+ },
507
+ )
508
+ await templateFile(
509
+ templateDirBase,
510
+ './src/App.tsx.ejs',
511
+ './src/routes/index.tsx',
512
+ )
513
+ } else {
514
+ await templateFile(
515
+ templateDirBase,
516
+ './src/App.tsx.ejs',
517
+ options.typescript ? undefined : './src/App.jsx',
518
+ )
519
+ if (options.framework === 'react') {
520
+ await templateFile(
521
+ templateDirBase,
522
+ './src/App.test.tsx.ejs',
523
+ options.typescript ? undefined : './src/App.test.jsx',
524
+ )
525
+ }
526
+ }
527
+
528
+ if (routes.length > 0) {
529
+ await templateFile(
530
+ templateDirBase,
531
+ './src/components/Header.tsx.ejs',
532
+ './src/components/Header.tsx',
533
+ {
534
+ integrations,
535
+ },
536
+ )
537
+ }
538
+
539
+ const warnings: Array<string> = []
540
+ for (const addOn of options.chosenAddOns) {
541
+ if (addOn.warning) {
542
+ warnings.push(addOn.warning)
543
+ }
544
+ }
545
+
546
+ // Add .gitignore
547
+ await copyFile(
548
+ resolve(templateDirBase, '_dot_gitignore'),
549
+ resolve(targetDir, '.gitignore'),
550
+ )
551
+
552
+ // Create the README.md
553
+ await templateFile(templateDirBase, 'README.md.ejs')
554
+
555
+ // Install dependencies
556
+ s?.start(`Installing dependencies via ${options.packageManager}...`)
557
+ await execa(options.packageManager, ['install'], { cwd: targetDir })
558
+ s?.stop(`Installed dependencies`)
559
+
560
+ if (warnings.length > 0) {
561
+ if (!silent) {
562
+ log.warn(chalk.red(warnings.join('\n')))
563
+ }
564
+ }
565
+
566
+ if (options.git) {
567
+ s?.start(`Initializing git repository...`)
568
+ await execa('git', ['init'], { cwd: targetDir })
569
+ s?.stop(`Initialized git repository`)
570
+ }
571
+
572
+ if (!silent) {
573
+ outro(`Created your new TanStack app in '${basename(targetDir)}'.
574
+
575
+ Use the following commands to start your app:
576
+ % cd ${options.projectName}
577
+ % ${options.packageManager === 'deno' ? 'deno start' : options.packageManager} ${isAddOnEnabled('start') ? 'dev' : 'start'}
578
+
579
+ Please read README.md for more information on testing, styling, adding routes, react-query, etc.
580
+ `)
581
+ }
582
+ }