kaddidlehopper 0.4.0 → 0.6.0

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 (297) hide show
  1. package/.claude/settings.local.json +7 -0
  2. package/dist/cli.js +104 -207
  3. package/dist/index.js +1 -31
  4. package/dist/types/cli.d.ts +1 -8
  5. package/dist/types/types.d.ts +7 -12
  6. package/package.json +1 -2
  7. package/src/cli.ts +129 -270
  8. package/src/index.ts +1 -47
  9. package/src/types.ts +7 -13
  10. package/CONTEXT.md +0 -139
  11. package/add-ons/ai/README.md +0 -34
  12. package/add-ons/ai/assets/AGENTS.md.append +0 -24
  13. package/add-ons/ai/assets/_dot_env.local.append +0 -13
  14. package/add-ons/ai/assets/src/components/AIAssistant.tsx +0 -149
  15. package/add-ons/ai/assets/src/lib/ai-hook.ts +0 -21
  16. package/add-ons/ai/assets/src/lib/weather-tools.ts +0 -30
  17. package/add-ons/ai/assets/src/routes/api.chat.ts +0 -94
  18. package/add-ons/ai/assets/src/routes/chat.css +0 -175
  19. package/add-ons/ai/assets/src/routes/chat.tsx +0 -141
  20. package/add-ons/ai/info.json +0 -21
  21. package/add-ons/ai/package.json +0 -17
  22. package/add-ons/ai/small-logo.svg +0 -8
  23. package/add-ons/db/assets/AGENTS.md.append +0 -22
  24. package/add-ons/db/assets/DB-SETUP.md +0 -65
  25. package/add-ons/db/assets/_dot_env.local.append +0 -2
  26. package/add-ons/db/assets/drizzle.config.ts +0 -10
  27. package/add-ons/db/assets/src/db/index.ts +0 -8
  28. package/add-ons/db/assets/src/db/schema.ts +0 -8
  29. package/add-ons/db/assets/src/routes/db-example.tsx +0 -118
  30. package/add-ons/db/assets/src/server/guestbook.functions.ts +0 -23
  31. package/add-ons/db/info.json +0 -21
  32. package/add-ons/db/package.json +0 -10
  33. package/add-ons/forms/assets/AGENTS.md.append +0 -13
  34. package/add-ons/forms/assets/public/form-example.html +0 -14
  35. package/add-ons/forms/assets/src/routes/form-example.tsx +0 -76
  36. package/add-ons/forms/info.json +0 -17
  37. package/add-ons/forms/package.json +0 -3
  38. package/examples/ai-chat/README.md +0 -36
  39. package/examples/ai-chat/assets/AGENTS.md.append +0 -24
  40. package/examples/ai-chat/assets/src/lib/ai-hook.ts +0 -21
  41. package/examples/ai-chat/assets/src/lib/weather-tools.ts +0 -30
  42. package/examples/ai-chat/assets/src/routes/__root.tsx +0 -57
  43. package/examples/ai-chat/assets/src/routes/api.chat.ts +0 -94
  44. package/examples/ai-chat/assets/src/routes/index.tsx +0 -141
  45. package/examples/ai-chat/assets/src/styles.css +0 -165
  46. package/examples/ai-chat/info.json +0 -24
  47. package/examples/ai-chat/package.json +0 -14
  48. package/examples/blog/README.md +0 -60
  49. package/examples/blog/assets/AGENTS.md.append +0 -18
  50. package/examples/blog/assets/content/posts/beach.md +0 -12
  51. package/examples/blog/assets/content/posts/jungle.md.ejs +0 -12
  52. package/examples/blog/assets/content/posts/mountains.md.ejs +0 -12
  53. package/examples/blog/assets/content/posts/snorkeling.md.ejs +0 -12
  54. package/examples/blog/assets/content/posts/waterfall.md.ejs +0 -12
  55. package/examples/blog/assets/content-collections.ts +0 -30
  56. package/examples/blog/assets/public/beach.jpg +0 -0
  57. package/examples/blog/assets/public/jungle.jpg +0 -0
  58. package/examples/blog/assets/public/mountains.jpg +0 -0
  59. package/examples/blog/assets/public/snorkeling.jpg +0 -0
  60. package/examples/blog/assets/public/waterfall.jpg +0 -0
  61. package/examples/blog/assets/src/components/Header.tsx +0 -52
  62. package/examples/blog/assets/src/components/VacayAssistant.tsx +0 -205
  63. package/examples/blog/assets/src/components/blog-posts.tsx +0 -78
  64. package/examples/blog/assets/src/components/ui/card.tsx +0 -92
  65. package/examples/blog/assets/src/lib/blog-ai-hook.ts +0 -25
  66. package/examples/blog/assets/src/lib/blog-tools.ts +0 -111
  67. package/examples/blog/assets/src/lib/utils.ts +0 -6
  68. package/examples/blog/assets/src/routes/__root.tsx +0 -57
  69. package/examples/blog/assets/src/routes/api.blog-chat.ts +0 -117
  70. package/examples/blog/assets/src/routes/category.$category.tsx +0 -19
  71. package/examples/blog/assets/src/routes/index.tsx +0 -19
  72. package/examples/blog/assets/src/routes/posts.$slug.tsx +0 -63
  73. package/examples/blog/assets/src/styles.css +0 -138
  74. package/examples/blog/info.json +0 -39
  75. package/examples/blog/package.json +0 -23
  76. package/examples/calculator/README.md +0 -16
  77. package/examples/calculator/assets/AGENTS.md.append +0 -9
  78. package/examples/calculator/assets/src/components/Calculator.tsx +0 -238
  79. package/examples/calculator/assets/src/components/Header.tsx +0 -17
  80. package/examples/calculator/assets/src/routes/__root.tsx +0 -57
  81. package/examples/calculator/assets/src/routes/index.tsx +0 -14
  82. package/examples/calculator/info.json +0 -19
  83. package/examples/calculator/package.json +0 -1
  84. package/examples/dashboard/README.md +0 -17
  85. package/examples/dashboard/assets/AGENTS.md.append +0 -12
  86. package/examples/dashboard/assets/src/components/Header.tsx +0 -17
  87. package/examples/dashboard/assets/src/routes/__root.tsx +0 -57
  88. package/examples/dashboard/assets/src/routes/index.tsx +0 -158
  89. package/examples/dashboard/info.json +0 -18
  90. package/examples/dashboard/package.json +0 -6
  91. package/examples/ecommerce/README.md +0 -48
  92. package/examples/ecommerce/assets/AGENTS.md.append +0 -22
  93. package/examples/ecommerce/assets/public/logo.png +0 -0
  94. package/examples/ecommerce/assets/public/motorcycle-scooter.jpg +0 -0
  95. package/examples/ecommerce/assets/src/components/BuyButton.tsx +0 -35
  96. package/examples/ecommerce/assets/src/components/Header.tsx +0 -36
  97. package/examples/ecommerce/assets/src/components/MotorcycleAIAssistant.tsx +0 -162
  98. package/examples/ecommerce/assets/src/components/MotorcycleRecommendation.tsx +0 -53
  99. package/examples/ecommerce/assets/src/data/motorcycles.ts +0 -27
  100. package/examples/ecommerce/assets/src/lib/motorcycle-ai-hook.ts +0 -24
  101. package/examples/ecommerce/assets/src/lib/motorcycle-tools.ts +0 -42
  102. package/examples/ecommerce/assets/src/lib/stripe.server.ts +0 -39
  103. package/examples/ecommerce/assets/src/routes/__root.tsx +0 -57
  104. package/examples/ecommerce/assets/src/routes/api.motorcycle-chat.ts +0 -78
  105. package/examples/ecommerce/assets/src/routes/checkout/cancel.tsx +0 -25
  106. package/examples/ecommerce/assets/src/routes/checkout/success.tsx +0 -25
  107. package/examples/ecommerce/assets/src/routes/index.tsx +0 -76
  108. package/examples/ecommerce/assets/src/routes/motorcycles/$motorcycleId.tsx +0 -55
  109. package/examples/ecommerce/assets/src/store/motorcycle-assistant.ts +0 -3
  110. package/examples/ecommerce/assets/src/styles.css +0 -212
  111. package/examples/ecommerce/info.json +0 -37
  112. package/examples/ecommerce/package.json +0 -13
  113. package/examples/events/README.md +0 -110
  114. package/examples/events/assets/AGENTS.md.append +0 -21
  115. package/examples/events/assets/content/speakers/andre-costa.md +0 -22
  116. package/examples/events/assets/content/speakers/hans-mueller.md.ejs +0 -22
  117. package/examples/events/assets/content/speakers/isabella-martinez.md.ejs +0 -22
  118. package/examples/events/assets/content/speakers/kenji-nakamura.md.ejs +0 -22
  119. package/examples/events/assets/content/speakers/marie-dubois.md.ejs +0 -20
  120. package/examples/events/assets/content/speakers/priya-sharma.md.ejs +0 -22
  121. package/examples/events/assets/content/talks/croissant-lamination-secrets.md +0 -39
  122. package/examples/events/assets/content/talks/french-macaron-mastery.md.ejs +0 -39
  123. package/examples/events/assets/content/talks/neapolitan-pizza-tradition-meets-innovation.md.ejs +0 -39
  124. package/examples/events/assets/content/talks/savory-breads-of-the-mediterranean.md.ejs +0 -39
  125. package/examples/events/assets/content/talks/sourdough-from-starter-to-masterpiece.md.ejs +0 -36
  126. package/examples/events/assets/content/talks/the-art-of-the-perfect-tart.md.ejs +0 -32
  127. package/examples/events/assets/content/talks/the-science-of-sugar.md.ejs +0 -39
  128. package/examples/events/assets/content/talks/umami-in-pastry-east-meets-west.md.ejs +0 -39
  129. package/examples/events/assets/content-collections.ts +0 -56
  130. package/examples/events/assets/public/background-1.jpg +0 -0
  131. package/examples/events/assets/public/background-2.jpg +0 -0
  132. package/examples/events/assets/public/background-3.jpg +0 -0
  133. package/examples/events/assets/public/background-4.jpg +0 -0
  134. package/examples/events/assets/public/conference-logo.png +0 -0
  135. package/examples/events/assets/public/favicon.ico +0 -0
  136. package/examples/events/assets/public/speakers/andre-costa.jpg +0 -0
  137. package/examples/events/assets/public/speakers/hans-mueller.jpg +0 -0
  138. package/examples/events/assets/public/speakers/isabella-martinez.jpg +0 -0
  139. package/examples/events/assets/public/speakers/kenji-nakamura.jpg +0 -0
  140. package/examples/events/assets/public/speakers/marie-dubois.jpg +0 -0
  141. package/examples/events/assets/public/speakers/priya-sharma.jpg +0 -0
  142. package/examples/events/assets/public/talks/croissant-lamination-secrets.jpg +0 -0
  143. package/examples/events/assets/public/talks/french-macaron-mastery.jpg +0 -0
  144. package/examples/events/assets/public/talks/neapolitan-pizza-tradition-meets-innovation.jpg +0 -0
  145. package/examples/events/assets/public/talks/savory-breads-of-the-mediterranean.jpg +0 -0
  146. package/examples/events/assets/public/talks/sourdough-from-starter-to-masterpiece.jpg +0 -0
  147. package/examples/events/assets/public/talks/the-art-of-the-perfect-tart.jpg +0 -0
  148. package/examples/events/assets/public/talks/the-science-of-sugar.jpg +0 -0
  149. package/examples/events/assets/public/talks/umami-in-pastry-east-meets-west.jpg +0 -0
  150. package/examples/events/assets/public/tanstack-circle-logo.png +0 -0
  151. package/examples/events/assets/public/tanstack-word-logo-white.svg +0 -1
  152. package/examples/events/assets/src/components/Header.tsx +0 -59
  153. package/examples/events/assets/src/components/HeaderNav.tsx +0 -67
  154. package/examples/events/assets/src/components/HeroCarousel.tsx +0 -61
  155. package/examples/events/assets/src/components/RemyAssistant.tsx +0 -207
  156. package/examples/events/assets/src/components/SpeakerCard.tsx +0 -67
  157. package/examples/events/assets/src/components/TalkCard.tsx +0 -77
  158. package/examples/events/assets/src/components/ui/card.tsx +0 -92
  159. package/examples/events/assets/src/lib/conference-ai-hook.ts +0 -26
  160. package/examples/events/assets/src/lib/conference-tools.ts +0 -210
  161. package/examples/events/assets/src/lib/model-selection.ts +0 -1
  162. package/examples/events/assets/src/lib/utils.ts +0 -6
  163. package/examples/events/assets/src/routes/__root.tsx +0 -70
  164. package/examples/events/assets/src/routes/api.remy-chat.ts +0 -119
  165. package/examples/events/assets/src/routes/index.tsx +0 -192
  166. package/examples/events/assets/src/routes/schedule.index.tsx +0 -274
  167. package/examples/events/assets/src/routes/speakers.$slug.tsx +0 -122
  168. package/examples/events/assets/src/routes/speakers.index.tsx +0 -40
  169. package/examples/events/assets/src/routes/talks.$slug.tsx +0 -116
  170. package/examples/events/assets/src/routes/talks.index.tsx +0 -40
  171. package/examples/events/assets/src/styles.css +0 -182
  172. package/examples/events/info.json +0 -60
  173. package/examples/events/package.json +0 -23
  174. package/examples/marketing/README.md +0 -60
  175. package/examples/marketing/assets/AGENTS.md.append +0 -15
  176. package/examples/marketing/assets/public/logo.png +0 -0
  177. package/examples/marketing/assets/public/motorcycle-adventure.jpg +0 -0
  178. package/examples/marketing/assets/public/motorcycle-cruiser.jpg +0 -0
  179. package/examples/marketing/assets/public/motorcycle-scooter.jpg +0 -0
  180. package/examples/marketing/assets/public/motorcycle-sport.jpg +0 -0
  181. package/examples/marketing/assets/public/motorcycle-supersport.jpg +0 -0
  182. package/examples/marketing/assets/src/components/Header.tsx +0 -36
  183. package/examples/marketing/assets/src/components/MotorcycleAIAssistant.tsx +0 -162
  184. package/examples/marketing/assets/src/components/MotorcycleRecommendation.tsx +0 -53
  185. package/examples/marketing/assets/src/data/motorcycles.ts.ejs +0 -77
  186. package/examples/marketing/assets/src/lib/motorcycle-ai-hook.ts +0 -24
  187. package/examples/marketing/assets/src/lib/motorcycle-tools.ts +0 -42
  188. package/examples/marketing/assets/src/routes/__root.tsx +0 -57
  189. package/examples/marketing/assets/src/routes/api.motorcycle-chat.ts +0 -78
  190. package/examples/marketing/assets/src/routes/index.tsx +0 -72
  191. package/examples/marketing/assets/src/routes/motorcycles/$motorcycleId.tsx +0 -56
  192. package/examples/marketing/assets/src/store/motorcycle-assistant.ts +0 -3
  193. package/examples/marketing/assets/src/styles.css +0 -212
  194. package/examples/marketing/info.json +0 -32
  195. package/examples/marketing/package.json +0 -14
  196. package/examples/portfolio/README.md +0 -49
  197. package/examples/portfolio/assets/AGENTS.md.append +0 -21
  198. package/examples/portfolio/assets/content/blog/getting-started-with-tanstack.md +0 -53
  199. package/examples/portfolio/assets/content/blog/react-19-features.md +0 -78
  200. package/examples/portfolio/assets/content/blog/tailwind-css-v4-guide.md +0 -60
  201. package/examples/portfolio/assets/content/education/code-school.md +0 -17
  202. package/examples/portfolio/assets/content/jobs/initech-junior.md +0 -20
  203. package/examples/portfolio/assets/content/projects/portfolio-site.md +0 -15
  204. package/examples/portfolio/assets/content/projects/task-manager.md +0 -15
  205. package/examples/portfolio/assets/content-collections.ts +0 -65
  206. package/examples/portfolio/assets/public/contact.html +0 -6
  207. package/examples/portfolio/assets/public/headshot-on-white.jpg +0 -0
  208. package/examples/portfolio/assets/src/components/Header.tsx +0 -33
  209. package/examples/portfolio/assets/src/components/ResumeAssistant.tsx +0 -193
  210. package/examples/portfolio/assets/src/components/ui/badge.tsx +0 -46
  211. package/examples/portfolio/assets/src/components/ui/card.tsx +0 -92
  212. package/examples/portfolio/assets/src/components/ui/checkbox.tsx +0 -30
  213. package/examples/portfolio/assets/src/components/ui/hover-card.tsx +0 -44
  214. package/examples/portfolio/assets/src/components/ui/separator.tsx +0 -26
  215. package/examples/portfolio/assets/src/lib/resume-ai-hook.ts +0 -21
  216. package/examples/portfolio/assets/src/lib/resume-tools.ts +0 -165
  217. package/examples/portfolio/assets/src/lib/utils.ts +0 -6
  218. package/examples/portfolio/assets/src/routes/__root.tsx +0 -57
  219. package/examples/portfolio/assets/src/routes/api.resume-chat.ts +0 -116
  220. package/examples/portfolio/assets/src/routes/blog/$slug.tsx +0 -73
  221. package/examples/portfolio/assets/src/routes/contact.tsx +0 -121
  222. package/examples/portfolio/assets/src/routes/index.tsx +0 -55
  223. package/examples/portfolio/assets/src/routes/projects.tsx +0 -62
  224. package/examples/portfolio/assets/src/routes/resume.tsx +0 -220
  225. package/examples/portfolio/assets/src/styles.css +0 -138
  226. package/examples/portfolio/info.json +0 -49
  227. package/examples/portfolio/package.json +0 -26
  228. package/examples/resume/README.md +0 -82
  229. package/examples/resume/assets/AGENTS.md.append +0 -19
  230. package/examples/resume/assets/content/education/code-school.md +0 -17
  231. package/examples/resume/assets/content/jobs/freelance.md.ejs +0 -13
  232. package/examples/resume/assets/content/jobs/initech-junior.md +0 -20
  233. package/examples/resume/assets/content/jobs/initech-lead.md.ejs +0 -29
  234. package/examples/resume/assets/content/jobs/initrode-senior.md.ejs +0 -28
  235. package/examples/resume/assets/content-collections.ts +0 -36
  236. package/examples/resume/assets/public/headshot-on-white.jpg +0 -0
  237. package/examples/resume/assets/src/components/Header.tsx +0 -33
  238. package/examples/resume/assets/src/components/ResumeAssistant.tsx +0 -193
  239. package/examples/resume/assets/src/components/ui/badge.tsx +0 -46
  240. package/examples/resume/assets/src/components/ui/card.tsx +0 -92
  241. package/examples/resume/assets/src/components/ui/checkbox.tsx +0 -30
  242. package/examples/resume/assets/src/components/ui/hover-card.tsx +0 -44
  243. package/examples/resume/assets/src/components/ui/separator.tsx +0 -26
  244. package/examples/resume/assets/src/lib/resume-ai-hook.ts +0 -21
  245. package/examples/resume/assets/src/lib/resume-tools.ts +0 -165
  246. package/examples/resume/assets/src/lib/utils.ts +0 -6
  247. package/examples/resume/assets/src/routes/api.resume-chat.ts +0 -110
  248. package/examples/resume/assets/src/routes/index.tsx +0 -220
  249. package/examples/resume/assets/src/styles.css +0 -138
  250. package/examples/resume/info.json +0 -34
  251. package/examples/resume/package.json +0 -26
  252. package/examples/saas/README.md +0 -16
  253. package/examples/saas/assets/AGENTS.md.append +0 -9
  254. package/examples/saas/assets/src/components/Header.tsx +0 -17
  255. package/examples/saas/assets/src/routes/__root.tsx +0 -57
  256. package/examples/saas/assets/src/routes/faq.tsx +0 -94
  257. package/examples/saas/assets/src/routes/index.tsx +0 -197
  258. package/examples/saas/info.json +0 -22
  259. package/examples/saas/package.json +0 -1
  260. package/examples/survey/README.md +0 -20
  261. package/examples/survey/assets/AGENTS.md.append +0 -9
  262. package/examples/survey/assets/public/form-survey.html +0 -15
  263. package/examples/survey/assets/src/components/SurveyForm.tsx +0 -128
  264. package/examples/survey/assets/src/routes/index.tsx +0 -14
  265. package/examples/survey/info.json +0 -18
  266. package/examples/survey/package.json +0 -1
  267. package/project/base/AGENTS.md +0 -86
  268. package/project/base/_dot_claude/skills/content-collections/SKILL.md +0 -505
  269. package/project/base/_dot_claude/skills/netlify-blobs/SKILL.md +0 -410
  270. package/project/base/_dot_claude/skills/netlify-db/SKILL.md +0 -424
  271. package/project/base/_dot_claude/skills/netlify-debugging/SKILL.md +0 -419
  272. package/project/base/_dot_claude/skills/netlify-forms/SKILL.md +0 -243
  273. package/project/base/_dot_claude/skills/netlify-functions/SKILL.md +0 -372
  274. package/project/base/_dot_claude/skills/tanstack-start-api-routes/SKILL.md +0 -421
  275. package/project/base/_dot_claude/skills/tanstack-start-loaders/SKILL.md +0 -426
  276. package/project/base/_dot_claude/skills/tanstack-start-project-setup/SKILL.md +0 -493
  277. package/project/base/_dot_claude/skills/tanstack-start-routes/SKILL.md +0 -430
  278. package/project/base/_dot_claude/skills/tanstack-start-server-functions/SKILL.md +0 -445
  279. package/project/base/_dot_claude/skills/tanstack-start-typesafe-routing/SKILL.md +0 -494
  280. package/project/base/_dot_gitignore +0 -8
  281. package/project/base/netlify.toml +0 -7
  282. package/project/base/package.json +0 -33
  283. package/project/base/public/favicon.ico +0 -0
  284. package/project/base/public/tanstack-circle-logo.png +0 -0
  285. package/project/base/public/tanstack-word-logo-white.svg +0 -1
  286. package/project/base/src/components/Header.tsx +0 -17
  287. package/project/base/src/components/HeaderNav.tsx.ejs +0 -179
  288. package/project/base/src/router.tsx +0 -15
  289. package/project/base/src/routes/__root.tsx +0 -57
  290. package/project/base/src/routes/index.tsx +0 -48
  291. package/project/base/src/styles.css +0 -15
  292. package/project/base/tsconfig.json +0 -28
  293. package/project/base/vite.config.ts.ejs +0 -25
  294. package/project/info.json +0 -17
  295. package/project/packages.json +0 -22
  296. package/scripts/check-outdated-packages.js +0 -421
  297. package/src/agents-md.ts +0 -139
package/src/cli.ts CHANGED
@@ -1,29 +1,19 @@
1
- import { resolve, basename, join } from 'node:path'
2
- import { unlink } from 'node:fs/promises'
3
- import { Command, InvalidArgumentError } from 'commander'
1
+ import { resolve, basename } from 'node:path'
2
+ import { existsSync, readdirSync } from 'node:fs'
3
+ import { cp, rm, mkdtemp, readFile, writeFile } from 'node:fs/promises'
4
+ import { tmpdir } from 'node:os'
5
+ import { join } from 'node:path'
6
+ import { execSync } from 'node:child_process'
7
+ import { Command } from 'commander'
4
8
  import chalk from 'chalk'
5
9
  import validatePackageName from 'validate-npm-package-name'
6
10
 
7
- import {
8
- SUPPORTED_PACKAGE_MANAGERS,
9
- createApp,
10
- createDefaultEnvironment,
11
- finalizeAddOns,
12
- getAllAddOns,
13
- getFrameworkByName,
14
- getPackageManager,
15
- DEFAULT_PACKAGE_MANAGER,
16
- populateAddOnOptionsDefaults,
17
- } from '@tanstack/cta-engine'
18
-
19
- import { generateAgentsMd } from './agents-md.js'
20
11
  import type { CliOptions } from './types.js'
21
- import type {
22
- Options,
23
- PackageManager,
24
- } from '@tanstack/cta-engine'
25
12
 
26
- // Utility functions
13
+ const GITHUB_REPO = 'https://github.com/jherr/kdh-templates.git'
14
+ const MANIFEST_URL =
15
+ 'https://raw.githubusercontent.com/jherr/kdh-templates/main/manifest.json'
16
+
27
17
  function sanitizePackageName(name: string): string {
28
18
  return name
29
19
  .toLowerCase()
@@ -52,302 +42,171 @@ function validateProjectName(name: string) {
52
42
  }
53
43
  }
54
44
 
55
- // Create environment with UI functions for console output
56
- function createEnvironment(appName: string) {
57
- const env = createDefaultEnvironment()
58
- env.appName = appName
59
- env.intro = (message: string) => console.log(chalk.bold.cyan(message))
60
- env.outro = (message: string) => console.log(chalk.bold.green(message))
61
- env.info = (title?: string, message?: string) => {
62
- if (title && message) {
63
- console.log(chalk.blue(`${title}: ${message}`))
64
- } else if (title) {
65
- console.log(chalk.blue(title))
66
- }
67
- }
68
- env.error = (title?: string, message?: string) => {
69
- if (title && message) {
70
- console.error(chalk.red(`${title}: ${message}`))
71
- } else if (title) {
72
- console.error(chalk.red(title))
73
- }
74
- }
75
- env.warn = (title?: string, message?: string) => {
76
- if (title && message) {
77
- console.warn(chalk.yellow(`${title}: ${message}`))
78
- } else if (title) {
79
- console.warn(chalk.yellow(title))
80
- }
81
- }
82
- env.spinner = () => ({
83
- start: (message: string) => console.log(chalk.gray(`⟳ ${message}`)),
84
- stop: (message: string) => console.log(chalk.green(`✓ ${message}`)),
85
- })
86
- return env
45
+ function getPackageManagerFromUserAgent(): string | undefined {
46
+ const userAgent = process.env.npm_config_user_agent
47
+ if (!userAgent) return undefined
48
+ const pmSpec = userAgent.split(' ')[0]
49
+ const separatorPos = pmSpec.lastIndexOf('/')
50
+ return separatorPos !== -1 ? pmSpec.substring(0, separatorPos) : pmSpec
87
51
  }
88
52
 
89
- export interface CliConfig {
90
- name: string
91
- appName: string
92
- defaultFramework: string
93
- forcedMode?: string
94
- forcedAddOns?: Array<string>
95
- }
96
-
97
- export function cli({
98
- name,
99
- appName,
100
- defaultFramework,
101
- forcedMode,
102
- forcedAddOns = [],
103
- }: CliConfig) {
104
- const environment = createEnvironment(appName)
53
+ export function cli() {
105
54
  const program = new Command()
106
55
 
107
- const defaultMode = forcedMode || 'file-router'
108
-
109
- program.name(name).description(`CLI to create a new ${appName} application`)
56
+ program
57
+ .name('netlify-cta')
58
+ .description('CLI to create a new Netlify TanStack Start application')
110
59
 
111
60
  program.argument('[project-name]', 'name of the project')
112
61
 
113
62
  program
114
63
  .option('--no-install', 'skip installing dependencies')
115
- .option<PackageManager>(
116
- `--package-manager <${SUPPORTED_PACKAGE_MANAGERS.join('|')}>`,
117
- `Explicitly tell the CLI to use this package manager`,
118
- (value) => {
119
- if (!SUPPORTED_PACKAGE_MANAGERS.includes(value as PackageManager)) {
120
- throw new InvalidArgumentError(
121
- `Invalid package manager: ${value}. The following are allowed: ${SUPPORTED_PACKAGE_MANAGERS.join(', ')}`,
122
- )
123
- }
124
- return value as PackageManager
125
- },
126
- )
127
- .option<Array<string> | boolean>(
128
- '--add-ons [...add-ons]',
129
- 'pick from a list of available add-ons (comma separated list)',
130
- (value: string) => {
131
- let addOns: Array<string> | boolean = !!value
132
- if (typeof value === 'string') {
133
- addOns = value.split(',').map((addon) => addon.trim())
134
- }
135
- return addOns
136
- },
137
- )
138
- .option('--list-add-ons', 'list all available add-ons', false)
139
- .option('--list-addons-json', 'list all available add-ons as JSON', false)
140
64
  .option(
141
- '--addon-details <addon-id>',
142
- 'show detailed information about a specific add-on',
65
+ '--package-manager <pm>',
66
+ 'explicitly tell the CLI to use this package manager',
143
67
  )
68
+ .option('--add-ons [id]', 'starter ID to use from the template repo')
69
+ .option('--list-addons-json', 'list all available starters as JSON', false)
144
70
  .option('--no-git', 'do not create a git repository')
145
71
  .option(
146
72
  '--target-dir <path>',
147
73
  'the target directory for the application root',
148
- '.',
149
74
  )
150
75
  .option(
151
76
  '-f, --force',
152
77
  'force project creation even if the target directory is not empty',
153
- true,
154
78
  )
155
- .option(
156
- '--no-force',
157
- 'refuse to create if the target directory is not empty',
158
- )
159
- .option(
160
- '--bare-bones',
161
- 'create minimal scaffolding for LLM modification',
162
- true,
163
- )
164
- .option(
165
- '--no-bare-bones',
166
- 'create full scaffolding (not minimal)',
167
- )
168
-
169
- program.action(async (projectName: string, options: CliOptions) => {
170
- const framework = getFrameworkByName(defaultFramework)
171
- if (!framework) {
172
- console.error(`Framework '${defaultFramework}' not found`)
173
- process.exit(1)
174
- }
175
79
 
80
+ program.action(async (projectName: string | undefined, options: CliOptions) => {
176
81
  // Handle --list-addons-json
177
82
  if (options.listAddonsJson) {
178
- const addOns = await getAllAddOns(framework, defaultMode)
179
- const serialized = addOns
180
- .filter((a) => !forcedAddOns.includes(a.id))
181
- .map((addon) => ({
182
- id: addon.id,
183
- name: addon.name,
184
- description: addon.description,
185
- type: addon.type,
186
- options: addon.options,
187
- }))
188
- console.log(JSON.stringify(serialized, null, 2))
189
- return
83
+ const manifest = await fetch(MANIFEST_URL).then((r) => r.json()) as { starters: unknown }
84
+ console.log(JSON.stringify(manifest.starters, null, 2))
85
+ process.exit(0)
190
86
  }
191
87
 
192
- // Handle --list-add-ons (text format)
193
- if (options.listAddOns) {
194
- const addOns = await getAllAddOns(framework, defaultMode)
195
- let hasConfigurableAddOns = false
196
- for (const addOn of addOns.filter((a) => !forcedAddOns.includes(a.id))) {
197
- const hasOptions =
198
- addOn.options && Object.keys(addOn.options).length > 0
199
- const optionMarker = hasOptions ? '*' : ' '
200
- if (hasOptions) hasConfigurableAddOns = true
201
- console.log(
202
- `${optionMarker} ${chalk.bold(addOn.id)}: ${addOn.description}`,
203
- )
204
- }
205
- if (hasConfigurableAddOns) {
206
- console.log('\n* = has configuration options')
207
- }
208
- return
209
- }
88
+ // Resolve starter ID
89
+ const starterId =
90
+ typeof options.addOns === 'string' ? options.addOns : 'basic'
210
91
 
211
- // Handle --addon-details
212
- if (options.addonDetails) {
213
- const addOns = await getAllAddOns(framework, defaultMode)
214
- const addOn = addOns.find((a) => a.id === options.addonDetails)
215
- if (!addOn) {
216
- console.error(`Add-on '${options.addonDetails}' not found`)
217
- process.exit(1)
218
- }
219
-
220
- console.log(
221
- `${chalk.bold.cyan('Add-on Details:')} ${chalk.bold(addOn.name)}`,
222
- )
223
- console.log(`${chalk.bold('ID:')} ${addOn.id}`)
224
- console.log(`${chalk.bold('Description:')} ${addOn.description}`)
225
- console.log(`${chalk.bold('Type:')} ${addOn.type}`)
226
- console.log(`${chalk.bold('Phase:')} ${addOn.phase}`)
227
- console.log(`${chalk.bold('Supported Modes:')} ${addOn.modes.join(', ')}`)
228
-
229
- if (addOn.dependsOn && addOn.dependsOn.length > 0) {
230
- console.log(
231
- `${chalk.bold('Dependencies:')} ${addOn.dependsOn.join(', ')}`,
232
- )
233
- }
234
-
235
- if (addOn.options && Object.keys(addOn.options).length > 0) {
236
- console.log(`\n${chalk.bold.yellow('Configuration Options:')}`)
237
- for (const [optionName, option] of Object.entries(addOn.options)) {
238
- if (option && typeof option === 'object' && 'type' in option) {
239
- const opt = option as { label: string; description?: string; type: string; default: unknown; options?: Array<{ value: string; label: string }> }
240
- console.log(` ${chalk.bold(optionName)}:`)
241
- console.log(` Label: ${opt.label}`)
242
- if (opt.description) {
243
- console.log(` Description: ${opt.description}`)
244
- }
245
- console.log(` Type: ${opt.type}`)
246
- console.log(` Default: ${opt.default}`)
247
- if (opt.type === 'select' && opt.options) {
248
- console.log(` Available values:`)
249
- for (const choice of opt.options) {
250
- console.log(` - ${choice.value}: ${choice.label}`)
251
- }
252
- }
253
- }
254
- }
255
- } else {
256
- console.log(`\n${chalk.gray('No configuration options available')}`)
257
- }
258
-
259
- if (addOn.routes && addOn.routes.length > 0) {
260
- console.log(`\n${chalk.bold.green('Routes:')}`)
261
- for (const route of addOn.routes) {
262
- console.log(` ${chalk.bold(route.url)} (${route.name})`)
263
- console.log(` File: ${route.path}`)
264
- }
265
- }
266
- return
267
- }
268
-
269
- // Handle project creation: target-dir defaults to current directory
270
- const targetDir = resolve(options.targetDir ?? '.')
271
- const isCurrentDir = targetDir === resolve(process.cwd())
92
+ // Resolve target directory — default to CWD
93
+ const targetDir = options.targetDir ? resolve(options.targetDir) : resolve(process.cwd())
272
94
 
95
+ // Resolve project name for package.json
273
96
  let resolvedProjectName: string
274
- if (projectName) {
275
- resolvedProjectName =
276
- projectName === '.'
277
- ? sanitizePackageName(getCurrentDirectoryName())
278
- : sanitizePackageName(projectName)
279
- } else if (isCurrentDir) {
280
- resolvedProjectName = sanitizePackageName(getCurrentDirectoryName())
97
+ if (projectName && projectName !== '.') {
98
+ resolvedProjectName = sanitizePackageName(projectName)
281
99
  } else {
282
- console.error('Project name is required when --target-dir is not the current directory')
283
- program.help()
284
- return
100
+ resolvedProjectName = sanitizePackageName(getCurrentDirectoryName())
285
101
  }
286
102
 
287
103
  const { valid, error } = validateProjectName(resolvedProjectName)
288
104
  if (!valid) {
289
- console.error(error)
105
+ console.error(chalk.red(error))
290
106
  process.exit(1)
291
107
  }
292
108
 
293
- // Determine selected add-ons
294
- const selectedAddOns = new Set<string>([...forcedAddOns])
295
- if (options.addOns && Array.isArray(options.addOns)) {
296
- for (const a of options.addOns) {
297
- selectedAddOns.add(a)
109
+ // Check if target directory exists and is non-empty
110
+ if (existsSync(targetDir)) {
111
+ const contents = readdirSync(targetDir)
112
+ if (contents.length > 0 && !options.force) {
113
+ console.error(
114
+ chalk.red(
115
+ `Target directory "${targetDir}" is not empty. Use --force to overwrite.`,
116
+ ),
117
+ )
118
+ process.exit(1)
298
119
  }
299
120
  }
300
121
 
301
- const chosenAddOns = await finalizeAddOns(
302
- framework,
303
- defaultMode,
304
- Array.from(selectedAddOns),
122
+ console.log(
123
+ chalk.bold.cyan(
124
+ `Creating a new Netlify TanStack Start app in ${chalk.white(targetDir)}...`,
125
+ ),
305
126
  )
127
+ console.log(chalk.gray(`Using starter: ${starterId}`))
128
+
129
+ // Sparse clone the template repo into a temp directory
130
+ const tmpDir = await mkdtemp(join(tmpdir(), 'netlify-cta-'))
131
+ try {
132
+ console.log(chalk.gray('⟳ Fetching template...'))
133
+ execSync(
134
+ `git clone --depth=1 --filter=blob:none --sparse ${GITHUB_REPO} ${tmpDir}`,
135
+ { stdio: 'pipe' },
136
+ )
137
+ execSync(
138
+ `git -C ${tmpDir} sparse-checkout set starters/${starterId}`,
139
+ { stdio: 'pipe' },
140
+ )
306
141
 
307
- const finalOptions: Options = {
308
- projectName: resolvedProjectName,
309
- targetDir,
310
- framework,
311
- mode: defaultMode,
312
- typescript: true,
313
- tailwind: true,
314
- packageManager:
315
- options.packageManager ||
316
- getPackageManager() ||
317
- DEFAULT_PACKAGE_MANAGER,
318
- git: options.git !== false,
319
- install: options.install !== false,
320
- chosenAddOns,
321
- addOnOptions: {
322
- ...populateAddOnOptionsDefaults(chosenAddOns),
323
- project: { bareBones: options.bareBones ?? true },
324
- },
325
- }
142
+ const starterPath = join(tmpDir, 'starters', starterId)
143
+ if (!existsSync(starterPath)) {
144
+ console.error(
145
+ chalk.red(
146
+ `Starter "${starterId}" not found in the template repo. Run --list-addons-json to see available starters.`,
147
+ ),
148
+ )
149
+ process.exit(1)
150
+ }
326
151
 
327
- environment.intro(`Creating a new ${appName} app in ${resolvedProjectName}...`)
328
- await createApp(environment, finalOptions)
152
+ // Copy starter files to target directory
153
+ await cp(starterPath, targetDir, { recursive: true })
329
154
 
330
- // Delete files specified in bareBones.deleteFiles for each add-on when in bare-bones mode
331
- if (options.bareBones !== false) {
332
- for (const addOn of chosenAddOns) {
333
- const addOnWithBareBones = addOn as typeof addOn & {
334
- bareBones?: { deleteFiles?: Array<string> }
335
- }
336
- if (addOnWithBareBones.bareBones?.deleteFiles) {
337
- for (const file of addOnWithBareBones.bareBones.deleteFiles) {
338
- const filePath = join(targetDir, file)
339
- try {
340
- await unlink(filePath)
341
- } catch {
342
- // File may not exist, ignore errors
343
- }
344
- }
155
+ // Update package.json name if a project name was provided
156
+ if (projectName && projectName !== '.') {
157
+ const pkgPath = join(targetDir, 'package.json')
158
+ if (existsSync(pkgPath)) {
159
+ const pkg = JSON.parse(await readFile(pkgPath, 'utf-8'))
160
+ pkg.name = resolvedProjectName
161
+ await writeFile(pkgPath, JSON.stringify(pkg, null, 2) + '\n')
345
162
  }
346
163
  }
164
+
165
+ console.log(chalk.green(`✓ Template copied`))
166
+ } finally {
167
+ await rm(tmpDir, { recursive: true, force: true })
168
+ }
169
+
170
+ // Initialize git repository
171
+ if (options.git !== false) {
172
+ try {
173
+ execSync('git init', { cwd: targetDir, stdio: 'pipe' })
174
+ console.log(chalk.green('✓ Initialized git repository'))
175
+ } catch {
176
+ console.warn(chalk.yellow('⚠ Could not initialize git repository'))
177
+ }
178
+ }
179
+
180
+ // Install dependencies
181
+ if (options.install !== false) {
182
+ const pm =
183
+ options.packageManager ??
184
+ getPackageManagerFromUserAgent() ??
185
+ 'pnpm'
186
+ console.log(chalk.gray(`⟳ Installing dependencies with ${pm}...`))
187
+ try {
188
+ execSync(`${pm} install`, { cwd: targetDir, stdio: 'inherit' })
189
+ console.log(chalk.green('✓ Dependencies installed'))
190
+ } catch {
191
+ console.error(
192
+ chalk.red(
193
+ `Failed to install dependencies. Run \`${pm} install\` manually.`,
194
+ ),
195
+ )
196
+ }
347
197
  }
348
198
 
349
- // Generate an AGENTS.md file in the root of the project
350
- await generateAgentsMd(targetDir, resolvedProjectName, chosenAddOns as Array<{ packageFileDescriptions?: Record<string, string> }>)
199
+ console.log(chalk.bold.green('\nDone! Your project is ready.'))
200
+ console.log(chalk.white('\nNext steps:'))
201
+ console.log(chalk.cyan(` cd ${basename(targetDir)}`))
202
+ if (options.install === false) {
203
+ const pm =
204
+ options.packageManager ??
205
+ getPackageManagerFromUserAgent() ??
206
+ 'pnpm'
207
+ console.log(chalk.cyan(` ${pm} install`))
208
+ }
209
+ console.log(chalk.cyan(' pnpm dev'))
351
210
  })
352
211
 
353
212
  program.parse()
package/src/index.ts CHANGED
@@ -1,49 +1,3 @@
1
1
  #!/usr/bin/env node
2
- import { dirname, join } from 'node:path'
3
- import { fileURLToPath } from 'node:url'
4
-
5
- import {
6
- registerFramework,
7
- scanAddOnDirectories,
8
- scanProjectDirectory,
9
- } from '@tanstack/cta-engine'
10
2
  import { cli } from './cli.js'
11
-
12
- const projectDirectory = join(
13
- dirname(dirname(fileURLToPath(import.meta.url))),
14
- 'project',
15
- )
16
-
17
- const addOns = scanAddOnDirectories([
18
- join(dirname(dirname(fileURLToPath(import.meta.url))), 'add-ons'),
19
- join(dirname(dirname(fileURLToPath(import.meta.url))), 'examples'),
20
- ])
21
-
22
- const { files, basePackageJSON, optionalPackages } = scanProjectDirectory(
23
- projectDirectory,
24
- join(dirname(dirname(fileURLToPath(import.meta.url))), 'project', 'base'),
25
- )
26
-
27
- registerFramework({
28
- id: 'netlify-start',
29
- name: 'Netlify TanStack Start',
30
- description: 'TanStack Start applications for Netlify deployment',
31
- version: '0.1.0',
32
- base: files,
33
- addOns,
34
- basePackageJSON,
35
- optionalPackages,
36
- supportedModes: {
37
- 'file-router': {
38
- displayName: 'File Router',
39
- description: 'File-based routing with TanStack Start',
40
- forceTypescript: true,
41
- },
42
- },
43
- })
44
-
45
- cli({
46
- name: 'netlify-cta',
47
- appName: 'Netlify TanStack Start',
48
- defaultFramework: 'Netlify TanStack Start',
49
- })
3
+ cli()
package/src/types.ts CHANGED
@@ -1,15 +1,9 @@
1
- import type { PackageManager } from '@tanstack/cta-engine'
2
-
3
1
  export interface CliOptions {
4
- projectName?: string
5
- packageManager?: PackageManager
6
- git?: boolean
7
- addOns?: Array<string> | boolean
8
- listAddOns?: boolean
9
- listAddonsJson?: boolean
10
- addonDetails?: string
11
- targetDir?: string
12
- install?: boolean
13
- force?: boolean
14
- bareBones?: boolean
2
+ install: boolean
3
+ packageManager: string | undefined
4
+ addOns: string | boolean | undefined
5
+ listAddonsJson: boolean
6
+ git: boolean
7
+ force: boolean | undefined
8
+ targetDir: string | undefined
15
9
  }
package/CONTEXT.md DELETED
@@ -1,139 +0,0 @@
1
- # Netlify CTA - Project Context
2
-
3
- ## Overview
4
-
5
- `netlify-cta` is a custom CLI application that creates TanStack Start applications pre-configured for Netlify deployment. It's based on the `create-rwsdk` example pattern but builds TanStack Start apps instead of Redwood SDK apps.
6
-
7
- ## What Was Built
8
-
9
- ### CLI Structure
10
-
11
- ```
12
- ./
13
- ├── package.json # CLI package (workspace:* deps)
14
- ├── tsconfig.json # TypeScript config
15
- ├── src/
16
- │ └── index.ts # CLI entry point
17
- ├── project/
18
- │ ├── packages.json # Conditional dependencies
19
- │ └── base/
20
- │ ├── package.json # Base project dependencies
21
- │ ├── vite.config.ts # Vite + Start + Netlify + Tailwind
22
- │ ├── tsconfig.json # TypeScript config
23
- │ ├── netlify.toml # Netlify deployment config
24
- │ ├── _dot_gitignore # Git ignore (becomes .gitignore)
25
- │ ├── public/
26
- │ │ └── favicon.ico
27
- │ └── src/
28
- │ ├── styles.css # Tailwind CSS imports
29
- │ ├── router.tsx # TanStack Router config
30
- │ └── routes/
31
- │ ├── __root.tsx # Root layout with SSR shell
32
- │ └── index.tsx # Home page - "Hello from Netlify"
33
- └── add-ons/
34
- └── _dot_gitkeep # Placeholder for future add-ons
35
- ```
36
-
37
- ### Key Features
38
-
39
- 1. **TanStack Start**: Full SSR support with `@tanstack/react-start`
40
- 2. **Netlify Deployment**: Pre-configured with `@netlify/vite-plugin-tanstack-start`
41
- 3. **Tailwind CSS**: Installed and configured out of the box
42
- 4. **File-based Routing**: Uses TanStack Router's file-based routing (TypeScript enforced)
43
- 5. **Minimal Setup**: Just a home route saying "Hello from Netlify" - no demo routes
44
-
45
- ### Framework Registration
46
-
47
- The CLI registers a framework called "Netlify TanStack Start" with:
48
- - ID: `netlify-start`
49
- - Single mode: `file-router` (TypeScript enforced)
50
- - No customized UI (uses default CTA UI)
51
-
52
- ## Usage
53
-
54
- ## Dependencies
55
-
56
- ### CLI Dependencies
57
- - `@tanstack/cta-cli`: workspace:*
58
- - `@tanstack/cta-engine`: workspace:*
59
-
60
- ### Generated Project Dependencies
61
- - `@tanstack/react-start`: ^1.132.0
62
- - `@tanstack/react-router`: ^1.132.0
63
- - `@netlify/vite-plugin-tanstack-start`: ^1.2.3
64
- - `tailwindcss`: ^4.0.6
65
- - `react`: ^19.2.0
66
- - `vite`: ^7.1.7
67
-
68
- ## Files Reference
69
-
70
- ### src/index.ts
71
- Registers the framework and starts the CLI. Key points:
72
- - Scans `project/` directory for base files
73
- - Scans `add-ons/` directory for add-ons (currently empty)
74
- - Registers single "file-router" mode with forced TypeScript
75
-
76
- ### project/base/vite.config.ts
77
- Configures Vite with:
78
- - `@tanstack/devtools-vite` - TanStack devtools
79
- - `vite-tsconfig-paths` - Path alias support
80
- - `@tailwindcss/vite` - Tailwind CSS
81
- - `@netlify/vite-plugin-tanstack-start` - Netlify deployment
82
- - `@tanstack/react-start/plugin/vite` - TanStack Start
83
- - `@vitejs/plugin-react` - React support
84
-
85
- ### project/base/src/routes/__root.tsx
86
- Root layout using TanStack Start's `shellComponent` for SSR:
87
- - Sets up HTML document structure
88
- - Configures head meta tags
89
- - Includes TanStack devtools
90
- - Renders children with Scripts for hydration
91
-
92
- ### project/base/src/routes/index.tsx
93
- Simple home page with:
94
- - Teal/cyan gradient background
95
- - "Hello from Netlify" heading
96
- - Links to TanStack Start and Netlify docs
97
- - Edit instruction pointing to the file
98
-
99
- ## Examples
100
-
101
- Four example applications are included:
102
-
103
- ### Blog Example (`examples/blog/`)
104
- A Hawaii adventures travel blog built with content-collections and shadcn UI components.
105
- - Content-collections for markdown blog posts
106
- - Category-based navigation
107
- - Postcard-style blog cards with beautiful UI
108
- - Routes: `/`, `/posts/$slug`, `/category/$category`
109
-
110
- ### Events Example (`examples/events/`)
111
- A pastry conference website ("Haute Pâtisserie 2026") with speakers, sessions, schedule, and AI assistant.
112
- - Content-collections for speakers and talks markdown content
113
- - Conference schedule with day-by-day timeline
114
- - AI-powered chat assistant (Remy) for conference navigation
115
- - Beautiful dark theme with Playfair Display font and copper/gold accents
116
- - Routes: `/`, `/schedule`, `/speakers`, `/speakers/$slug`, `/talks`, `/talks/$slug`, `/api/remy-chat`
117
-
118
- ### Marketing Example (`examples/marketing/`)
119
- An AI-powered motorcycle marketing site with TanStack AI chat assistant.
120
- - TanStack AI for intelligent chat
121
- - TanStack Store for state management
122
- - Motorcycle product catalog
123
- - AI-powered product recommendations
124
- - Routes: `/`, `/motorcycles/$motorcycleId`, `/api/motorcycle-chat`
125
-
126
- ### Resume Example (`examples/resume/`)
127
- A professional resume template with content-collections and shadcn UI components.
128
- - Content-collections for jobs and education
129
- - Interactive skills filter sidebar
130
- - Multiple shadcn UI components (badge, card, checkbox, hover-card, separator)
131
- - Routes: `/`
132
-
133
- ## Future Enhancements
134
-
135
- Potential additions:
136
- - Add-ons for common Netlify features (Functions, Edge Functions, Blobs)
137
- - Authentication add-ons (Netlify Identity)
138
- - Database add-ons (Netlify DB, Supabase, etc.)
139
- - Customized UI with Netlify branding