@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
package/src/add-ons.ts ADDED
@@ -0,0 +1,145 @@
1
+ import { readFile } from 'node:fs/promises'
2
+ import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs'
3
+ import { resolve } from 'node:path'
4
+ import chalk from 'chalk'
5
+ import { getTemplatesRoot } from './templates.js'
6
+
7
+ import { DEFAULT_FRAMEWORK } from './constants.js'
8
+ import type { AddOn, CliOptions, Framework } from './types.js'
9
+
10
+ function isDirectory(path: string): boolean {
11
+ return statSync(path).isDirectory()
12
+ }
13
+
14
+ function findFilesRecursively(path: string, files: Record<string, string>) {
15
+ const dirFiles = readdirSync(path)
16
+ for (const file of dirFiles) {
17
+ const filePath = resolve(path, file)
18
+ if (isDirectory(filePath)) {
19
+ findFilesRecursively(filePath, files)
20
+ } else {
21
+ files[filePath] = readFileSync(filePath, 'utf-8').toString()
22
+ }
23
+ }
24
+ }
25
+
26
+ export async function getAllAddOns(
27
+ framework: Framework,
28
+ template: string,
29
+ ): Promise<Array<AddOn>> {
30
+ const addOns: Array<AddOn> = []
31
+
32
+ for (const type of ['add-on', 'example']) {
33
+ const addOnsBase = resolve(getTemplatesRoot(), framework, type)
34
+
35
+ if (!existsSync(addOnsBase)) {
36
+ continue
37
+ }
38
+
39
+ for (const dir of await readdirSync(addOnsBase).filter((file) =>
40
+ isDirectory(resolve(addOnsBase, file)),
41
+ )) {
42
+ const filePath = resolve(addOnsBase, dir, 'info.json')
43
+ const fileContent = await readFile(filePath, 'utf-8')
44
+ const info = JSON.parse(fileContent)
45
+
46
+ if (!info.templates.includes(template)) {
47
+ continue
48
+ }
49
+
50
+ let packageAdditions: Record<string, string> = {}
51
+ if (existsSync(resolve(addOnsBase, dir, 'package.json'))) {
52
+ packageAdditions = JSON.parse(
53
+ await readFile(resolve(addOnsBase, dir, 'package.json'), 'utf-8'),
54
+ )
55
+ }
56
+
57
+ let readme: string | undefined
58
+ if (existsSync(resolve(addOnsBase, dir, 'README.md'))) {
59
+ readme = await readFile(resolve(addOnsBase, dir, 'README.md'), 'utf-8')
60
+ }
61
+
62
+ const absoluteFiles: Record<string, string> = {}
63
+ const assetsDir = resolve(addOnsBase, dir, 'assets')
64
+ if (existsSync(assetsDir)) {
65
+ await findFilesRecursively(assetsDir, absoluteFiles)
66
+ }
67
+ const files: Record<string, string> = {}
68
+ for (const file of Object.keys(absoluteFiles)) {
69
+ files[file.replace(assetsDir, '.')] = absoluteFiles[file]
70
+ }
71
+
72
+ addOns.push({
73
+ ...info,
74
+ id: dir,
75
+ type,
76
+ packageAdditions,
77
+ readme,
78
+ files,
79
+ deletedFiles: [],
80
+ })
81
+ }
82
+ }
83
+
84
+ return addOns
85
+ }
86
+
87
+ // Turn the list of chosen add-on IDs into a final list of add-ons by resolving dependencies
88
+ export async function finalizeAddOns(
89
+ framework: Framework,
90
+ template: string,
91
+ chosenAddOnIDs: Array<string>,
92
+ ): Promise<Array<AddOn>> {
93
+ const finalAddOnIDs = new Set(chosenAddOnIDs)
94
+
95
+ const addOns = await getAllAddOns(framework, template)
96
+
97
+ for (const addOnID of finalAddOnIDs) {
98
+ let addOn: AddOn | undefined
99
+ const localAddOn = addOns.find((a) => a.id === addOnID)
100
+ if (localAddOn) {
101
+ addOn = loadAddOn(localAddOn)
102
+ } else if (addOnID.startsWith('http')) {
103
+ addOn = await loadRemoteAddOn(addOnID)
104
+ addOns.push(addOn)
105
+ } else {
106
+ throw new Error(`Add-on ${addOnID} not found`)
107
+ }
108
+
109
+ for (const dependsOn of addOn.dependsOn || []) {
110
+ const dep = addOns.find((a) => a.id === dependsOn)
111
+ if (!dep) {
112
+ throw new Error(`Dependency ${dependsOn} not found`)
113
+ }
114
+ finalAddOnIDs.add(dep.id)
115
+ }
116
+ }
117
+
118
+ const finalAddOns = [...finalAddOnIDs].map(
119
+ (id) => addOns.find((a) => a.id === id)!,
120
+ )
121
+ return finalAddOns
122
+ }
123
+
124
+ export async function listAddOns(options: CliOptions) {
125
+ const mode =
126
+ options.template === 'file-router' ? 'file-router' : 'code-router'
127
+ const addOns = await getAllAddOns(
128
+ options.framework || DEFAULT_FRAMEWORK,
129
+ mode,
130
+ )
131
+ for (const addOn of addOns) {
132
+ console.log(`${chalk.bold(addOn.id)}: ${addOn.description}`)
133
+ }
134
+ }
135
+
136
+ function loadAddOn(addOn: AddOn): AddOn {
137
+ return addOn
138
+ }
139
+
140
+ export async function loadRemoteAddOn(url: string): Promise<AddOn> {
141
+ const response = await fetch(url)
142
+ const fileContent = await response.json()
143
+ fileContent.id = url
144
+ return fileContent
145
+ }
package/src/add.ts ADDED
@@ -0,0 +1,184 @@
1
+ import { mkdir, readFile, writeFile } from 'node:fs/promises'
2
+ import { existsSync, statSync } from 'node:fs'
3
+ import { basename, dirname, resolve } from 'node:path'
4
+ import chalk from 'chalk'
5
+ import { execa, execaSync } from 'execa'
6
+ import {
7
+ cancel,
8
+ confirm,
9
+ intro,
10
+ isCancel,
11
+ log,
12
+ outro,
13
+ spinner,
14
+ } from '@clack/prompts'
15
+
16
+ import { CONFIG_FILE } from './constants.js'
17
+ import {
18
+ createDefaultEnvironment,
19
+ createMemoryEnvironment,
20
+ } from './environment.js'
21
+ import { createApp } from './create-app.js'
22
+ import { finalizeAddOns } from './add-ons.js'
23
+ import { sortObject } from './utils.js'
24
+ import { readConfigFile, writeConfigFile } from './config-file.js'
25
+
26
+ import type { PersistedOptions } from './config-file.js'
27
+
28
+ import type { Options } from './types.js'
29
+
30
+ function isDirectory(path: string) {
31
+ return statSync(path).isDirectory()
32
+ }
33
+
34
+ async function hasPendingGitChanges() {
35
+ const status = await execaSync('git', ['status', '--porcelain'])
36
+ return status.stdout.length > 0
37
+ }
38
+
39
+ async function createOptions(
40
+ json: PersistedOptions,
41
+ addOns: Array<string>,
42
+ ): Promise<Required<Options>> {
43
+ return {
44
+ ...json,
45
+ tailwind: true,
46
+ chosenAddOns: await finalizeAddOns(json.framework!, json.mode!, [
47
+ ...json.existingAddOns,
48
+ ...addOns,
49
+ ]),
50
+ } as Required<Options>
51
+ }
52
+
53
+ async function runCreateApp(options: Required<Options>) {
54
+ const { environment, output } = createMemoryEnvironment()
55
+ await createApp(options, {
56
+ silent: true,
57
+ environment,
58
+ cwd: process.cwd(),
59
+ })
60
+ return output
61
+ }
62
+
63
+ export async function add(
64
+ addOns: Array<string>,
65
+ {
66
+ silent = false,
67
+ }: {
68
+ silent?: boolean
69
+ } = {},
70
+ ) {
71
+ const persistedOptions = await readConfigFile(process.cwd())
72
+ if (!persistedOptions) {
73
+ console.error(`${chalk.red('There is no .cta.json file in your project.')}
74
+
75
+ This is probably because this was created with an older version of create-tsrouter-app.`)
76
+ return
77
+ }
78
+
79
+ if (!silent) {
80
+ intro(`Adding ${addOns.join(', ')} to the project...`)
81
+ }
82
+
83
+ if (await hasPendingGitChanges()) {
84
+ log.error(
85
+ `${chalk.red('You have pending git changes.')} Please commit or stash them before adding add-ons.`,
86
+ )
87
+ return
88
+ }
89
+
90
+ const newOptions = await createOptions(persistedOptions, addOns)
91
+
92
+ const output = await runCreateApp(newOptions)
93
+
94
+ const overwrittenFiles: Array<string> = []
95
+ const changedFiles: Array<string> = []
96
+ const contentMap = new Map<string, string>()
97
+ for (const file of Object.keys(output.files)) {
98
+ const relativeFile = file.replace(process.cwd(), '')
99
+ if (existsSync(file)) {
100
+ if (!isDirectory(file)) {
101
+ const contents = (await readFile(file)).toString()
102
+ if (
103
+ ['package.json', CONFIG_FILE].includes(basename(file)) ||
104
+ contents !== output.files[file]
105
+ ) {
106
+ overwrittenFiles.push(relativeFile)
107
+ contentMap.set(relativeFile, output.files[file])
108
+ }
109
+ }
110
+ } else {
111
+ changedFiles.push(relativeFile)
112
+ contentMap.set(relativeFile, output.files[file])
113
+ }
114
+ }
115
+
116
+ if (overwrittenFiles.length > 0 && !silent) {
117
+ log.warn(
118
+ `${chalk.yellow('The following will be overwritten:')}\n${overwrittenFiles.join('\n')}`,
119
+ )
120
+ const shouldContinue = await confirm({
121
+ message: 'Do you want to continue?',
122
+ })
123
+ if (isCancel(shouldContinue)) {
124
+ cancel('Operation cancelled.')
125
+ process.exit(0)
126
+ }
127
+ }
128
+
129
+ for (const file of [...changedFiles, ...overwrittenFiles]) {
130
+ const targetFile = `.${file}`
131
+ const fName = basename(file)
132
+ const contents = contentMap.get(file)!
133
+ if (fName === 'package.json') {
134
+ const currentJson = JSON.parse(
135
+ (await readFile(resolve(fName), 'utf-8')).toString(),
136
+ )
137
+ const newJson = JSON.parse(contents)
138
+
139
+ currentJson.scripts = newJson.scripts
140
+ currentJson.dependencies = sortObject({
141
+ ...currentJson.dependencies,
142
+ ...newJson.dependencies,
143
+ })
144
+ currentJson.devDependencies = sortObject({
145
+ ...currentJson.devDependencies,
146
+ ...newJson.devDependencies,
147
+ })
148
+
149
+ await writeFile(targetFile, JSON.stringify(currentJson, null, 2))
150
+ } else if (fName !== CONFIG_FILE) {
151
+ await mkdir(resolve(dirname(targetFile)), { recursive: true })
152
+ await writeFile(resolve(targetFile), contents)
153
+ }
154
+ }
155
+
156
+ // Handle commands
157
+ const originalOutput = await runCreateApp(
158
+ await createOptions(persistedOptions, []),
159
+ )
160
+ const originalCommands = new Set(
161
+ originalOutput.commands.map((c) => [c.command, ...c.args].join(' ')),
162
+ )
163
+ for (const command of output.commands) {
164
+ const commandString = [command.command, ...command.args].join(' ')
165
+ if (!originalCommands.has(commandString)) {
166
+ await execa(command.command, command.args)
167
+ }
168
+ }
169
+ const realEnvironment = createDefaultEnvironment()
170
+ writeConfigFile(realEnvironment, process.cwd(), newOptions)
171
+
172
+ const s = silent ? null : spinner()
173
+ s?.start(`Installing dependencies via ${newOptions.packageManager}...`)
174
+ await realEnvironment.execute(
175
+ newOptions.packageManager,
176
+ ['install'],
177
+ resolve(process.cwd()),
178
+ )
179
+ s?.stop(`Installed dependencies`)
180
+
181
+ if (!silent) {
182
+ outro('Add-ons added successfully!')
183
+ }
184
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,163 @@
1
+ import { Command, InvalidArgumentError } from 'commander'
2
+ import { intro, log } from '@clack/prompts'
3
+
4
+ import { createApp } from './create-app.js'
5
+ import { normalizeOptions, promptForOptions } from './options.js'
6
+ import { SUPPORTED_PACKAGE_MANAGERS } from './package-manager.js'
7
+ import { SUPPORTED_TOOLCHAINS } from './toolchain.js'
8
+
9
+ import runServer from './mcp.js'
10
+ import { listAddOns } from './add-ons.js'
11
+ import { DEFAULT_FRAMEWORK, SUPPORTED_FRAMEWORKS } from './constants.js'
12
+ // import { initAddOn } from './custom-add-on.js'
13
+
14
+ import { createDefaultEnvironment } from './environment.js'
15
+ // import { add } from './add.js'
16
+
17
+ import type { PackageManager } from './package-manager.js'
18
+ import type { ToolChain } from './toolchain.js'
19
+ import type { CliOptions, Framework } from './types.js'
20
+
21
+ export function cli() {
22
+ const program = new Command()
23
+
24
+ program
25
+ .name('create-tsrouter-app')
26
+ .description('CLI to create a new TanStack application')
27
+
28
+ // program
29
+ // .command('add')
30
+ // .argument('add-on', 'Name of the add-on (or add-ons separated by commas)')
31
+ // .action(async (addOn: string) => {
32
+ // await add(addOn.split(',').map((addon) => addon.trim()))
33
+ // })
34
+
35
+ // program
36
+ // .command('update-add-on')
37
+ // .description('Create or update an add-on from the current project')
38
+ // .action(async () => {
39
+ // await initAddOn('add-on')
40
+ // })
41
+
42
+ // program
43
+ // .command('update-overlay')
44
+ // .description('Create or update a project overlay from the current project')
45
+ // .action(async () => {
46
+ // await initAddOn('overlay')
47
+ // })
48
+
49
+ program // 104 22
50
+ .argument('[project-name]', 'name of the project')
51
+ .option('--no-git', 'do not create a git repository')
52
+ .option('--target-dir <path>', 'the directory to create the project in')
53
+ .option<Framework>(
54
+ '--framework <type>',
55
+ 'project framework (solid, react)',
56
+ (value) => {
57
+ if (!SUPPORTED_FRAMEWORKS.includes(value as Framework)) {
58
+ throw new InvalidArgumentError(
59
+ `Invalid framework: ${value}. Only the following are allowed: ${SUPPORTED_FRAMEWORKS.join(
60
+ ', ',
61
+ )}`,
62
+ )
63
+ }
64
+ return value as Framework
65
+ },
66
+ DEFAULT_FRAMEWORK,
67
+ )
68
+ .option<'typescript' | 'javascript' | 'file-router'>(
69
+ '--template <type>',
70
+ 'project template (typescript, javascript, file-router)',
71
+ (value) => {
72
+ if (
73
+ value !== 'typescript' &&
74
+ value !== 'javascript' &&
75
+ value !== 'file-router'
76
+ ) {
77
+ throw new InvalidArgumentError(
78
+ `Invalid template: ${value}. Only the following are allowed: typescript, javascript, file-router`,
79
+ )
80
+ }
81
+ return value
82
+ },
83
+ )
84
+ .option<PackageManager>(
85
+ `--package-manager <${SUPPORTED_PACKAGE_MANAGERS.join('|')}>`,
86
+ `Explicitly tell the CLI to use this package manager`,
87
+ (value) => {
88
+ if (!SUPPORTED_PACKAGE_MANAGERS.includes(value as PackageManager)) {
89
+ throw new InvalidArgumentError(
90
+ `Invalid package manager: ${value}. The following are allowed: ${SUPPORTED_PACKAGE_MANAGERS.join(
91
+ ', ',
92
+ )}`,
93
+ )
94
+ }
95
+ return value as PackageManager
96
+ },
97
+ )
98
+ .option<ToolChain>(
99
+ `--toolchain <${SUPPORTED_TOOLCHAINS.join('|')}>`,
100
+ `Explicitly tell the CLI to use this toolchain`,
101
+ (value) => {
102
+ if (!SUPPORTED_TOOLCHAINS.includes(value as ToolChain)) {
103
+ throw new InvalidArgumentError(
104
+ `Invalid toolchain: ${value}. The following are allowed: ${SUPPORTED_TOOLCHAINS.join(
105
+ ', ',
106
+ )}`,
107
+ )
108
+ }
109
+ return value as ToolChain
110
+ },
111
+ )
112
+ .option('--tailwind', 'add Tailwind CSS', false)
113
+ .option<Array<string> | boolean>(
114
+ '--add-ons [...add-ons]',
115
+ 'pick from a list of available add-ons (comma separated list)',
116
+ (value: string) => {
117
+ let addOns: Array<string> | boolean = !!value
118
+ if (typeof value === 'string') {
119
+ addOns = value.split(',').map((addon) => addon.trim())
120
+ }
121
+ return addOns
122
+ },
123
+ )
124
+ .option('--list-add-ons', 'list all available add-ons', false)
125
+ .option('--overlay [url]', 'add an overlay from a URL', false)
126
+ .option('--mcp', 'run the MCP server', false)
127
+ .option('--mcp-sse', 'run the MCP server in SSE mode', false)
128
+ .action(async (projectName: string, options: CliOptions) => {
129
+ if (options.listAddOns) {
130
+ await listAddOns(options)
131
+ } else if (options.mcp || options.mcpSse) {
132
+ await runServer(!!options.mcpSse)
133
+ } else {
134
+ try {
135
+ const cliOptions = {
136
+ projectName,
137
+ ...options,
138
+ } as CliOptions
139
+
140
+ let finalOptions = await normalizeOptions(cliOptions)
141
+ if (finalOptions) {
142
+ intro(`Creating a new TanStack app in ${projectName}...`)
143
+ } else {
144
+ intro("Let's configure your TanStack application")
145
+ finalOptions = await promptForOptions(cliOptions)
146
+ }
147
+ await createApp(finalOptions, {
148
+ environment: createDefaultEnvironment(),
149
+ cwd: options.targetDir || undefined,
150
+ })
151
+ } catch (error) {
152
+ log.error(
153
+ error instanceof Error
154
+ ? error.message
155
+ : 'An unknown error occurred',
156
+ )
157
+ process.exit(1)
158
+ }
159
+ }
160
+ })
161
+
162
+ program.parse()
163
+ }
@@ -0,0 +1,45 @@
1
+ import { readFile } from 'node:fs/promises'
2
+ import { resolve } from 'node:path'
3
+
4
+ import { CONFIG_FILE } from './constants.js'
5
+
6
+ import type { Environment, Options } from './types'
7
+
8
+ export type PersistedOptions = Exclude<
9
+ Partial<Options>,
10
+ 'addOns' | 'chosenAddOns'
11
+ > & {
12
+ version: number
13
+ existingAddOns: Array<string>
14
+ }
15
+
16
+ export async function writeConfigFile(
17
+ environment: Environment,
18
+ targetDir: string,
19
+ options: Options,
20
+ ) {
21
+ const persistedOptions: PersistedOptions = {
22
+ ...options,
23
+ version: 1,
24
+ existingAddOns: options.chosenAddOns.map((addOn) => addOn.id),
25
+ }
26
+ delete persistedOptions.addOns
27
+ delete persistedOptions.chosenAddOns
28
+
29
+ await environment.writeFile(
30
+ resolve(targetDir, CONFIG_FILE),
31
+ JSON.stringify(persistedOptions, null, 2),
32
+ )
33
+ }
34
+
35
+ export async function readConfigFile(
36
+ targetDir: string,
37
+ ): Promise<PersistedOptions | null> {
38
+ try {
39
+ const configFile = resolve(targetDir, CONFIG_FILE)
40
+ const config = await readFile(configFile, 'utf8')
41
+ return JSON.parse(config)
42
+ } catch {
43
+ return null
44
+ }
45
+ }
@@ -0,0 +1,9 @@
1
+ import type { Framework } from './types.js'
2
+
3
+ export const CODE_ROUTER = 'code-router'
4
+ export const FILE_ROUTER = 'file-router'
5
+
6
+ export const SUPPORTED_FRAMEWORKS: Array<Framework> = ['solid', 'react']
7
+ export const DEFAULT_FRAMEWORK: Framework = 'react'
8
+
9
+ export const CONFIG_FILE = '.cta.json'