@tanstack/cta-engine 0.10.0-alpha.10

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 (272) hide show
  1. package/LICENSE +21 -0
  2. package/dist/add-ons.js +109 -0
  3. package/dist/add.js +125 -0
  4. package/dist/cli.js +119 -0
  5. package/dist/config-file.js +23 -0
  6. package/dist/constants.js +5 -0
  7. package/dist/create-app.js +491 -0
  8. package/dist/custom-add-on.js +253 -0
  9. package/dist/environment.js +119 -0
  10. package/dist/index.js +1 -0
  11. package/dist/mcp.js +213 -0
  12. package/dist/options.js +328 -0
  13. package/dist/package-manager.js +30 -0
  14. package/dist/templates.js +6 -0
  15. package/dist/toolchain.js +6 -0
  16. package/dist/types/add-ons.d.ts +5 -0
  17. package/dist/types/add.d.ts +3 -0
  18. package/dist/types/cli.d.ts +5 -0
  19. package/dist/types/config-file.d.ts +7 -0
  20. package/dist/types/constants.d.ts +6 -0
  21. package/dist/types/create-app.d.ts +7 -0
  22. package/dist/types/custom-add-on.d.ts +3 -0
  23. package/dist/types/environment.d.ts +12 -0
  24. package/dist/types/index.d.ts +1 -0
  25. package/dist/types/mcp.d.ts +1 -0
  26. package/dist/types/options.d.ts +6 -0
  27. package/dist/types/package-manager.d.ts +6 -0
  28. package/dist/types/templates.d.ts +1 -0
  29. package/dist/types/toolchain.d.ts +3 -0
  30. package/dist/types/types.d.ts +107 -0
  31. package/dist/types/utils.d.ts +1 -0
  32. package/dist/types.js +1 -0
  33. package/dist/utils.js +8 -0
  34. package/package.json +49 -0
  35. package/src/add-ons.ts +145 -0
  36. package/src/add.ts +186 -0
  37. package/src/cli.ts +181 -0
  38. package/src/config-file.ts +45 -0
  39. package/src/constants.ts +9 -0
  40. package/src/create-app.ts +793 -0
  41. package/src/custom-add-on.ts +326 -0
  42. package/src/environment.ts +144 -0
  43. package/src/index.ts +1 -0
  44. package/src/mcp.ts +254 -0
  45. package/src/options.ts +392 -0
  46. package/src/package-manager.ts +46 -0
  47. package/src/templates.ts +7 -0
  48. package/src/toolchain.ts +7 -0
  49. package/src/types.ts +119 -0
  50. package/src/utils.ts +10 -0
  51. package/templates/react/add-on/clerk/README.md +3 -0
  52. package/templates/react/add-on/clerk/assets/_dot_env.local.append +2 -0
  53. package/templates/react/add-on/clerk/assets/src/integrations/clerk/header-user.tsx +19 -0
  54. package/templates/react/add-on/clerk/assets/src/integrations/clerk/provider.tsx +18 -0
  55. package/templates/react/add-on/clerk/assets/src/routes/demo.clerk.tsx +20 -0
  56. package/templates/react/add-on/clerk/info.json +13 -0
  57. package/templates/react/add-on/clerk/package.json +5 -0
  58. package/templates/react/add-on/convex/README.md +4 -0
  59. package/templates/react/add-on/convex/assets/_dot_cursorrules.append +93 -0
  60. package/templates/react/add-on/convex/assets/_dot_env.local.append +3 -0
  61. package/templates/react/add-on/convex/assets/convex/products.ts +8 -0
  62. package/templates/react/add-on/convex/assets/convex/schema.ts +10 -0
  63. package/templates/react/add-on/convex/assets/src/integrations/convex/provider.tsx +20 -0
  64. package/templates/react/add-on/convex/assets/src/routes/demo.convex.tsx +33 -0
  65. package/templates/react/add-on/convex/info.json +13 -0
  66. package/templates/react/add-on/convex/package.json +6 -0
  67. package/templates/react/add-on/form/assets/src/components/demo.FormComponents.tsx.ejs +300 -0
  68. package/templates/react/add-on/form/assets/src/hooks/demo.form-context.ts +4 -0
  69. package/templates/react/add-on/form/assets/src/hooks/demo.form.ts +22 -0
  70. package/templates/react/add-on/form/assets/src/routes/demo.form.address.tsx.ejs +213 -0
  71. package/templates/react/add-on/form/assets/src/routes/demo.form.simple.tsx.ejs +77 -0
  72. package/templates/react/add-on/form/info.json +26 -0
  73. package/templates/react/add-on/form/package.json +6 -0
  74. package/templates/react/add-on/module-federation/assets/module-federation.config.js.ejs +31 -0
  75. package/templates/react/add-on/module-federation/assets/src/demo-mf-component.tsx +3 -0
  76. package/templates/react/add-on/module-federation/assets/src/demo-mf-self-contained.tsx +11 -0
  77. package/templates/react/add-on/module-federation/info.json +7 -0
  78. package/templates/react/add-on/module-federation/package.json +5 -0
  79. package/templates/react/add-on/netlify/README.md +11 -0
  80. package/templates/react/add-on/netlify/info.json +7 -0
  81. package/templates/react/add-on/sentry/assets/_dot_cursorrules.append +22 -0
  82. package/templates/react/add-on/sentry/assets/_dot_env.local.append +2 -0
  83. package/templates/react/add-on/sentry/assets/src/app/global-middleware.ts +25 -0
  84. package/templates/react/add-on/sentry/assets/src/routes/demo.sentry.testing.tsx +480 -0
  85. package/templates/react/add-on/sentry/info.json +14 -0
  86. package/templates/react/add-on/sentry/package.json +7 -0
  87. package/templates/react/add-on/shadcn/README.md +7 -0
  88. package/templates/react/add-on/shadcn/assets/_dot_cursorrules.append +7 -0
  89. package/templates/react/add-on/shadcn/assets/components.json +21 -0
  90. package/templates/react/add-on/shadcn/assets/src/lib/utils.ts +6 -0
  91. package/templates/react/add-on/shadcn/assets/src/styles.css +138 -0
  92. package/templates/react/add-on/shadcn/info.json +7 -0
  93. package/templates/react/add-on/shadcn/package.json +9 -0
  94. package/templates/react/add-on/start/assets/_dot_gitignore.append +2 -0
  95. package/templates/react/add-on/start/assets/app.config.ts.ejs +19 -0
  96. package/templates/react/add-on/start/assets/src/api.ts +6 -0
  97. package/templates/react/add-on/start/assets/src/client.tsx +8 -0
  98. package/templates/react/add-on/start/assets/src/router.tsx.ejs +77 -0
  99. package/templates/react/add-on/start/assets/src/routes/api.demo-names.ts +11 -0
  100. package/templates/react/add-on/start/assets/src/routes/demo.start.api-request.tsx.ejs +33 -0
  101. package/templates/react/add-on/start/assets/src/routes/demo.start.server-funcs.tsx +50 -0
  102. package/templates/react/add-on/start/assets/src/ssr.tsx +12 -0
  103. package/templates/react/add-on/start/info.json +18 -0
  104. package/templates/react/add-on/start/package.json +13 -0
  105. package/templates/react/add-on/store/assets/src/lib/demo-store.ts +13 -0
  106. package/templates/react/add-on/store/assets/src/routes/demo.store.tsx.ejs +75 -0
  107. package/templates/react/add-on/store/info.json +13 -0
  108. package/templates/react/add-on/store/package.json +6 -0
  109. package/templates/react/add-on/tRPC/assets/src/integrations/trpc/init.ts +9 -0
  110. package/templates/react/add-on/tRPC/assets/src/integrations/trpc/react.ts +4 -0
  111. package/templates/react/add-on/tRPC/assets/src/integrations/trpc/router.ts +18 -0
  112. package/templates/react/add-on/tRPC/assets/src/routes/api.trpc.$.tsx +16 -0
  113. package/templates/react/add-on/tRPC/info.json +9 -0
  114. package/templates/react/add-on/tRPC/package.json +9 -0
  115. package/templates/react/add-on/table/assets/src/data/demo-table-data.ts +50 -0
  116. package/templates/react/add-on/table/assets/src/routes/demo.table.tsx.ejs +373 -0
  117. package/templates/react/add-on/table/info.json +13 -0
  118. package/templates/react/add-on/table/package.json +7 -0
  119. package/templates/react/add-on/tanstack-query/assets/src/integrations/tanstack-query/layout.tsx +5 -0
  120. package/templates/react/add-on/tanstack-query/assets/src/integrations/tanstack-query/root-provider.tsx.ejs +70 -0
  121. package/templates/react/add-on/tanstack-query/assets/src/routes/demo.tanstack-query.tsx.ejs +53 -0
  122. package/templates/react/add-on/tanstack-query/info.json +13 -0
  123. package/templates/react/add-on/tanstack-query/package.json +6 -0
  124. package/templates/react/base/README.md.ejs +558 -0
  125. package/templates/react/base/_dot_gitignore +5 -0
  126. package/templates/react/base/_dot_vscode/settings.biome.json +38 -0
  127. package/templates/react/base/_dot_vscode/settings.json +11 -0
  128. package/templates/react/base/index.html.ejs +20 -0
  129. package/templates/react/base/package.biome.json +10 -0
  130. package/templates/react/base/package.eslintprettier.json +11 -0
  131. package/templates/react/base/package.json +29 -0
  132. package/templates/react/base/package.ts.json +7 -0
  133. package/templates/react/base/package.tw.json +6 -0
  134. package/templates/react/base/public/favicon.ico +0 -0
  135. package/templates/react/base/public/logo192.png +0 -0
  136. package/templates/react/base/public/logo512.png +0 -0
  137. package/templates/react/base/public/manifest.json +25 -0
  138. package/templates/react/base/public/robots.txt +3 -0
  139. package/templates/react/base/src/App.css +38 -0
  140. package/templates/react/base/src/App.test.tsx.ejs +10 -0
  141. package/templates/react/base/src/App.tsx.ejs +74 -0
  142. package/templates/react/base/src/components/Header.tsx.ejs +27 -0
  143. package/templates/react/base/src/logo.svg +44 -0
  144. package/templates/react/base/src/reportWebVitals.ts.ejs +28 -0
  145. package/templates/react/base/src/styles.css.ejs +15 -0
  146. package/templates/react/base/toolchain/.prettierignore +3 -0
  147. package/templates/react/base/toolchain/biome.json +31 -0
  148. package/templates/react/base/toolchain/eslint.config.js +5 -0
  149. package/templates/react/base/toolchain/prettier.config.js +10 -0
  150. package/templates/react/base/tsconfig.json.ejs +29 -0
  151. package/templates/react/base/vite.config.js.ejs +23 -0
  152. package/templates/react/code-router/src/main.tsx.ejs +92 -0
  153. package/templates/react/example/tanchat/README.md +37 -0
  154. package/templates/react/example/tanchat/assets/_dot_env.local.append +2 -0
  155. package/templates/react/example/tanchat/assets/public/example-guitar-flowers.jpg +0 -0
  156. package/templates/react/example/tanchat/assets/public/example-guitar-motherboard.jpg +0 -0
  157. package/templates/react/example/tanchat/assets/public/example-guitar-racing.jpg +0 -0
  158. package/templates/react/example/tanchat/assets/public/example-guitar-steamer-trunk.jpg +0 -0
  159. package/templates/react/example/tanchat/assets/public/example-guitar-superhero.jpg +0 -0
  160. package/templates/react/example/tanchat/assets/public/example-guitar-traveling.jpg +0 -0
  161. package/templates/react/example/tanchat/assets/public/example-guitar-video-games.jpg +0 -0
  162. package/templates/react/example/tanchat/assets/src/components/example-AIAssistant.tsx +173 -0
  163. package/templates/react/example/tanchat/assets/src/components/example-GuitarRecommendation.tsx +47 -0
  164. package/templates/react/example/tanchat/assets/src/data/example-guitars.ts +83 -0
  165. package/templates/react/example/tanchat/assets/src/demo.index.css +220 -0
  166. package/templates/react/example/tanchat/assets/src/integrations/tanchat/header-user.tsx +5 -0
  167. package/templates/react/example/tanchat/assets/src/routes/example.chat.tsx +159 -0
  168. package/templates/react/example/tanchat/assets/src/routes/example.guitars/$guitarId.tsx +50 -0
  169. package/templates/react/example/tanchat/assets/src/routes/example.guitars/index.tsx +54 -0
  170. package/templates/react/example/tanchat/assets/src/store/example-assistant.ts +3 -0
  171. package/templates/react/example/tanchat/assets/src/utils/demo.ai.ts +62 -0
  172. package/templates/react/example/tanchat/assets/src/utils/demo.tools.ts +47 -0
  173. package/templates/react/example/tanchat/info.json +19 -0
  174. package/templates/react/example/tanchat/package.json +15 -0
  175. package/templates/react/file-router/package.fr.json +5 -0
  176. package/templates/react/file-router/src/main.tsx.ejs +55 -0
  177. package/templates/react/file-router/src/routes/__root.tsx.ejs +82 -0
  178. package/templates/solid/add-on/form/assets/src/routes/demo.form.tsx.ejs +352 -0
  179. package/templates/solid/add-on/form/info.json +13 -0
  180. package/templates/solid/add-on/form/package.json +5 -0
  181. package/templates/solid/add-on/module-federation/assets/module-federation.config.js.ejs +27 -0
  182. package/templates/solid/add-on/module-federation/assets/src/demo-mf-component.tsx +3 -0
  183. package/templates/solid/add-on/module-federation/assets/src/demo-mf-self-contained.tsx +9 -0
  184. package/templates/solid/add-on/module-federation/info.json +7 -0
  185. package/templates/solid/add-on/module-federation/package.json +5 -0
  186. package/templates/solid/add-on/sentry/assets/_dot_cursorrules.append +22 -0
  187. package/templates/solid/add-on/sentry/assets/_dot_env.local.append +2 -0
  188. package/templates/solid/add-on/sentry/assets/src/routes/demo.sentry.bad-event-handler.tsx +20 -0
  189. package/templates/solid/add-on/sentry/info.json +13 -0
  190. package/templates/solid/add-on/sentry/package.json +5 -0
  191. package/templates/solid/add-on/solid-ui/README.md +9 -0
  192. package/templates/solid/add-on/solid-ui/assets/src/lib/utils.ts +6 -0
  193. package/templates/solid/add-on/solid-ui/assets/src/styles.css +138 -0
  194. package/templates/solid/add-on/solid-ui/assets/ui.config.json +13 -0
  195. package/templates/solid/add-on/solid-ui/info.json +11 -0
  196. package/templates/solid/add-on/solid-ui/package.json +9 -0
  197. package/templates/solid/add-on/start/assets/app.config.ts +16 -0
  198. package/templates/solid/add-on/start/assets/src/api.ts +6 -0
  199. package/templates/solid/add-on/start/assets/src/client.tsx +7 -0
  200. package/templates/solid/add-on/start/assets/src/router.tsx.ejs +24 -0
  201. package/templates/solid/add-on/start/assets/src/routes/demo.start.server-funcs.tsx +49 -0
  202. package/templates/solid/add-on/start/assets/src/ssr.tsx +12 -0
  203. package/templates/solid/add-on/start/info.json +14 -0
  204. package/templates/solid/add-on/start/package.json +12 -0
  205. package/templates/solid/add-on/store/assets/src/lib/demo-store.ts +13 -0
  206. package/templates/solid/add-on/store/assets/src/routes/demo.store.tsx.ejs +77 -0
  207. package/templates/solid/add-on/store/info.json +13 -0
  208. package/templates/solid/add-on/store/package.json +6 -0
  209. package/templates/solid/add-on/tanstack-query/assets/src/integrations/tanstack-query/header-user.tsx +5 -0
  210. package/templates/solid/add-on/tanstack-query/assets/src/integrations/tanstack-query/provider.tsx +15 -0
  211. package/templates/solid/add-on/tanstack-query/assets/src/routes/demo.tanstack-query.tsx +30 -0
  212. package/templates/solid/add-on/tanstack-query/info.json +13 -0
  213. package/templates/solid/add-on/tanstack-query/package.json +6 -0
  214. package/templates/solid/base/README.md.ejs +215 -0
  215. package/templates/solid/base/_dot_cursorrules.append +35 -0
  216. package/templates/solid/base/_dot_gitignore +5 -0
  217. package/templates/solid/base/_dot_vscode/settings.biome.json +38 -0
  218. package/templates/solid/base/_dot_vscode/settings.json +11 -0
  219. package/templates/solid/base/index.html.ejs +20 -0
  220. package/templates/solid/base/package.biome.json +10 -0
  221. package/templates/solid/base/package.eslintprettier.json +11 -0
  222. package/templates/solid/base/package.json +22 -0
  223. package/templates/solid/base/package.ts.json +5 -0
  224. package/templates/solid/base/package.tw.json +6 -0
  225. package/templates/solid/base/public/favicon.ico +0 -0
  226. package/templates/solid/base/public/logo192.png +0 -0
  227. package/templates/solid/base/public/logo512.png +0 -0
  228. package/templates/solid/base/public/manifest.json +25 -0
  229. package/templates/solid/base/public/robots.txt +3 -0
  230. package/templates/solid/base/src/App.css +0 -0
  231. package/templates/solid/base/src/App.tsx.ejs +47 -0
  232. package/templates/solid/base/src/components/Header.tsx.ejs +26 -0
  233. package/templates/solid/base/src/logo.svg +120 -0
  234. package/templates/solid/base/src/styles.css.ejs +15 -0
  235. package/templates/solid/base/toolchain/.prettierignore +3 -0
  236. package/templates/solid/base/toolchain/biome.json +31 -0
  237. package/templates/solid/base/toolchain/eslint.config.js +5 -0
  238. package/templates/solid/base/toolchain/prettier.config.js +10 -0
  239. package/templates/solid/base/tsconfig.json.ejs +31 -0
  240. package/templates/solid/base/vite.config.js.ejs +22 -0
  241. package/templates/solid/code-router/src/main.tsx.ejs +71 -0
  242. package/templates/solid/example/tanchat/README.md +52 -0
  243. package/templates/solid/example/tanchat/assets/ai-streaming-server/README.md +110 -0
  244. package/templates/solid/example/tanchat/assets/ai-streaming-server/_dot_env.example +1 -0
  245. package/templates/solid/example/tanchat/assets/ai-streaming-server/package.json +26 -0
  246. package/templates/solid/example/tanchat/assets/ai-streaming-server/src/index.ts +102 -0
  247. package/templates/solid/example/tanchat/assets/ai-streaming-server/tsconfig.json +15 -0
  248. package/templates/solid/example/tanchat/assets/src/components/demo.SettingsDialog.tsx +149 -0
  249. package/templates/solid/example/tanchat/assets/src/demo.index.css +227 -0
  250. package/templates/solid/example/tanchat/assets/src/lib/demo-store.ts +13 -0
  251. package/templates/solid/example/tanchat/assets/src/routes/example.chat.tsx +435 -0
  252. package/templates/solid/example/tanchat/assets/src/store/demo.hooks.ts +17 -0
  253. package/templates/solid/example/tanchat/assets/src/store/demo.store.ts +133 -0
  254. package/templates/solid/example/tanchat/info.json +14 -0
  255. package/templates/solid/example/tanchat/package.json +7 -0
  256. package/templates/solid/file-router/package.fr.json +5 -0
  257. package/templates/solid/file-router/src/main.tsx.ejs +47 -0
  258. package/templates/solid/file-router/src/routes/__root.tsx.ejs +41 -0
  259. package/templates/solid/file-router/src/routes/index.tsx +43 -0
  260. package/tests/cra.test.ts +293 -0
  261. package/tests/snapshots/cra/cr-js-npm.json +33 -0
  262. package/tests/snapshots/cra/cr-ts-npm.json +34 -0
  263. package/tests/snapshots/cra/cr-ts-start-npm.json +38 -0
  264. package/tests/snapshots/cra/fr-ts-npm.json +34 -0
  265. package/tests/snapshots/cra/fr-ts-tw-npm.json +33 -0
  266. package/tests/snapshots/cra/solid-cr-js-npm.json +31 -0
  267. package/tests/snapshots/cra/solid-cr-ts-npm.json +32 -0
  268. package/tests/snapshots/cra/solid-cr-ts-start-npm.json +36 -0
  269. package/tests/snapshots/cra/solid-fr-ts-npm.json +33 -0
  270. package/tests/snapshots/cra/solid-fr-ts-tw-npm.json +32 -0
  271. package/tests/test-utilities.ts +87 -0
  272. package/tsconfig.json +17 -0
@@ -0,0 +1,793 @@
1
+ import { basename, dirname, resolve } from 'node:path'
2
+ import { log, outro, spinner } from '@clack/prompts'
3
+ import { render } from 'ejs'
4
+ import { format } from 'prettier'
5
+ import chalk from 'chalk'
6
+
7
+ import { getTemplatesRoot } from './templates.js'
8
+ import { CODE_ROUTER, FILE_ROUTER } from './constants.js'
9
+ import { sortObject } from './utils.js'
10
+ import { writeConfigFile } from './config-file.js'
11
+ import { packageManagerExecute } from './package-manager.js'
12
+
13
+ import type { AddOn, Environment, Options } from './types.js'
14
+
15
+ function createCopyFiles(environment: Environment, targetDir: string) {
16
+ return async function copyFiles(
17
+ templateDir: string,
18
+ files: Array<string>,
19
+ // optionally copy files from a folder to the root
20
+ toRoot?: boolean,
21
+ ) {
22
+ for (const file of files) {
23
+ let targetFileName = file.replace('.tw', '')
24
+ if (toRoot) {
25
+ const fileNoPath = targetFileName.split('/').pop()
26
+ targetFileName = fileNoPath ? `./${fileNoPath}` : targetFileName
27
+ }
28
+ await environment.copyFile(
29
+ resolve(templateDir, file),
30
+ resolve(targetDir, targetFileName),
31
+ )
32
+ }
33
+ }
34
+ }
35
+
36
+ function jsSafeName(name: string) {
37
+ return name
38
+ .split(/[^a-zA-Z0-9]/)
39
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
40
+ .join('')
41
+ }
42
+
43
+ function createTemplateFile(
44
+ environment: Environment,
45
+ projectName: string,
46
+ options: Options,
47
+ targetDir: string,
48
+ ) {
49
+ return async function templateFile(
50
+ file: string,
51
+ content: string,
52
+ targetFileName?: string,
53
+ extraTemplateValues?: Record<string, any>,
54
+ ) {
55
+ function getPackageManagerAddScript(
56
+ packageName: string,
57
+ isDev: boolean = false,
58
+ ) {
59
+ let command
60
+ switch (options.packageManager) {
61
+ case 'yarn':
62
+ case 'pnpm':
63
+ command = isDev
64
+ ? `${options.packageManager} add ${packageName} --dev`
65
+ : `${options.packageManager} add ${packageName}`
66
+ break
67
+ default:
68
+ command = isDev
69
+ ? `${options.packageManager} install ${packageName} -D`
70
+ : `${options.packageManager} install ${packageName}`
71
+ break
72
+ }
73
+ return command
74
+ }
75
+
76
+ function getPackageManagerRunScript(scriptName: string) {
77
+ let command
78
+ switch (options.packageManager) {
79
+ case 'yarn':
80
+ case 'pnpm':
81
+ command = `${options.packageManager} ${scriptName}`
82
+ break
83
+ case 'deno':
84
+ command = `${options.packageManager} task ${scriptName}`
85
+ break
86
+ default:
87
+ command = `${options.packageManager} run ${scriptName}`
88
+ break
89
+ }
90
+ return command
91
+ }
92
+
93
+ const templateValues = {
94
+ packageManager: options.packageManager,
95
+ projectName: projectName,
96
+ typescript: options.typescript,
97
+ tailwind: options.tailwind,
98
+ toolchain: options.toolchain,
99
+ js: options.typescript ? 'ts' : 'js',
100
+ jsx: options.typescript ? 'tsx' : 'jsx',
101
+ fileRouter: options.mode === FILE_ROUTER,
102
+ codeRouter: options.mode === CODE_ROUTER,
103
+ addOnEnabled: options.chosenAddOns.reduce<Record<string, boolean>>(
104
+ (acc, addOn) => {
105
+ acc[addOn.id] = true
106
+ return acc
107
+ },
108
+ {},
109
+ ),
110
+ addOns: options.chosenAddOns,
111
+
112
+ ...extraTemplateValues,
113
+
114
+ getPackageManagerAddScript,
115
+ getPackageManagerRunScript,
116
+ }
117
+
118
+ try {
119
+ content = render(content, templateValues)
120
+ } catch (error) {
121
+ console.error(chalk.red(`EJS error in file ${file}`))
122
+ console.error(error)
123
+ process.exit(1)
124
+ }
125
+ const target = targetFileName ?? file.replace('.ejs', '')
126
+
127
+ if (target.endsWith('.ts') || target.endsWith('.tsx')) {
128
+ content = await format(content, {
129
+ semi: false,
130
+ singleQuote: true,
131
+ trailingComma: 'all',
132
+ parser: 'typescript',
133
+ })
134
+ }
135
+
136
+ await environment.writeFile(resolve(targetDir, target), content)
137
+ }
138
+ }
139
+
140
+ async function createPackageJSON(
141
+ environment: Environment,
142
+ projectName: string,
143
+ options: Options,
144
+ templateDir: string,
145
+ routerDir: string,
146
+ targetDir: string,
147
+ addOns: Array<{
148
+ dependencies?: Record<string, string>
149
+ devDependencies?: Record<string, string>
150
+ scripts?: Record<string, string>
151
+ }>,
152
+ ) {
153
+ let packageJSON = JSON.parse(
154
+ await environment.readFile(resolve(templateDir, 'package.json'), 'utf8'),
155
+ )
156
+ packageJSON.name = projectName
157
+ if (options.typescript) {
158
+ const tsPackageJSON = JSON.parse(
159
+ await environment.readFile(
160
+ resolve(templateDir, 'package.ts.json'),
161
+ 'utf8',
162
+ ),
163
+ )
164
+ packageJSON = {
165
+ ...packageJSON,
166
+ devDependencies: {
167
+ ...packageJSON.devDependencies,
168
+ ...tsPackageJSON.devDependencies,
169
+ },
170
+ }
171
+ }
172
+ if (options.tailwind) {
173
+ const twPackageJSON = JSON.parse(
174
+ await environment.readFile(
175
+ resolve(templateDir, 'package.tw.json'),
176
+ 'utf8',
177
+ ),
178
+ )
179
+ packageJSON = {
180
+ ...packageJSON,
181
+ dependencies: {
182
+ ...packageJSON.dependencies,
183
+ ...twPackageJSON.dependencies,
184
+ },
185
+ }
186
+ }
187
+ if (options.toolchain === 'biome') {
188
+ const biomePackageJSON = JSON.parse(
189
+ await environment.readFile(
190
+ resolve(templateDir, 'package.biome.json'),
191
+ 'utf8',
192
+ ),
193
+ )
194
+ packageJSON = {
195
+ ...packageJSON,
196
+ scripts: {
197
+ ...packageJSON.scripts,
198
+ ...biomePackageJSON.scripts,
199
+ },
200
+ devDependencies: {
201
+ ...packageJSON.devDependencies,
202
+ ...biomePackageJSON.devDependencies,
203
+ },
204
+ }
205
+ }
206
+ if (options.toolchain === 'eslint+prettier') {
207
+ const eslintPrettierPackageJSON = JSON.parse(
208
+ await environment.readFile(
209
+ resolve(templateDir, 'package.eslintprettier.json'),
210
+ 'utf-8',
211
+ ),
212
+ )
213
+ packageJSON = {
214
+ ...packageJSON,
215
+ scripts: {
216
+ ...packageJSON.scripts,
217
+ ...eslintPrettierPackageJSON.scripts,
218
+ },
219
+ devDependencies: {
220
+ ...packageJSON.devDependencies,
221
+ ...eslintPrettierPackageJSON.devDependencies,
222
+ },
223
+ }
224
+ }
225
+ if (options.mode === FILE_ROUTER) {
226
+ const frPackageJSON = JSON.parse(
227
+ await environment.readFile(resolve(routerDir, 'package.fr.json'), 'utf8'),
228
+ )
229
+ packageJSON = {
230
+ ...packageJSON,
231
+ dependencies: {
232
+ ...packageJSON.dependencies,
233
+ ...frPackageJSON.dependencies,
234
+ },
235
+ }
236
+ }
237
+
238
+ for (const addOn of addOns) {
239
+ packageJSON = {
240
+ ...packageJSON,
241
+ dependencies: {
242
+ ...packageJSON.dependencies,
243
+ ...addOn.dependencies,
244
+ },
245
+ devDependencies: {
246
+ ...packageJSON.devDependencies,
247
+ ...addOn.devDependencies,
248
+ },
249
+ scripts: {
250
+ ...packageJSON.scripts,
251
+ ...addOn.scripts,
252
+ },
253
+ }
254
+ }
255
+
256
+ packageJSON.dependencies = sortObject(
257
+ packageJSON.dependencies as Record<string, string>,
258
+ )
259
+ packageJSON.devDependencies = sortObject(
260
+ packageJSON.devDependencies as Record<string, string>,
261
+ )
262
+
263
+ await environment.writeFile(
264
+ resolve(targetDir, 'package.json'),
265
+ JSON.stringify(packageJSON, null, 2),
266
+ )
267
+ }
268
+
269
+ async function copyAddOnFile(
270
+ environment: Environment,
271
+ content: string,
272
+ target: string,
273
+ targetPath: string,
274
+ templateFile: (content: string, targetFileName: string) => Promise<void>,
275
+ ) {
276
+ let targetFile = basename(target).replace(/^_dot_/, '.')
277
+ let isTemplate = false
278
+ if (targetFile.endsWith('.ejs')) {
279
+ targetFile = targetFile.replace('.ejs', '')
280
+ isTemplate = true
281
+ }
282
+ let isAppend = false
283
+ if (targetFile.endsWith('.append')) {
284
+ targetFile = targetFile.replace('.append', '')
285
+ isAppend = true
286
+ }
287
+
288
+ const finalTargetPath = resolve(dirname(targetPath), targetFile)
289
+
290
+ if (isTemplate) {
291
+ await templateFile(content, finalTargetPath)
292
+ } else {
293
+ if (isAppend) {
294
+ await environment.appendFile(finalTargetPath, content)
295
+ } else {
296
+ await environment.writeFile(finalTargetPath, content)
297
+ }
298
+ }
299
+ }
300
+
301
+ export async function createApp(
302
+ options: Options,
303
+ {
304
+ silent = false,
305
+ environment,
306
+ cwd,
307
+ name = 'create-tsrouter-app',
308
+ }: {
309
+ silent?: boolean
310
+ environment: Environment
311
+ cwd?: string
312
+ name: string
313
+ },
314
+ ) {
315
+ environment.startRun()
316
+
317
+ const templateDirBase = resolve(getTemplatesRoot(), options.framework, 'base')
318
+ const templateDirRouter = resolve(
319
+ getTemplatesRoot(),
320
+ options.framework,
321
+ options.mode,
322
+ )
323
+
324
+ let targetDir: string = cwd || ''
325
+ if (!targetDir.length) {
326
+ targetDir = resolve(process.cwd(), options.projectName)
327
+
328
+ if (environment.exists(targetDir)) {
329
+ if (!silent) {
330
+ log.error(`Directory "${options.projectName}" already exists`)
331
+ }
332
+ return
333
+ }
334
+ }
335
+
336
+ const copyFiles = createCopyFiles(environment, targetDir)
337
+ const templateFileFromContent = createTemplateFile(
338
+ environment,
339
+ options.projectName,
340
+ options,
341
+ targetDir,
342
+ )
343
+
344
+ async function templateFile(
345
+ templateBase: string,
346
+ file: string,
347
+ targetFileName?: string,
348
+ extraTemplateValues?: Record<string, any>,
349
+ ) {
350
+ const content = await environment.readFile(
351
+ resolve(templateBase, file),
352
+ 'utf-8',
353
+ )
354
+ return templateFileFromContent(
355
+ file,
356
+ content.toString(),
357
+ targetFileName,
358
+ extraTemplateValues,
359
+ )
360
+ }
361
+
362
+ const isAddOnEnabled = (id: string) =>
363
+ options.chosenAddOns.find((a) => a.id === id)
364
+
365
+ async function runAddOn(addOn: AddOn) {
366
+ if (addOn.files) {
367
+ for (const file of Object.keys(addOn.files)) {
368
+ await copyAddOnFile(
369
+ environment,
370
+ addOn.files[file],
371
+ file,
372
+ resolve(targetDir, file),
373
+ (content, targetFileName) =>
374
+ templateFileFromContent(targetFileName, content),
375
+ )
376
+ }
377
+ }
378
+ if (addOn.deletedFiles) {
379
+ for (const file of addOn.deletedFiles) {
380
+ await environment.deleteFile(resolve(targetDir, file))
381
+ }
382
+ }
383
+
384
+ if (addOn.command && addOn.command.command) {
385
+ await environment.execute(
386
+ addOn.command.command,
387
+ addOn.command.args || [],
388
+ resolve(targetDir),
389
+ )
390
+ }
391
+ }
392
+
393
+ // Setup the .vscode directory
394
+ switch (options.toolchain) {
395
+ case 'biome':
396
+ await environment.copyFile(
397
+ resolve(templateDirBase, '_dot_vscode/settings.biome.json'),
398
+ resolve(targetDir, '.vscode/settings.json'),
399
+ )
400
+ break
401
+ case 'none':
402
+ default:
403
+ await environment.copyFile(
404
+ resolve(templateDirBase, '_dot_vscode/settings.json'),
405
+ resolve(targetDir, '.vscode/settings.json'),
406
+ )
407
+ }
408
+
409
+ // Fill the public directory
410
+ copyFiles(templateDirBase, [
411
+ './public/robots.txt',
412
+ './public/favicon.ico',
413
+ './public/manifest.json',
414
+ './public/logo192.png',
415
+ './public/logo512.png',
416
+ ])
417
+
418
+ // Check for a .cursorrules file
419
+ if (environment.exists(resolve(templateDirBase, '.cursorrules'))) {
420
+ await environment.copyFile(
421
+ resolve(templateDirBase, '.cursorrules'),
422
+ resolve(targetDir, '.cursorrules'),
423
+ )
424
+ }
425
+
426
+ // Copy in Vite and Tailwind config and CSS
427
+ if (!options.tailwind) {
428
+ await copyFiles(templateDirBase, ['./src/App.css'])
429
+ }
430
+
431
+ // Don't create a vite.config.js file if we are building a Start app
432
+ if (!isAddOnEnabled('start')) {
433
+ await templateFile(templateDirBase, './vite.config.js.ejs')
434
+ }
435
+
436
+ await templateFile(templateDirBase, './src/styles.css.ejs')
437
+
438
+ copyFiles(templateDirBase, ['./src/logo.svg'])
439
+
440
+ if (options.toolchain === 'biome') {
441
+ copyFiles(templateDirBase, ['./toolchain/biome.json'], true)
442
+ }
443
+
444
+ if (options.toolchain === 'eslint+prettier') {
445
+ copyFiles(
446
+ templateDirBase,
447
+ [
448
+ './toolchain/eslint.config.js',
449
+ './toolchain/prettier.config.js',
450
+ './toolchain/.prettierignore',
451
+ ],
452
+ true,
453
+ )
454
+ }
455
+
456
+ // Setup reportWebVitals
457
+ if (!isAddOnEnabled('start') && options.framework === 'react') {
458
+ if (options.typescript) {
459
+ await templateFile(templateDirBase, './src/reportWebVitals.ts.ejs')
460
+ } else {
461
+ await templateFile(
462
+ templateDirBase,
463
+ './src/reportWebVitals.ts.ejs',
464
+ './src/reportWebVitals.js',
465
+ )
466
+ }
467
+ }
468
+ if (!isAddOnEnabled('start')) {
469
+ await templateFile(templateDirBase, './index.html.ejs')
470
+ }
471
+
472
+ // Add .gitignore
473
+ await environment.copyFile(
474
+ resolve(templateDirBase, '_dot_gitignore'),
475
+ resolve(targetDir, '.gitignore'),
476
+ )
477
+
478
+ // Setup tsconfig
479
+ if (options.typescript) {
480
+ await templateFile(
481
+ templateDirBase,
482
+ './tsconfig.json.ejs',
483
+ './tsconfig.json',
484
+ )
485
+ }
486
+
487
+ // Setup the package.json file, optionally with typescript, tailwind and formatter/linter
488
+ await createPackageJSON(
489
+ environment,
490
+ options.projectName,
491
+ options,
492
+ templateDirBase,
493
+ templateDirRouter,
494
+ targetDir,
495
+ options.chosenAddOns.map((addOn) => addOn.packageAdditions),
496
+ )
497
+
498
+ // Copy all the asset files from the addons
499
+ const s = silent ? null : spinner()
500
+ for (const type of ['add-on', 'example']) {
501
+ for (const phase of ['setup', 'add-on']) {
502
+ for (const addOn of options.chosenAddOns.filter(
503
+ (addOn) => addOn.phase === phase && addOn.type === type,
504
+ )) {
505
+ s?.start(`Setting up ${addOn.name}...`)
506
+ await runAddOn(addOn)
507
+ s?.stop(`${addOn.name} setup complete`)
508
+ }
509
+ }
510
+ }
511
+
512
+ if (isAddOnEnabled('shadcn')) {
513
+ const shadcnComponents = new Set<string>()
514
+ for (const addOn of options.chosenAddOns) {
515
+ if (addOn.shadcnComponents) {
516
+ for (const component of addOn.shadcnComponents) {
517
+ shadcnComponents.add(component)
518
+ }
519
+ }
520
+ }
521
+ if (options.overlay) {
522
+ if (options.overlay.shadcnComponents) {
523
+ for (const component of options.overlay.shadcnComponents) {
524
+ shadcnComponents.add(component)
525
+ }
526
+ }
527
+ }
528
+
529
+ if (shadcnComponents.size > 0) {
530
+ s?.start(
531
+ `Installing shadcn components (${Array.from(shadcnComponents).join(', ')})...`,
532
+ )
533
+ await packageManagerExecute(
534
+ environment,
535
+ options.packageManager,
536
+ 'shadcn@latest',
537
+ ['add', '--silent', '--yes', ...shadcnComponents],
538
+ resolve(targetDir),
539
+ )
540
+ s?.stop(`Installed additional shadcn components`)
541
+ }
542
+ }
543
+
544
+ const integrations: Array<{
545
+ type: 'layout' | 'provider' | 'root-provider' | 'header-user'
546
+ name: string
547
+ path: string
548
+ }> = []
549
+ if (environment.exists(resolve(targetDir, 'src/integrations'))) {
550
+ for (const integration of environment.readdir(
551
+ resolve(targetDir, 'src/integrations'),
552
+ )) {
553
+ const integrationName = jsSafeName(integration)
554
+ if (
555
+ environment.exists(
556
+ resolve(targetDir, 'src/integrations', integration, 'layout.tsx'),
557
+ )
558
+ ) {
559
+ integrations.push({
560
+ type: 'layout',
561
+ name: `${integrationName}Layout`,
562
+ path: `integrations/${integration}/layout`,
563
+ })
564
+ }
565
+ if (
566
+ environment.exists(
567
+ resolve(targetDir, 'src/integrations', integration, 'provider.tsx'),
568
+ )
569
+ ) {
570
+ integrations.push({
571
+ type: 'provider',
572
+ name: `${integrationName}Provider`,
573
+ path: `integrations/${integration}/provider`,
574
+ })
575
+ }
576
+ if (
577
+ environment.exists(
578
+ resolve(
579
+ targetDir,
580
+ 'src/integrations',
581
+ integration,
582
+ 'root-provider.tsx',
583
+ ),
584
+ )
585
+ ) {
586
+ integrations.push({
587
+ type: 'root-provider',
588
+ name: integrationName,
589
+ path: `integrations/${integration}/root-provider`,
590
+ })
591
+ }
592
+ if (
593
+ environment.exists(
594
+ resolve(
595
+ targetDir,
596
+ 'src/integrations',
597
+ integration,
598
+ 'header-user.tsx',
599
+ ),
600
+ )
601
+ ) {
602
+ integrations.push({
603
+ type: 'header-user',
604
+ name: `${integrationName}Header`,
605
+ path: `integrations/${integration}/header-user`,
606
+ })
607
+ }
608
+ }
609
+ }
610
+
611
+ const routes: Array<{
612
+ path: string
613
+ name: string
614
+ }> = []
615
+ if (environment.exists(resolve(targetDir, 'src/routes'))) {
616
+ for (const file of environment.readdir(resolve(targetDir, 'src/routes'))) {
617
+ const name = file.replace(/\.tsx?|\.jsx?/, '')
618
+ const safeRouteName = jsSafeName(name)
619
+ routes.push({
620
+ path: `./routes/${name}`,
621
+ name: safeRouteName,
622
+ })
623
+ }
624
+ }
625
+
626
+ // Create the main entry point
627
+ if (!isAddOnEnabled('start')) {
628
+ if (options.typescript) {
629
+ await templateFile(
630
+ templateDirRouter,
631
+ './src/main.tsx.ejs',
632
+ './src/main.tsx',
633
+ {
634
+ routes,
635
+ integrations,
636
+ },
637
+ )
638
+ } else {
639
+ await templateFile(
640
+ templateDirRouter,
641
+ './src/main.tsx.ejs',
642
+ './src/main.jsx',
643
+ {
644
+ routes,
645
+ integrations,
646
+ },
647
+ )
648
+ }
649
+ }
650
+
651
+ // Setup the app component. There are four variations, typescript/javascript and tailwind/non-tailwind.
652
+ if (options.mode === FILE_ROUTER) {
653
+ await templateFile(
654
+ templateDirRouter,
655
+ './src/routes/__root.tsx.ejs',
656
+ './src/routes/__root.tsx',
657
+ {
658
+ integrations,
659
+ },
660
+ )
661
+ await templateFile(
662
+ templateDirBase,
663
+ './src/App.tsx.ejs',
664
+ './src/routes/index.tsx',
665
+ )
666
+ } else {
667
+ await templateFile(
668
+ templateDirBase,
669
+ './src/App.tsx.ejs',
670
+ options.typescript ? undefined : './src/App.jsx',
671
+ )
672
+ if (options.framework === 'react') {
673
+ await templateFile(
674
+ templateDirBase,
675
+ './src/App.test.tsx.ejs',
676
+ options.typescript ? undefined : './src/App.test.jsx',
677
+ )
678
+ }
679
+ }
680
+
681
+ if (
682
+ routes.length > 0 ||
683
+ options.chosenAddOns.length > 0 ||
684
+ integrations.length > 0
685
+ ) {
686
+ await templateFile(
687
+ templateDirBase,
688
+ './src/components/Header.tsx.ejs',
689
+ './src/components/Header.tsx',
690
+ {
691
+ integrations,
692
+ },
693
+ )
694
+ }
695
+
696
+ const warnings: Array<string> = []
697
+ for (const addOn of options.chosenAddOns) {
698
+ if (addOn.warning) {
699
+ warnings.push(addOn.warning)
700
+ }
701
+ }
702
+
703
+ // Create the README.md
704
+ await templateFile(templateDirBase, 'README.md.ejs')
705
+
706
+ // Adding overlay
707
+ if (options.overlay) {
708
+ s?.start(`Setting up overlay ${options.overlay.name}...`)
709
+ await runAddOn(options.overlay)
710
+ s?.stop(`Overlay ${options.overlay.name} setup complete`)
711
+ }
712
+
713
+ // Install dependencies
714
+ s?.start(`Installing dependencies via ${options.packageManager}...`)
715
+ await environment.execute(
716
+ options.packageManager,
717
+ ['install'],
718
+ resolve(targetDir),
719
+ )
720
+ s?.stop(`Installed dependencies`)
721
+
722
+ if (warnings.length > 0) {
723
+ if (!silent) {
724
+ log.warn(chalk.red(warnings.join('\n')))
725
+ }
726
+ }
727
+
728
+ if (options.toolchain === 'biome') {
729
+ s?.start(`Applying toolchain ${options.toolchain}...`)
730
+ switch (options.packageManager) {
731
+ case 'pnpm':
732
+ // pnpm automatically forwards extra arguments
733
+ await environment.execute(
734
+ options.packageManager,
735
+ ['run', 'check', '--fix'],
736
+ resolve(targetDir),
737
+ )
738
+ break
739
+ default:
740
+ await environment.execute(
741
+ options.packageManager,
742
+ ['run', 'check', '--', '--fix'],
743
+ resolve(targetDir),
744
+ )
745
+ break
746
+ }
747
+ s?.stop(`Applied toolchain ${options.toolchain}...`)
748
+ }
749
+
750
+ if (options.toolchain === 'eslint+prettier') {
751
+ s?.start(`Applying toolchain ${options.toolchain}...`)
752
+ await environment.execute(
753
+ options.packageManager,
754
+ ['run', 'check'],
755
+ targetDir,
756
+ )
757
+ s?.stop(`Applied toolchain ${options.toolchain}...`)
758
+ }
759
+
760
+ if (options.git) {
761
+ s?.start(`Initializing git repository...`)
762
+ await environment.execute('git', ['init'], resolve(targetDir))
763
+ s?.stop(`Initialized git repository`)
764
+ }
765
+
766
+ await writeConfigFile(environment, targetDir, options)
767
+
768
+ environment.finishRun()
769
+
770
+ let errorStatement = ''
771
+ if (environment.getErrors().length) {
772
+ errorStatement = `
773
+
774
+ ${chalk.red('Errors were encountered during this process:')}
775
+
776
+ ${environment.getErrors().join('\n')}`
777
+ }
778
+
779
+ if (!silent) {
780
+ let startCommand = `${options.packageManager} ${isAddOnEnabled('start') ? 'dev' : 'start'}`
781
+ if (options.packageManager === 'deno') {
782
+ startCommand = `deno ${isAddOnEnabled('start') ? 'task dev' : 'start'}`
783
+ }
784
+
785
+ outro(`Your TanStack app is ready in '${basename(targetDir)}'.
786
+
787
+ Use the following commands to start your app:
788
+ % cd ${options.projectName}
789
+ % ${startCommand}
790
+
791
+ Please read the README.md for more information on testing, styling, adding routes, react-query, etc.${errorStatement}`)
792
+ }
793
+ }