kaddidlehopper 0.5.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 (296) hide show
  1. package/dist/cli.js +104 -208
  2. package/dist/index.js +1 -31
  3. package/dist/types/cli.d.ts +1 -8
  4. package/dist/types/types.d.ts +7 -12
  5. package/package.json +1 -2
  6. package/src/cli.ts +129 -271
  7. package/src/index.ts +1 -47
  8. package/src/types.ts +7 -13
  9. package/CONTEXT.md +0 -139
  10. package/add-ons/ai/README.md +0 -34
  11. package/add-ons/ai/assets/AGENTS.md.append +0 -24
  12. package/add-ons/ai/assets/_dot_env.local.append +0 -13
  13. package/add-ons/ai/assets/src/components/AIAssistant.tsx +0 -149
  14. package/add-ons/ai/assets/src/lib/ai-hook.ts +0 -21
  15. package/add-ons/ai/assets/src/lib/weather-tools.ts +0 -30
  16. package/add-ons/ai/assets/src/routes/api.chat.ts +0 -94
  17. package/add-ons/ai/assets/src/routes/chat.css +0 -175
  18. package/add-ons/ai/assets/src/routes/chat.tsx +0 -141
  19. package/add-ons/ai/info.json +0 -22
  20. package/add-ons/ai/package.json +0 -17
  21. package/add-ons/ai/small-logo.svg +0 -8
  22. package/add-ons/db/assets/AGENTS.md.append +0 -22
  23. package/add-ons/db/assets/DB-SETUP.md +0 -65
  24. package/add-ons/db/assets/_dot_env.local.append +0 -2
  25. package/add-ons/db/assets/drizzle.config.ts +0 -10
  26. package/add-ons/db/assets/src/db/index.ts +0 -8
  27. package/add-ons/db/assets/src/db/schema.ts +0 -8
  28. package/add-ons/db/assets/src/routes/db-example.tsx +0 -118
  29. package/add-ons/db/assets/src/server/guestbook.functions.ts +0 -23
  30. package/add-ons/db/info.json +0 -22
  31. package/add-ons/db/package.json +0 -10
  32. package/add-ons/forms/assets/AGENTS.md.append +0 -13
  33. package/add-ons/forms/assets/public/form-example.html +0 -14
  34. package/add-ons/forms/assets/src/routes/form-example.tsx +0 -76
  35. package/add-ons/forms/info.json +0 -18
  36. package/add-ons/forms/package.json +0 -3
  37. package/examples/ai-chat/README.md +0 -36
  38. package/examples/ai-chat/assets/AGENTS.md.append +0 -24
  39. package/examples/ai-chat/assets/src/lib/ai-hook.ts +0 -21
  40. package/examples/ai-chat/assets/src/lib/weather-tools.ts +0 -30
  41. package/examples/ai-chat/assets/src/routes/__root.tsx +0 -57
  42. package/examples/ai-chat/assets/src/routes/api.chat.ts +0 -94
  43. package/examples/ai-chat/assets/src/routes/index.tsx +0 -141
  44. package/examples/ai-chat/assets/src/styles.css +0 -165
  45. package/examples/ai-chat/info.json +0 -25
  46. package/examples/ai-chat/package.json +0 -14
  47. package/examples/blog/README.md +0 -60
  48. package/examples/blog/assets/AGENTS.md.append +0 -18
  49. package/examples/blog/assets/content/posts/beach.md +0 -12
  50. package/examples/blog/assets/content/posts/jungle.md.ejs +0 -12
  51. package/examples/blog/assets/content/posts/mountains.md.ejs +0 -12
  52. package/examples/blog/assets/content/posts/snorkeling.md.ejs +0 -12
  53. package/examples/blog/assets/content/posts/waterfall.md.ejs +0 -12
  54. package/examples/blog/assets/content-collections.ts +0 -30
  55. package/examples/blog/assets/public/beach.jpg +0 -0
  56. package/examples/blog/assets/public/jungle.jpg +0 -0
  57. package/examples/blog/assets/public/mountains.jpg +0 -0
  58. package/examples/blog/assets/public/snorkeling.jpg +0 -0
  59. package/examples/blog/assets/public/waterfall.jpg +0 -0
  60. package/examples/blog/assets/src/components/Header.tsx +0 -52
  61. package/examples/blog/assets/src/components/VacayAssistant.tsx +0 -205
  62. package/examples/blog/assets/src/components/blog-posts.tsx +0 -78
  63. package/examples/blog/assets/src/components/ui/card.tsx +0 -92
  64. package/examples/blog/assets/src/lib/blog-ai-hook.ts +0 -25
  65. package/examples/blog/assets/src/lib/blog-tools.ts +0 -111
  66. package/examples/blog/assets/src/lib/utils.ts +0 -6
  67. package/examples/blog/assets/src/routes/__root.tsx +0 -57
  68. package/examples/blog/assets/src/routes/api.blog-chat.ts +0 -117
  69. package/examples/blog/assets/src/routes/category.$category.tsx +0 -19
  70. package/examples/blog/assets/src/routes/index.tsx +0 -19
  71. package/examples/blog/assets/src/routes/posts.$slug.tsx +0 -63
  72. package/examples/blog/assets/src/styles.css +0 -138
  73. package/examples/blog/info.json +0 -40
  74. package/examples/blog/package.json +0 -23
  75. package/examples/calculator/README.md +0 -16
  76. package/examples/calculator/assets/AGENTS.md.append +0 -9
  77. package/examples/calculator/assets/src/components/Calculator.tsx +0 -238
  78. package/examples/calculator/assets/src/components/Header.tsx +0 -17
  79. package/examples/calculator/assets/src/routes/__root.tsx +0 -57
  80. package/examples/calculator/assets/src/routes/index.tsx +0 -14
  81. package/examples/calculator/info.json +0 -20
  82. package/examples/calculator/package.json +0 -1
  83. package/examples/dashboard/README.md +0 -17
  84. package/examples/dashboard/assets/AGENTS.md.append +0 -12
  85. package/examples/dashboard/assets/src/components/Header.tsx +0 -17
  86. package/examples/dashboard/assets/src/routes/__root.tsx +0 -57
  87. package/examples/dashboard/assets/src/routes/index.tsx +0 -158
  88. package/examples/dashboard/info.json +0 -19
  89. package/examples/dashboard/package.json +0 -6
  90. package/examples/ecommerce/README.md +0 -48
  91. package/examples/ecommerce/assets/AGENTS.md.append +0 -22
  92. package/examples/ecommerce/assets/public/logo.png +0 -0
  93. package/examples/ecommerce/assets/public/motorcycle-scooter.jpg +0 -0
  94. package/examples/ecommerce/assets/src/components/BuyButton.tsx +0 -35
  95. package/examples/ecommerce/assets/src/components/Header.tsx +0 -36
  96. package/examples/ecommerce/assets/src/components/MotorcycleAIAssistant.tsx +0 -162
  97. package/examples/ecommerce/assets/src/components/MotorcycleRecommendation.tsx +0 -53
  98. package/examples/ecommerce/assets/src/data/motorcycles.ts +0 -27
  99. package/examples/ecommerce/assets/src/lib/motorcycle-ai-hook.ts +0 -24
  100. package/examples/ecommerce/assets/src/lib/motorcycle-tools.ts +0 -42
  101. package/examples/ecommerce/assets/src/lib/stripe.server.ts +0 -39
  102. package/examples/ecommerce/assets/src/routes/__root.tsx +0 -57
  103. package/examples/ecommerce/assets/src/routes/api.motorcycle-chat.ts +0 -78
  104. package/examples/ecommerce/assets/src/routes/checkout/cancel.tsx +0 -25
  105. package/examples/ecommerce/assets/src/routes/checkout/success.tsx +0 -25
  106. package/examples/ecommerce/assets/src/routes/index.tsx +0 -76
  107. package/examples/ecommerce/assets/src/routes/motorcycles/$motorcycleId.tsx +0 -55
  108. package/examples/ecommerce/assets/src/store/motorcycle-assistant.ts +0 -3
  109. package/examples/ecommerce/assets/src/styles.css +0 -212
  110. package/examples/ecommerce/info.json +0 -38
  111. package/examples/ecommerce/package.json +0 -13
  112. package/examples/events/README.md +0 -110
  113. package/examples/events/assets/AGENTS.md.append +0 -21
  114. package/examples/events/assets/content/speakers/andre-costa.md +0 -22
  115. package/examples/events/assets/content/speakers/hans-mueller.md.ejs +0 -22
  116. package/examples/events/assets/content/speakers/isabella-martinez.md.ejs +0 -22
  117. package/examples/events/assets/content/speakers/kenji-nakamura.md.ejs +0 -22
  118. package/examples/events/assets/content/speakers/marie-dubois.md.ejs +0 -20
  119. package/examples/events/assets/content/speakers/priya-sharma.md.ejs +0 -22
  120. package/examples/events/assets/content/talks/croissant-lamination-secrets.md +0 -39
  121. package/examples/events/assets/content/talks/french-macaron-mastery.md.ejs +0 -39
  122. package/examples/events/assets/content/talks/neapolitan-pizza-tradition-meets-innovation.md.ejs +0 -39
  123. package/examples/events/assets/content/talks/savory-breads-of-the-mediterranean.md.ejs +0 -39
  124. package/examples/events/assets/content/talks/sourdough-from-starter-to-masterpiece.md.ejs +0 -36
  125. package/examples/events/assets/content/talks/the-art-of-the-perfect-tart.md.ejs +0 -32
  126. package/examples/events/assets/content/talks/the-science-of-sugar.md.ejs +0 -39
  127. package/examples/events/assets/content/talks/umami-in-pastry-east-meets-west.md.ejs +0 -39
  128. package/examples/events/assets/content-collections.ts +0 -56
  129. package/examples/events/assets/public/background-1.jpg +0 -0
  130. package/examples/events/assets/public/background-2.jpg +0 -0
  131. package/examples/events/assets/public/background-3.jpg +0 -0
  132. package/examples/events/assets/public/background-4.jpg +0 -0
  133. package/examples/events/assets/public/conference-logo.png +0 -0
  134. package/examples/events/assets/public/favicon.ico +0 -0
  135. package/examples/events/assets/public/speakers/andre-costa.jpg +0 -0
  136. package/examples/events/assets/public/speakers/hans-mueller.jpg +0 -0
  137. package/examples/events/assets/public/speakers/isabella-martinez.jpg +0 -0
  138. package/examples/events/assets/public/speakers/kenji-nakamura.jpg +0 -0
  139. package/examples/events/assets/public/speakers/marie-dubois.jpg +0 -0
  140. package/examples/events/assets/public/speakers/priya-sharma.jpg +0 -0
  141. package/examples/events/assets/public/talks/croissant-lamination-secrets.jpg +0 -0
  142. package/examples/events/assets/public/talks/french-macaron-mastery.jpg +0 -0
  143. package/examples/events/assets/public/talks/neapolitan-pizza-tradition-meets-innovation.jpg +0 -0
  144. package/examples/events/assets/public/talks/savory-breads-of-the-mediterranean.jpg +0 -0
  145. package/examples/events/assets/public/talks/sourdough-from-starter-to-masterpiece.jpg +0 -0
  146. package/examples/events/assets/public/talks/the-art-of-the-perfect-tart.jpg +0 -0
  147. package/examples/events/assets/public/talks/the-science-of-sugar.jpg +0 -0
  148. package/examples/events/assets/public/talks/umami-in-pastry-east-meets-west.jpg +0 -0
  149. package/examples/events/assets/public/tanstack-circle-logo.png +0 -0
  150. package/examples/events/assets/public/tanstack-word-logo-white.svg +0 -1
  151. package/examples/events/assets/src/components/Header.tsx +0 -59
  152. package/examples/events/assets/src/components/HeaderNav.tsx +0 -67
  153. package/examples/events/assets/src/components/HeroCarousel.tsx +0 -61
  154. package/examples/events/assets/src/components/RemyAssistant.tsx +0 -207
  155. package/examples/events/assets/src/components/SpeakerCard.tsx +0 -67
  156. package/examples/events/assets/src/components/TalkCard.tsx +0 -77
  157. package/examples/events/assets/src/components/ui/card.tsx +0 -92
  158. package/examples/events/assets/src/lib/conference-ai-hook.ts +0 -26
  159. package/examples/events/assets/src/lib/conference-tools.ts +0 -210
  160. package/examples/events/assets/src/lib/model-selection.ts +0 -1
  161. package/examples/events/assets/src/lib/utils.ts +0 -6
  162. package/examples/events/assets/src/routes/__root.tsx +0 -70
  163. package/examples/events/assets/src/routes/api.remy-chat.ts +0 -119
  164. package/examples/events/assets/src/routes/index.tsx +0 -192
  165. package/examples/events/assets/src/routes/schedule.index.tsx +0 -274
  166. package/examples/events/assets/src/routes/speakers.$slug.tsx +0 -122
  167. package/examples/events/assets/src/routes/speakers.index.tsx +0 -40
  168. package/examples/events/assets/src/routes/talks.$slug.tsx +0 -116
  169. package/examples/events/assets/src/routes/talks.index.tsx +0 -40
  170. package/examples/events/assets/src/styles.css +0 -182
  171. package/examples/events/info.json +0 -61
  172. package/examples/events/package.json +0 -23
  173. package/examples/marketing/README.md +0 -60
  174. package/examples/marketing/assets/AGENTS.md.append +0 -15
  175. package/examples/marketing/assets/public/logo.png +0 -0
  176. package/examples/marketing/assets/public/motorcycle-adventure.jpg +0 -0
  177. package/examples/marketing/assets/public/motorcycle-cruiser.jpg +0 -0
  178. package/examples/marketing/assets/public/motorcycle-scooter.jpg +0 -0
  179. package/examples/marketing/assets/public/motorcycle-sport.jpg +0 -0
  180. package/examples/marketing/assets/public/motorcycle-supersport.jpg +0 -0
  181. package/examples/marketing/assets/src/components/Header.tsx +0 -36
  182. package/examples/marketing/assets/src/components/MotorcycleAIAssistant.tsx +0 -162
  183. package/examples/marketing/assets/src/components/MotorcycleRecommendation.tsx +0 -53
  184. package/examples/marketing/assets/src/data/motorcycles.ts.ejs +0 -77
  185. package/examples/marketing/assets/src/lib/motorcycle-ai-hook.ts +0 -24
  186. package/examples/marketing/assets/src/lib/motorcycle-tools.ts +0 -42
  187. package/examples/marketing/assets/src/routes/__root.tsx +0 -57
  188. package/examples/marketing/assets/src/routes/api.motorcycle-chat.ts +0 -78
  189. package/examples/marketing/assets/src/routes/index.tsx +0 -72
  190. package/examples/marketing/assets/src/routes/motorcycles/$motorcycleId.tsx +0 -56
  191. package/examples/marketing/assets/src/store/motorcycle-assistant.ts +0 -3
  192. package/examples/marketing/assets/src/styles.css +0 -212
  193. package/examples/marketing/info.json +0 -33
  194. package/examples/marketing/package.json +0 -14
  195. package/examples/portfolio/README.md +0 -49
  196. package/examples/portfolio/assets/AGENTS.md.append +0 -21
  197. package/examples/portfolio/assets/content/blog/getting-started-with-tanstack.md +0 -53
  198. package/examples/portfolio/assets/content/blog/react-19-features.md +0 -78
  199. package/examples/portfolio/assets/content/blog/tailwind-css-v4-guide.md +0 -60
  200. package/examples/portfolio/assets/content/education/code-school.md +0 -17
  201. package/examples/portfolio/assets/content/jobs/initech-junior.md +0 -20
  202. package/examples/portfolio/assets/content/projects/portfolio-site.md +0 -15
  203. package/examples/portfolio/assets/content/projects/task-manager.md +0 -15
  204. package/examples/portfolio/assets/content-collections.ts +0 -65
  205. package/examples/portfolio/assets/public/contact.html +0 -6
  206. package/examples/portfolio/assets/public/headshot-on-white.jpg +0 -0
  207. package/examples/portfolio/assets/src/components/Header.tsx +0 -33
  208. package/examples/portfolio/assets/src/components/ResumeAssistant.tsx +0 -193
  209. package/examples/portfolio/assets/src/components/ui/badge.tsx +0 -46
  210. package/examples/portfolio/assets/src/components/ui/card.tsx +0 -92
  211. package/examples/portfolio/assets/src/components/ui/checkbox.tsx +0 -30
  212. package/examples/portfolio/assets/src/components/ui/hover-card.tsx +0 -44
  213. package/examples/portfolio/assets/src/components/ui/separator.tsx +0 -26
  214. package/examples/portfolio/assets/src/lib/resume-ai-hook.ts +0 -21
  215. package/examples/portfolio/assets/src/lib/resume-tools.ts +0 -165
  216. package/examples/portfolio/assets/src/lib/utils.ts +0 -6
  217. package/examples/portfolio/assets/src/routes/__root.tsx +0 -57
  218. package/examples/portfolio/assets/src/routes/api.resume-chat.ts +0 -116
  219. package/examples/portfolio/assets/src/routes/blog/$slug.tsx +0 -73
  220. package/examples/portfolio/assets/src/routes/contact.tsx +0 -121
  221. package/examples/portfolio/assets/src/routes/index.tsx +0 -55
  222. package/examples/portfolio/assets/src/routes/projects.tsx +0 -62
  223. package/examples/portfolio/assets/src/routes/resume.tsx +0 -220
  224. package/examples/portfolio/assets/src/styles.css +0 -138
  225. package/examples/portfolio/info.json +0 -50
  226. package/examples/portfolio/package.json +0 -26
  227. package/examples/resume/README.md +0 -82
  228. package/examples/resume/assets/AGENTS.md.append +0 -19
  229. package/examples/resume/assets/content/education/code-school.md +0 -17
  230. package/examples/resume/assets/content/jobs/freelance.md.ejs +0 -13
  231. package/examples/resume/assets/content/jobs/initech-junior.md +0 -20
  232. package/examples/resume/assets/content/jobs/initech-lead.md.ejs +0 -29
  233. package/examples/resume/assets/content/jobs/initrode-senior.md.ejs +0 -28
  234. package/examples/resume/assets/content-collections.ts +0 -36
  235. package/examples/resume/assets/public/headshot-on-white.jpg +0 -0
  236. package/examples/resume/assets/src/components/Header.tsx +0 -33
  237. package/examples/resume/assets/src/components/ResumeAssistant.tsx +0 -193
  238. package/examples/resume/assets/src/components/ui/badge.tsx +0 -46
  239. package/examples/resume/assets/src/components/ui/card.tsx +0 -92
  240. package/examples/resume/assets/src/components/ui/checkbox.tsx +0 -30
  241. package/examples/resume/assets/src/components/ui/hover-card.tsx +0 -44
  242. package/examples/resume/assets/src/components/ui/separator.tsx +0 -26
  243. package/examples/resume/assets/src/lib/resume-ai-hook.ts +0 -21
  244. package/examples/resume/assets/src/lib/resume-tools.ts +0 -165
  245. package/examples/resume/assets/src/lib/utils.ts +0 -6
  246. package/examples/resume/assets/src/routes/api.resume-chat.ts +0 -110
  247. package/examples/resume/assets/src/routes/index.tsx +0 -220
  248. package/examples/resume/assets/src/styles.css +0 -138
  249. package/examples/resume/info.json +0 -35
  250. package/examples/resume/package.json +0 -26
  251. package/examples/saas/README.md +0 -16
  252. package/examples/saas/assets/AGENTS.md.append +0 -9
  253. package/examples/saas/assets/src/components/Header.tsx +0 -17
  254. package/examples/saas/assets/src/routes/__root.tsx +0 -57
  255. package/examples/saas/assets/src/routes/faq.tsx +0 -94
  256. package/examples/saas/assets/src/routes/index.tsx +0 -197
  257. package/examples/saas/info.json +0 -23
  258. package/examples/saas/package.json +0 -1
  259. package/examples/survey/README.md +0 -20
  260. package/examples/survey/assets/AGENTS.md.append +0 -9
  261. package/examples/survey/assets/public/form-survey.html +0 -15
  262. package/examples/survey/assets/src/components/SurveyForm.tsx +0 -128
  263. package/examples/survey/assets/src/routes/index.tsx +0 -14
  264. package/examples/survey/info.json +0 -19
  265. package/examples/survey/package.json +0 -1
  266. package/project/base/AGENTS.md +0 -86
  267. package/project/base/_dot_claude/skills/content-collections/SKILL.md +0 -505
  268. package/project/base/_dot_claude/skills/netlify-blobs/SKILL.md +0 -410
  269. package/project/base/_dot_claude/skills/netlify-db/SKILL.md +0 -424
  270. package/project/base/_dot_claude/skills/netlify-debugging/SKILL.md +0 -419
  271. package/project/base/_dot_claude/skills/netlify-forms/SKILL.md +0 -243
  272. package/project/base/_dot_claude/skills/netlify-functions/SKILL.md +0 -372
  273. package/project/base/_dot_claude/skills/tanstack-start-api-routes/SKILL.md +0 -421
  274. package/project/base/_dot_claude/skills/tanstack-start-loaders/SKILL.md +0 -426
  275. package/project/base/_dot_claude/skills/tanstack-start-project-setup/SKILL.md +0 -493
  276. package/project/base/_dot_claude/skills/tanstack-start-routes/SKILL.md +0 -430
  277. package/project/base/_dot_claude/skills/tanstack-start-server-functions/SKILL.md +0 -445
  278. package/project/base/_dot_claude/skills/tanstack-start-typesafe-routing/SKILL.md +0 -494
  279. package/project/base/_dot_gitignore +0 -8
  280. package/project/base/netlify.toml +0 -7
  281. package/project/base/package.json +0 -33
  282. package/project/base/public/favicon.ico +0 -0
  283. package/project/base/public/tanstack-circle-logo.png +0 -0
  284. package/project/base/public/tanstack-word-logo-white.svg +0 -1
  285. package/project/base/src/components/Header.tsx +0 -17
  286. package/project/base/src/components/HeaderNav.tsx.ejs +0 -179
  287. package/project/base/src/router.tsx +0 -15
  288. package/project/base/src/routes/__root.tsx +0 -57
  289. package/project/base/src/routes/index.tsx +0 -48
  290. package/project/base/src/styles.css +0 -15
  291. package/project/base/tsconfig.json +0 -28
  292. package/project/base/vite.config.ts.ejs +0 -25
  293. package/project/info.json +0 -17
  294. package/project/packages.json +0 -22
  295. package/scripts/check-outdated-packages.js +0 -421
  296. 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,303 +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
- features: (addon as unknown as { features?: Array<string> }).features ?? [],
187
- options: addon.options,
188
- }))
189
- console.log(JSON.stringify(serialized, null, 2))
190
- 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)
191
86
  }
192
87
 
193
- // Handle --list-add-ons (text format)
194
- if (options.listAddOns) {
195
- const addOns = await getAllAddOns(framework, defaultMode)
196
- let hasConfigurableAddOns = false
197
- for (const addOn of addOns.filter((a) => !forcedAddOns.includes(a.id))) {
198
- const hasOptions =
199
- addOn.options && Object.keys(addOn.options).length > 0
200
- const optionMarker = hasOptions ? '*' : ' '
201
- if (hasOptions) hasConfigurableAddOns = true
202
- console.log(
203
- `${optionMarker} ${chalk.bold(addOn.id)}: ${addOn.description}`,
204
- )
205
- }
206
- if (hasConfigurableAddOns) {
207
- console.log('\n* = has configuration options')
208
- }
209
- return
210
- }
88
+ // Resolve starter ID
89
+ const starterId =
90
+ typeof options.addOns === 'string' ? options.addOns : 'basic'
211
91
 
212
- // Handle --addon-details
213
- if (options.addonDetails) {
214
- const addOns = await getAllAddOns(framework, defaultMode)
215
- const addOn = addOns.find((a) => a.id === options.addonDetails)
216
- if (!addOn) {
217
- console.error(`Add-on '${options.addonDetails}' not found`)
218
- process.exit(1)
219
- }
220
-
221
- console.log(
222
- `${chalk.bold.cyan('Add-on Details:')} ${chalk.bold(addOn.name)}`,
223
- )
224
- console.log(`${chalk.bold('ID:')} ${addOn.id}`)
225
- console.log(`${chalk.bold('Description:')} ${addOn.description}`)
226
- console.log(`${chalk.bold('Type:')} ${addOn.type}`)
227
- console.log(`${chalk.bold('Phase:')} ${addOn.phase}`)
228
- console.log(`${chalk.bold('Supported Modes:')} ${addOn.modes.join(', ')}`)
229
-
230
- if (addOn.dependsOn && addOn.dependsOn.length > 0) {
231
- console.log(
232
- `${chalk.bold('Dependencies:')} ${addOn.dependsOn.join(', ')}`,
233
- )
234
- }
235
-
236
- if (addOn.options && Object.keys(addOn.options).length > 0) {
237
- console.log(`\n${chalk.bold.yellow('Configuration Options:')}`)
238
- for (const [optionName, option] of Object.entries(addOn.options)) {
239
- if (option && typeof option === 'object' && 'type' in option) {
240
- const opt = option as { label: string; description?: string; type: string; default: unknown; options?: Array<{ value: string; label: string }> }
241
- console.log(` ${chalk.bold(optionName)}:`)
242
- console.log(` Label: ${opt.label}`)
243
- if (opt.description) {
244
- console.log(` Description: ${opt.description}`)
245
- }
246
- console.log(` Type: ${opt.type}`)
247
- console.log(` Default: ${opt.default}`)
248
- if (opt.type === 'select' && opt.options) {
249
- console.log(` Available values:`)
250
- for (const choice of opt.options) {
251
- console.log(` - ${choice.value}: ${choice.label}`)
252
- }
253
- }
254
- }
255
- }
256
- } else {
257
- console.log(`\n${chalk.gray('No configuration options available')}`)
258
- }
259
-
260
- if (addOn.routes && addOn.routes.length > 0) {
261
- console.log(`\n${chalk.bold.green('Routes:')}`)
262
- for (const route of addOn.routes) {
263
- console.log(` ${chalk.bold(route.url)} (${route.name})`)
264
- console.log(` File: ${route.path}`)
265
- }
266
- }
267
- return
268
- }
269
-
270
- // Handle project creation: target-dir defaults to current directory
271
- const targetDir = resolve(options.targetDir ?? '.')
272
- const isCurrentDir = targetDir === resolve(process.cwd())
92
+ // Resolve target directory — default to CWD
93
+ const targetDir = options.targetDir ? resolve(options.targetDir) : resolve(process.cwd())
273
94
 
95
+ // Resolve project name for package.json
274
96
  let resolvedProjectName: string
275
- if (projectName) {
276
- resolvedProjectName =
277
- projectName === '.'
278
- ? sanitizePackageName(getCurrentDirectoryName())
279
- : sanitizePackageName(projectName)
280
- } else if (isCurrentDir) {
281
- resolvedProjectName = sanitizePackageName(getCurrentDirectoryName())
97
+ if (projectName && projectName !== '.') {
98
+ resolvedProjectName = sanitizePackageName(projectName)
282
99
  } else {
283
- console.error('Project name is required when --target-dir is not the current directory')
284
- program.help()
285
- return
100
+ resolvedProjectName = sanitizePackageName(getCurrentDirectoryName())
286
101
  }
287
102
 
288
103
  const { valid, error } = validateProjectName(resolvedProjectName)
289
104
  if (!valid) {
290
- console.error(error)
105
+ console.error(chalk.red(error))
291
106
  process.exit(1)
292
107
  }
293
108
 
294
- // Determine selected add-ons
295
- const selectedAddOns = new Set<string>([...forcedAddOns])
296
- if (options.addOns && Array.isArray(options.addOns)) {
297
- for (const a of options.addOns) {
298
- 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)
299
119
  }
300
120
  }
301
121
 
302
- const chosenAddOns = await finalizeAddOns(
303
- framework,
304
- defaultMode,
305
- Array.from(selectedAddOns),
122
+ console.log(
123
+ chalk.bold.cyan(
124
+ `Creating a new Netlify TanStack Start app in ${chalk.white(targetDir)}...`,
125
+ ),
306
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
+ )
307
141
 
308
- const finalOptions: Options = {
309
- projectName: resolvedProjectName,
310
- targetDir,
311
- framework,
312
- mode: defaultMode,
313
- typescript: true,
314
- tailwind: true,
315
- packageManager:
316
- options.packageManager ||
317
- getPackageManager() ||
318
- DEFAULT_PACKAGE_MANAGER,
319
- git: options.git !== false,
320
- install: options.install !== false,
321
- chosenAddOns,
322
- addOnOptions: {
323
- ...populateAddOnOptionsDefaults(chosenAddOns),
324
- project: { bareBones: options.bareBones ?? true },
325
- },
326
- }
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
+ }
327
151
 
328
- environment.intro(`Creating a new ${appName} app in ${resolvedProjectName}...`)
329
- await createApp(environment, finalOptions)
152
+ // Copy starter files to target directory
153
+ await cp(starterPath, targetDir, { recursive: true })
330
154
 
331
- // Delete files specified in bareBones.deleteFiles for each add-on when in bare-bones mode
332
- if (options.bareBones !== false) {
333
- for (const addOn of chosenAddOns) {
334
- const addOnWithBareBones = addOn as typeof addOn & {
335
- bareBones?: { deleteFiles?: Array<string> }
336
- }
337
- if (addOnWithBareBones.bareBones?.deleteFiles) {
338
- for (const file of addOnWithBareBones.bareBones.deleteFiles) {
339
- const filePath = join(targetDir, file)
340
- try {
341
- await unlink(filePath)
342
- } catch {
343
- // File may not exist, ignore errors
344
- }
345
- }
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')
346
162
  }
347
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
+ }
348
197
  }
349
198
 
350
- // Generate an AGENTS.md file in the root of the project
351
- 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'))
352
210
  })
353
211
 
354
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