@tanstack/cta-engine 0.10.0-alpha.6

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 +127 -0
  4. package/dist/cli.js +112 -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 +254 -0
  9. package/dist/environment.js +119 -0
  10. package/dist/index.js +1 -0
  11. package/dist/mcp.js +211 -0
  12. package/dist/options.js +309 -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 +1 -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 +6 -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 +3 -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 +184 -0
  37. package/src/cli.ts +163 -0
  38. package/src/config-file.ts +45 -0
  39. package/src/constants.ts +9 -0
  40. package/src/create-app.ts +791 -0
  41. package/src/custom-add-on.ts +323 -0
  42. package/src/environment.ts +144 -0
  43. package/src/index.ts +1 -0
  44. package/src/mcp.ts +252 -0
  45. package/src/options.ts +359 -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 +11 -0
@@ -0,0 +1,791 @@
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
+ }: {
308
+ silent?: boolean
309
+ environment: Environment
310
+ cwd?: string
311
+ },
312
+ ) {
313
+ environment.startRun()
314
+
315
+ const templateDirBase = resolve(getTemplatesRoot(), options.framework, 'base')
316
+ const templateDirRouter = resolve(
317
+ getTemplatesRoot(),
318
+ options.framework,
319
+ options.mode,
320
+ )
321
+
322
+ let targetDir: string = cwd || ''
323
+ if (!targetDir.length) {
324
+ targetDir = resolve(process.cwd(), options.projectName)
325
+
326
+ if (environment.exists(targetDir)) {
327
+ if (!silent) {
328
+ log.error(`Directory "${options.projectName}" already exists`)
329
+ }
330
+ return
331
+ }
332
+ }
333
+
334
+ const copyFiles = createCopyFiles(environment, targetDir)
335
+ const templateFileFromContent = createTemplateFile(
336
+ environment,
337
+ options.projectName,
338
+ options,
339
+ targetDir,
340
+ )
341
+
342
+ async function templateFile(
343
+ templateBase: string,
344
+ file: string,
345
+ targetFileName?: string,
346
+ extraTemplateValues?: Record<string, any>,
347
+ ) {
348
+ const content = await environment.readFile(
349
+ resolve(templateBase, file),
350
+ 'utf-8',
351
+ )
352
+ return templateFileFromContent(
353
+ file,
354
+ content.toString(),
355
+ targetFileName,
356
+ extraTemplateValues,
357
+ )
358
+ }
359
+
360
+ const isAddOnEnabled = (id: string) =>
361
+ options.chosenAddOns.find((a) => a.id === id)
362
+
363
+ async function runAddOn(addOn: AddOn) {
364
+ if (addOn.files) {
365
+ for (const file of Object.keys(addOn.files)) {
366
+ await copyAddOnFile(
367
+ environment,
368
+ addOn.files[file],
369
+ file,
370
+ resolve(targetDir, file),
371
+ (content, targetFileName) =>
372
+ templateFileFromContent(targetFileName, content),
373
+ )
374
+ }
375
+ }
376
+ if (addOn.deletedFiles) {
377
+ for (const file of addOn.deletedFiles) {
378
+ await environment.deleteFile(resolve(targetDir, file))
379
+ }
380
+ }
381
+
382
+ if (addOn.command && addOn.command.command) {
383
+ await environment.execute(
384
+ addOn.command.command,
385
+ addOn.command.args || [],
386
+ resolve(targetDir),
387
+ )
388
+ }
389
+ }
390
+
391
+ // Setup the .vscode directory
392
+ switch (options.toolchain) {
393
+ case 'biome':
394
+ await environment.copyFile(
395
+ resolve(templateDirBase, '_dot_vscode/settings.biome.json'),
396
+ resolve(targetDir, '.vscode/settings.json'),
397
+ )
398
+ break
399
+ case 'none':
400
+ default:
401
+ await environment.copyFile(
402
+ resolve(templateDirBase, '_dot_vscode/settings.json'),
403
+ resolve(targetDir, '.vscode/settings.json'),
404
+ )
405
+ }
406
+
407
+ // Fill the public directory
408
+ copyFiles(templateDirBase, [
409
+ './public/robots.txt',
410
+ './public/favicon.ico',
411
+ './public/manifest.json',
412
+ './public/logo192.png',
413
+ './public/logo512.png',
414
+ ])
415
+
416
+ // Check for a .cursorrules file
417
+ if (environment.exists(resolve(templateDirBase, '.cursorrules'))) {
418
+ await environment.copyFile(
419
+ resolve(templateDirBase, '.cursorrules'),
420
+ resolve(targetDir, '.cursorrules'),
421
+ )
422
+ }
423
+
424
+ // Copy in Vite and Tailwind config and CSS
425
+ if (!options.tailwind) {
426
+ await copyFiles(templateDirBase, ['./src/App.css'])
427
+ }
428
+
429
+ // Don't create a vite.config.js file if we are building a Start app
430
+ if (!isAddOnEnabled('start')) {
431
+ await templateFile(templateDirBase, './vite.config.js.ejs')
432
+ }
433
+
434
+ await templateFile(templateDirBase, './src/styles.css.ejs')
435
+
436
+ copyFiles(templateDirBase, ['./src/logo.svg'])
437
+
438
+ if (options.toolchain === 'biome') {
439
+ copyFiles(templateDirBase, ['./toolchain/biome.json'], true)
440
+ }
441
+
442
+ if (options.toolchain === 'eslint+prettier') {
443
+ copyFiles(
444
+ templateDirBase,
445
+ [
446
+ './toolchain/eslint.config.js',
447
+ './toolchain/prettier.config.js',
448
+ './toolchain/.prettierignore',
449
+ ],
450
+ true,
451
+ )
452
+ }
453
+
454
+ // Setup reportWebVitals
455
+ if (!isAddOnEnabled('start') && options.framework === 'react') {
456
+ if (options.typescript) {
457
+ await templateFile(templateDirBase, './src/reportWebVitals.ts.ejs')
458
+ } else {
459
+ await templateFile(
460
+ templateDirBase,
461
+ './src/reportWebVitals.ts.ejs',
462
+ './src/reportWebVitals.js',
463
+ )
464
+ }
465
+ }
466
+ if (!isAddOnEnabled('start')) {
467
+ await templateFile(templateDirBase, './index.html.ejs')
468
+ }
469
+
470
+ // Add .gitignore
471
+ await environment.copyFile(
472
+ resolve(templateDirBase, '_dot_gitignore'),
473
+ resolve(targetDir, '.gitignore'),
474
+ )
475
+
476
+ // Setup tsconfig
477
+ if (options.typescript) {
478
+ await templateFile(
479
+ templateDirBase,
480
+ './tsconfig.json.ejs',
481
+ './tsconfig.json',
482
+ )
483
+ }
484
+
485
+ // Setup the package.json file, optionally with typescript, tailwind and formatter/linter
486
+ await createPackageJSON(
487
+ environment,
488
+ options.projectName,
489
+ options,
490
+ templateDirBase,
491
+ templateDirRouter,
492
+ targetDir,
493
+ options.chosenAddOns.map((addOn) => addOn.packageAdditions),
494
+ )
495
+
496
+ // Copy all the asset files from the addons
497
+ const s = silent ? null : spinner()
498
+ for (const type of ['add-on', 'example']) {
499
+ for (const phase of ['setup', 'add-on']) {
500
+ for (const addOn of options.chosenAddOns.filter(
501
+ (addOn) => addOn.phase === phase && addOn.type === type,
502
+ )) {
503
+ s?.start(`Setting up ${addOn.name}...`)
504
+ await runAddOn(addOn)
505
+ s?.stop(`${addOn.name} setup complete`)
506
+ }
507
+ }
508
+ }
509
+
510
+ if (isAddOnEnabled('shadcn')) {
511
+ const shadcnComponents = new Set<string>()
512
+ for (const addOn of options.chosenAddOns) {
513
+ if (addOn.shadcnComponents) {
514
+ for (const component of addOn.shadcnComponents) {
515
+ shadcnComponents.add(component)
516
+ }
517
+ }
518
+ }
519
+ if (options.overlay) {
520
+ if (options.overlay.shadcnComponents) {
521
+ for (const component of options.overlay.shadcnComponents) {
522
+ shadcnComponents.add(component)
523
+ }
524
+ }
525
+ }
526
+
527
+ if (shadcnComponents.size > 0) {
528
+ s?.start(
529
+ `Installing shadcn components (${Array.from(shadcnComponents).join(', ')})...`,
530
+ )
531
+ await packageManagerExecute(
532
+ environment,
533
+ options.packageManager,
534
+ 'shadcn@latest',
535
+ ['add', '--silent', '--yes', ...shadcnComponents],
536
+ resolve(targetDir),
537
+ )
538
+ s?.stop(`Installed additional shadcn components`)
539
+ }
540
+ }
541
+
542
+ const integrations: Array<{
543
+ type: 'layout' | 'provider' | 'root-provider' | 'header-user'
544
+ name: string
545
+ path: string
546
+ }> = []
547
+ if (environment.exists(resolve(targetDir, 'src/integrations'))) {
548
+ for (const integration of environment.readdir(
549
+ resolve(targetDir, 'src/integrations'),
550
+ )) {
551
+ const integrationName = jsSafeName(integration)
552
+ if (
553
+ environment.exists(
554
+ resolve(targetDir, 'src/integrations', integration, 'layout.tsx'),
555
+ )
556
+ ) {
557
+ integrations.push({
558
+ type: 'layout',
559
+ name: `${integrationName}Layout`,
560
+ path: `integrations/${integration}/layout`,
561
+ })
562
+ }
563
+ if (
564
+ environment.exists(
565
+ resolve(targetDir, 'src/integrations', integration, 'provider.tsx'),
566
+ )
567
+ ) {
568
+ integrations.push({
569
+ type: 'provider',
570
+ name: `${integrationName}Provider`,
571
+ path: `integrations/${integration}/provider`,
572
+ })
573
+ }
574
+ if (
575
+ environment.exists(
576
+ resolve(
577
+ targetDir,
578
+ 'src/integrations',
579
+ integration,
580
+ 'root-provider.tsx',
581
+ ),
582
+ )
583
+ ) {
584
+ integrations.push({
585
+ type: 'root-provider',
586
+ name: integrationName,
587
+ path: `integrations/${integration}/root-provider`,
588
+ })
589
+ }
590
+ if (
591
+ environment.exists(
592
+ resolve(
593
+ targetDir,
594
+ 'src/integrations',
595
+ integration,
596
+ 'header-user.tsx',
597
+ ),
598
+ )
599
+ ) {
600
+ integrations.push({
601
+ type: 'header-user',
602
+ name: `${integrationName}Header`,
603
+ path: `integrations/${integration}/header-user`,
604
+ })
605
+ }
606
+ }
607
+ }
608
+
609
+ const routes: Array<{
610
+ path: string
611
+ name: string
612
+ }> = []
613
+ if (environment.exists(resolve(targetDir, 'src/routes'))) {
614
+ for (const file of environment.readdir(resolve(targetDir, 'src/routes'))) {
615
+ const name = file.replace(/\.tsx?|\.jsx?/, '')
616
+ const safeRouteName = jsSafeName(name)
617
+ routes.push({
618
+ path: `./routes/${name}`,
619
+ name: safeRouteName,
620
+ })
621
+ }
622
+ }
623
+
624
+ // Create the main entry point
625
+ if (!isAddOnEnabled('start')) {
626
+ if (options.typescript) {
627
+ await templateFile(
628
+ templateDirRouter,
629
+ './src/main.tsx.ejs',
630
+ './src/main.tsx',
631
+ {
632
+ routes,
633
+ integrations,
634
+ },
635
+ )
636
+ } else {
637
+ await templateFile(
638
+ templateDirRouter,
639
+ './src/main.tsx.ejs',
640
+ './src/main.jsx',
641
+ {
642
+ routes,
643
+ integrations,
644
+ },
645
+ )
646
+ }
647
+ }
648
+
649
+ // Setup the app component. There are four variations, typescript/javascript and tailwind/non-tailwind.
650
+ if (options.mode === FILE_ROUTER) {
651
+ await templateFile(
652
+ templateDirRouter,
653
+ './src/routes/__root.tsx.ejs',
654
+ './src/routes/__root.tsx',
655
+ {
656
+ integrations,
657
+ },
658
+ )
659
+ await templateFile(
660
+ templateDirBase,
661
+ './src/App.tsx.ejs',
662
+ './src/routes/index.tsx',
663
+ )
664
+ } else {
665
+ await templateFile(
666
+ templateDirBase,
667
+ './src/App.tsx.ejs',
668
+ options.typescript ? undefined : './src/App.jsx',
669
+ )
670
+ if (options.framework === 'react') {
671
+ await templateFile(
672
+ templateDirBase,
673
+ './src/App.test.tsx.ejs',
674
+ options.typescript ? undefined : './src/App.test.jsx',
675
+ )
676
+ }
677
+ }
678
+
679
+ if (
680
+ routes.length > 0 ||
681
+ options.chosenAddOns.length > 0 ||
682
+ integrations.length > 0
683
+ ) {
684
+ await templateFile(
685
+ templateDirBase,
686
+ './src/components/Header.tsx.ejs',
687
+ './src/components/Header.tsx',
688
+ {
689
+ integrations,
690
+ },
691
+ )
692
+ }
693
+
694
+ const warnings: Array<string> = []
695
+ for (const addOn of options.chosenAddOns) {
696
+ if (addOn.warning) {
697
+ warnings.push(addOn.warning)
698
+ }
699
+ }
700
+
701
+ // Create the README.md
702
+ await templateFile(templateDirBase, 'README.md.ejs')
703
+
704
+ // Adding overlay
705
+ if (options.overlay) {
706
+ s?.start(`Setting up overlay ${options.overlay.name}...`)
707
+ await runAddOn(options.overlay)
708
+ s?.stop(`Overlay ${options.overlay.name} setup complete`)
709
+ }
710
+
711
+ // Install dependencies
712
+ s?.start(`Installing dependencies via ${options.packageManager}...`)
713
+ await environment.execute(
714
+ options.packageManager,
715
+ ['install'],
716
+ resolve(targetDir),
717
+ )
718
+ s?.stop(`Installed dependencies`)
719
+
720
+ if (warnings.length > 0) {
721
+ if (!silent) {
722
+ log.warn(chalk.red(warnings.join('\n')))
723
+ }
724
+ }
725
+
726
+ if (options.toolchain === 'biome') {
727
+ s?.start(`Applying toolchain ${options.toolchain}...`)
728
+ switch (options.packageManager) {
729
+ case 'pnpm':
730
+ // pnpm automatically forwards extra arguments
731
+ await environment.execute(
732
+ options.packageManager,
733
+ ['run', 'check', '--fix'],
734
+ resolve(targetDir),
735
+ )
736
+ break
737
+ default:
738
+ await environment.execute(
739
+ options.packageManager,
740
+ ['run', 'check', '--', '--fix'],
741
+ resolve(targetDir),
742
+ )
743
+ break
744
+ }
745
+ s?.stop(`Applied toolchain ${options.toolchain}...`)
746
+ }
747
+
748
+ if (options.toolchain === 'eslint+prettier') {
749
+ s?.start(`Applying toolchain ${options.toolchain}...`)
750
+ await environment.execute(
751
+ options.packageManager,
752
+ ['run', 'check'],
753
+ targetDir,
754
+ )
755
+ s?.stop(`Applied toolchain ${options.toolchain}...`)
756
+ }
757
+
758
+ if (options.git) {
759
+ s?.start(`Initializing git repository...`)
760
+ await environment.execute('git', ['init'], resolve(targetDir))
761
+ s?.stop(`Initialized git repository`)
762
+ }
763
+
764
+ await writeConfigFile(environment, targetDir, options)
765
+
766
+ environment.finishRun()
767
+
768
+ let errorStatement = ''
769
+ if (environment.getErrors().length) {
770
+ errorStatement = `
771
+
772
+ ${chalk.red('Errors were encountered during this process:')}
773
+
774
+ ${environment.getErrors().join('\n')}`
775
+ }
776
+
777
+ if (!silent) {
778
+ let startCommand = `${options.packageManager} ${isAddOnEnabled('start') ? 'dev' : 'start'}`
779
+ if (options.packageManager === 'deno') {
780
+ startCommand = `deno ${isAddOnEnabled('start') ? 'task dev' : 'start'}`
781
+ }
782
+
783
+ outro(`Your TanStack app is ready in '${basename(targetDir)}'.
784
+
785
+ Use the following commands to start your app:
786
+ % cd ${options.projectName}
787
+ % ${startCommand}
788
+
789
+ Please read the README.md for more information on testing, styling, adding routes, react-query, etc.${errorStatement}`)
790
+ }
791
+ }