@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,326 @@
1
+ import { readFile, readdir } from 'node:fs/promises'
2
+ import { existsSync, mkdirSync, writeFileSync } from 'node:fs'
3
+ import { basename, dirname, resolve } from 'node:path'
4
+ import chalk from 'chalk'
5
+
6
+ import { createMemoryEnvironment } from './environment.js'
7
+ import { createApp } from './create-app.js'
8
+ import { readConfigFile } from './config-file.js'
9
+ import { finalizeAddOns } from './add-ons.js'
10
+
11
+ import type { Framework, Options } from './types.js'
12
+ import type { PersistedOptions } from './config-file.js'
13
+
14
+ type AddOnMode = 'add-on' | 'overlay'
15
+
16
+ const INFO_FILE: Record<AddOnMode, string> = {
17
+ 'add-on': '.add-on/info.json',
18
+ overlay: 'overlay-info.json',
19
+ }
20
+ const COMPILED_FILE: Record<AddOnMode, string> = {
21
+ 'add-on': 'add-on.json',
22
+ overlay: 'overlay.json',
23
+ }
24
+
25
+ const ADD_ON_DIR = '.add-on'
26
+ const ASSETS_DIR = 'assets'
27
+
28
+ const IGNORE_FILES = [
29
+ ADD_ON_DIR,
30
+ 'node_modules',
31
+ 'dist',
32
+ 'build',
33
+ '.git',
34
+ 'pnpm-lock.yaml',
35
+ 'package-lock.json',
36
+ 'yarn.lock',
37
+ 'bun.lockb',
38
+ 'bun.lock',
39
+ 'deno.lock',
40
+ 'add-on.json',
41
+ 'add-on-info.json',
42
+ 'package.json',
43
+ ]
44
+
45
+ const ADD_ON_IGNORE_FILES: Array<string> = [
46
+ 'main.jsx',
47
+ 'App.jsx',
48
+ 'main.tsx',
49
+ 'App.tsx',
50
+ 'routeTree.gen.ts',
51
+ ]
52
+
53
+ function templatize(routeCode: string, routeFile: string) {
54
+ let code = routeCode
55
+
56
+ // Replace the import
57
+ code = code.replace(
58
+ /import { createFileRoute } from '@tanstack\/react-router'/g,
59
+ `import { <% if (fileRouter) { %>createFileRoute<% } else { %>createRoute<% } %> } from '@tanstack/react-router'`,
60
+ )
61
+
62
+ // Extract route path and definition, then transform the route declaration
63
+ const routeMatch = code.match(
64
+ /export\s+const\s+Route\s*=\s*createFileRoute\(['"]([^'"]+)['"]\)\s*\(\{([^}]+)\}\)/,
65
+ )
66
+
67
+ let path = ''
68
+
69
+ if (routeMatch) {
70
+ const fullMatch = routeMatch[0]
71
+ path = routeMatch[1]
72
+ const routeDefinition = routeMatch[2]
73
+ code = code.replace(
74
+ fullMatch,
75
+ `<% if (codeRouter) { %>
76
+ import type { RootRoute } from '@tanstack/react-router'
77
+ <% } else { %>
78
+ export const Route = createFileRoute('${path}')({${routeDefinition}})
79
+ <% } %>`,
80
+ )
81
+
82
+ code += `
83
+ <% if (codeRouter) { %>
84
+ export default (parentRoute: RootRoute) => createRoute({
85
+ path: '${path}',
86
+ ${routeDefinition}
87
+ getParentRoute: () => parentRoute,
88
+ })
89
+ <% } %>
90
+ `
91
+ } else {
92
+ console.error(`No route found in the file: ${routeFile}`)
93
+ }
94
+
95
+ const name = basename(path)
96
+ .replace('.tsx', '')
97
+ .replace(/^demo/, '')
98
+ .replace('.', ' ')
99
+ .trim()
100
+
101
+ return { url: path, code, name }
102
+ }
103
+
104
+ async function createOptions(
105
+ json: PersistedOptions,
106
+ ): Promise<Required<Options>> {
107
+ return {
108
+ ...json,
109
+ chosenAddOns: await finalizeAddOns(
110
+ json.framework as Framework,
111
+ json.mode as string,
112
+ [...json.existingAddOns],
113
+ ),
114
+ } as Required<Options>
115
+ }
116
+
117
+ async function runCreateApp(options: Required<Options>) {
118
+ const { environment, output } = createMemoryEnvironment()
119
+ await createApp(options, {
120
+ silent: true,
121
+ environment,
122
+ cwd: process.cwd(),
123
+ name: 'create-tsrouter-app',
124
+ })
125
+ return output
126
+ }
127
+
128
+ async function recursivelyGatherFiles(
129
+ path: string,
130
+ files: Record<string, string>,
131
+ ) {
132
+ const dirFiles = await readdir(path, { withFileTypes: true })
133
+ for (const file of dirFiles) {
134
+ if (file.isDirectory()) {
135
+ await recursivelyGatherFiles(resolve(path, file.name), files)
136
+ } else {
137
+ files[resolve(path, file.name)] = (
138
+ await readFile(resolve(path, file.name))
139
+ ).toString()
140
+ }
141
+ }
142
+ }
143
+
144
+ async function compareFiles(
145
+ path: string,
146
+ ignore: Array<string>,
147
+ original: Record<string, string>,
148
+ changedFiles: Record<string, string>,
149
+ ) {
150
+ const files = await readdir(path, { withFileTypes: true })
151
+ for (const file of files) {
152
+ const filePath = `${path}/${file.name}`
153
+ if (!ignore.includes(file.name)) {
154
+ if (file.isDirectory()) {
155
+ await compareFiles(filePath, ignore, original, changedFiles)
156
+ } else {
157
+ const contents = (await readFile(filePath)).toString()
158
+ const absolutePath = resolve(process.cwd(), filePath)
159
+ if (!original[absolutePath] || original[absolutePath] !== contents) {
160
+ changedFiles[filePath] = contents
161
+ }
162
+ }
163
+ }
164
+ }
165
+ }
166
+
167
+ export async function initAddOn(mode: AddOnMode) {
168
+ const persistedOptions = await readConfigFile(process.cwd())
169
+ if (!persistedOptions) {
170
+ console.error(`${chalk.red('There is no .cta.json file in your project.')}
171
+
172
+ This is probably because this was created with an older version of create-tsrouter-app.`)
173
+ return
174
+ }
175
+
176
+ if (mode === 'add-on') {
177
+ if (persistedOptions.mode !== 'file-router') {
178
+ console.error(`${chalk.red('This project is not using file-router mode.')}
179
+
180
+ To create an add-on, the project must be created with the file-router mode.`)
181
+ return
182
+ }
183
+ if (!persistedOptions.tailwind) {
184
+ console.error(`${chalk.red('This project is not using Tailwind CSS.')}
185
+
186
+ To create an add-on, the project must be created with Tailwind CSS.`)
187
+ return
188
+ }
189
+ if (!persistedOptions.typescript) {
190
+ console.error(`${chalk.red('This project is not using TypeScript.')}
191
+
192
+ To create an add-on, the project must be created with TypeScript.`)
193
+ return
194
+ }
195
+ }
196
+
197
+ const info = existsSync(INFO_FILE[mode])
198
+ ? JSON.parse((await readFile(INFO_FILE[mode])).toString())
199
+ : {
200
+ name: `${persistedOptions.projectName}-${mode}`,
201
+ version: '0.0.1',
202
+ description: mode === 'add-on' ? 'Add-on' : 'Project overlay',
203
+ author: 'Jane Smith <jane.smith@example.com>',
204
+ license: 'MIT',
205
+ link: `https://github.com/jane-smith/${persistedOptions.projectName}-${mode}`,
206
+ command: {},
207
+ shadcnComponents: [],
208
+ templates: [persistedOptions.mode],
209
+ routes: [],
210
+ warning: '',
211
+ variables: {},
212
+ phase: 'add-on',
213
+ type: mode,
214
+ packageAdditions: {
215
+ scripts: {},
216
+ dependencies: {},
217
+ devDependencies: {},
218
+ },
219
+ }
220
+
221
+ const compiledInfo = JSON.parse(JSON.stringify(info))
222
+
223
+ const originalOutput = await runCreateApp(
224
+ await createOptions(persistedOptions),
225
+ )
226
+
227
+ const originalPackageJson = JSON.parse(
228
+ originalOutput.files[resolve(process.cwd(), 'package.json')],
229
+ )
230
+ const currentPackageJson = JSON.parse(
231
+ (await readFile('package.json')).toString(),
232
+ )
233
+
234
+ for (const script of Object.keys(currentPackageJson.scripts)) {
235
+ if (
236
+ originalPackageJson.scripts[script] !== currentPackageJson.scripts[script]
237
+ ) {
238
+ info.packageAdditions.scripts[script] = currentPackageJson.scripts[script]
239
+ }
240
+ }
241
+
242
+ const dependencies: Record<string, string> = {}
243
+ for (const dependency of Object.keys(currentPackageJson.dependencies)) {
244
+ if (
245
+ originalPackageJson.dependencies[dependency] !==
246
+ currentPackageJson.dependencies[dependency]
247
+ ) {
248
+ dependencies[dependency] = currentPackageJson.dependencies[dependency]
249
+ }
250
+ }
251
+ info.packageAdditions.dependencies = dependencies
252
+
253
+ const devDependencies: Record<string, string> = {}
254
+ for (const dependency of Object.keys(currentPackageJson.devDependencies)) {
255
+ if (
256
+ originalPackageJson.devDependencies[dependency] !==
257
+ currentPackageJson.devDependencies[dependency]
258
+ ) {
259
+ devDependencies[dependency] =
260
+ currentPackageJson.devDependencies[dependency]
261
+ }
262
+ }
263
+ info.packageAdditions.devDependencies = devDependencies
264
+
265
+ // Find altered files
266
+ const changedFiles: Record<string, string> = {}
267
+ await compareFiles('.', IGNORE_FILES, originalOutput.files, changedFiles)
268
+ if (mode === 'overlay') {
269
+ compiledInfo.files = changedFiles
270
+ } else {
271
+ const assetsDir = resolve(ADD_ON_DIR, ASSETS_DIR)
272
+ if (!existsSync(assetsDir)) {
273
+ await compareFiles('.', IGNORE_FILES, originalOutput.files, changedFiles)
274
+ for (const file of Object.keys(changedFiles).filter(
275
+ (file) => !ADD_ON_IGNORE_FILES.includes(basename(file)),
276
+ )) {
277
+ mkdirSync(dirname(resolve(assetsDir, file)), {
278
+ recursive: true,
279
+ })
280
+ if (file.includes('/routes/')) {
281
+ const { url, code, name } = templatize(changedFiles[file], file)
282
+ info.routes.push({
283
+ url,
284
+ name,
285
+ })
286
+ writeFileSync(resolve(assetsDir, `${file}.ejs`), code)
287
+ } else {
288
+ writeFileSync(resolve(assetsDir, file), changedFiles[file])
289
+ }
290
+ }
291
+ }
292
+ const addOnFiles: Record<string, string> = {}
293
+ await recursivelyGatherFiles(assetsDir, addOnFiles)
294
+ compiledInfo.files = Object.keys(addOnFiles).reduce(
295
+ (acc, file) => {
296
+ acc[file.replace(assetsDir, '.')] = addOnFiles[file]
297
+ return acc
298
+ },
299
+ {} as Record<string, string>,
300
+ )
301
+ }
302
+
303
+ compiledInfo.routes = info.routes
304
+ compiledInfo.framework = persistedOptions.framework
305
+ compiledInfo.addDependencies = persistedOptions.existingAddOns
306
+
307
+ if (mode === 'overlay') {
308
+ compiledInfo.mode = persistedOptions.mode
309
+ compiledInfo.typescript = persistedOptions.typescript
310
+ compiledInfo.tailwind = persistedOptions.tailwind
311
+
312
+ compiledInfo.deletedFiles = []
313
+ for (const file of Object.keys(originalOutput.files)) {
314
+ if (!existsSync(file)) {
315
+ compiledInfo.deletedFiles.push(file.replace(process.cwd(), '.'))
316
+ }
317
+ }
318
+ }
319
+
320
+ if (!existsSync(resolve(INFO_FILE[mode]))) {
321
+ mkdirSync(resolve(dirname(INFO_FILE[mode])), { recursive: true })
322
+ writeFileSync(INFO_FILE[mode], JSON.stringify(info, null, 2))
323
+ }
324
+
325
+ writeFileSync(COMPILED_FILE[mode], JSON.stringify(compiledInfo, null, 2))
326
+ }
@@ -0,0 +1,144 @@
1
+ import {
2
+ appendFile,
3
+ copyFile,
4
+ mkdir,
5
+ readFile,
6
+ unlink,
7
+ writeFile,
8
+ } from 'node:fs/promises'
9
+ import { existsSync, readdirSync, statSync } from 'node:fs'
10
+ import { dirname } from 'node:path'
11
+ import { execa } from 'execa'
12
+ import { memfs } from 'memfs'
13
+
14
+ import type { Environment } from './types.js'
15
+
16
+ export function createDefaultEnvironment(): Environment {
17
+ let errors: Array<string> = []
18
+ return {
19
+ startRun: () => {
20
+ errors = []
21
+ },
22
+ finishRun: () => {},
23
+ getErrors: () => errors,
24
+
25
+ appendFile: async (path: string, contents: string) => {
26
+ await mkdir(dirname(path), { recursive: true })
27
+ return appendFile(path, contents)
28
+ },
29
+ copyFile: async (from: string, to: string) => {
30
+ await mkdir(dirname(to), { recursive: true })
31
+ return copyFile(from, to)
32
+ },
33
+ writeFile: async (path: string, contents: string) => {
34
+ await mkdir(dirname(path), { recursive: true })
35
+ return writeFile(path, contents)
36
+ },
37
+ execute: async (command: string, args: Array<string>, cwd: string) => {
38
+ try {
39
+ await execa(command, args, {
40
+ cwd,
41
+ })
42
+ } catch {
43
+ errors.push(
44
+ `Command "${command} ${args.join(' ')}" did not run successfully. Please run this manually in your project.`,
45
+ )
46
+ }
47
+ },
48
+ deleteFile: async (path: string) => {
49
+ await unlink(path)
50
+ },
51
+
52
+ readFile: (path: string, encoding?: BufferEncoding) =>
53
+ readFile(path, { encoding: encoding || 'utf8' }),
54
+ exists: (path: string) => existsSync(path),
55
+ readdir: (path) => readdirSync(path),
56
+ isDirectory: (path) => {
57
+ const stat = statSync(path)
58
+ return stat.isDirectory()
59
+ },
60
+ }
61
+ }
62
+
63
+ export function createMemoryEnvironment() {
64
+ const environment = createDefaultEnvironment()
65
+
66
+ const output: {
67
+ files: Record<string, string>
68
+ commands: Array<{
69
+ command: string
70
+ args: Array<string>
71
+ }>
72
+ } = {
73
+ files: {},
74
+ commands: [],
75
+ }
76
+
77
+ const { fs, vol } = memfs({})
78
+
79
+ const cwd = process.cwd()
80
+ const isInCwd = (path: string) => path.startsWith(cwd)
81
+ const isTemplatePath = (path: string) => !isInCwd(path)
82
+
83
+ environment.appendFile = async (path: string, contents: string) => {
84
+ fs.mkdirSync(dirname(path), { recursive: true })
85
+ await fs.appendFileSync(path, contents)
86
+ }
87
+ environment.copyFile = async (from: string, to: string) => {
88
+ if (isTemplatePath(from)) {
89
+ const contents = (await readFile(from)).toString()
90
+ fs.mkdirSync(dirname(to), { recursive: true })
91
+ fs.writeFileSync(to, contents)
92
+ } else {
93
+ fs.mkdirSync(dirname(to), { recursive: true })
94
+ fs.copyFileSync(from, to)
95
+ }
96
+ }
97
+ environment.execute = async (command: string, args: Array<string>) => {
98
+ output.commands.push({
99
+ command,
100
+ args,
101
+ })
102
+ return Promise.resolve()
103
+ }
104
+ environment.readFile = async (path: string, encoding?: BufferEncoding) => {
105
+ if (isTemplatePath(path)) {
106
+ return (await readFile(path, encoding)).toString()
107
+ }
108
+ return fs.readFileSync(path, 'utf8').toString()
109
+ }
110
+ environment.writeFile = async (path: string, contents: string) => {
111
+ fs.mkdirSync(dirname(path), { recursive: true })
112
+ await fs.writeFileSync(path, contents)
113
+ }
114
+ environment.deleteFile = async (path: string) => {
115
+ await fs.unlinkSync(path)
116
+ }
117
+ environment.exists = (path: string) => {
118
+ if (isTemplatePath(path)) {
119
+ return existsSync(path)
120
+ }
121
+ return fs.existsSync(path)
122
+ }
123
+ environment.readdir = (path: string) => {
124
+ if (isTemplatePath(path)) {
125
+ return readdirSync(path)
126
+ }
127
+ return fs.readdirSync(path).map((file) => file.toString())
128
+ }
129
+ environment.isDirectory = (path) => {
130
+ if (isTemplatePath(path)) {
131
+ const stat = statSync(path)
132
+ return stat.isDirectory()
133
+ }
134
+ return fs.statSync(path).isDirectory()
135
+ }
136
+ environment.finishRun = () => {
137
+ output.files = vol.toJSON() as Record<string, string>
138
+ }
139
+
140
+ return {
141
+ environment,
142
+ output,
143
+ }
144
+ }
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export { cli } from './cli.js'