kaddidlehopper 0.5.0 → 0.7.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 +123 -206
  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 +152 -269
  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/netlify/swar-templates.git'
14
+ const MANIFEST_URL =
15
+ 'https://raw.githubusercontent.com/netlify/swar-templates/main/manifest.json'
16
+
27
17
  function sanitizePackageName(name: string): string {
28
18
  return name
29
19
  .toLowerCase()
@@ -52,303 +42,196 @@ 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
88
+ // Resolve starter ID
89
+ const starterId =
90
+ typeof options.addOns === 'string' ? options.addOns : 'basic'
91
+
92
+ // Resolve target directory default to CWD
93
+ const targetDir = options.targetDir ? resolve(options.targetDir) : resolve(process.cwd())
94
+
95
+ // Resolve project name for package.json
96
+ let resolvedProjectName: string
97
+ if (projectName && projectName !== '.') {
98
+ resolvedProjectName = sanitizePackageName(projectName)
99
+ } else {
100
+ resolvedProjectName = sanitizePackageName(getCurrentDirectoryName())
101
+ }
102
+
103
+ const { valid, error } = validateProjectName(resolvedProjectName)
104
+ if (!valid) {
105
+ console.error(chalk.red(error))
106
+ process.exit(1)
210
107
  }
211
108
 
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`)
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
+ )
218
118
  process.exit(1)
219
119
  }
120
+ }
220
121
 
221
- console.log(
222
- `${chalk.bold.cyan('Add-on Details:')} ${chalk.bold(addOn.name)}`,
122
+ // Delete index.html if it exists in the target directory
123
+ const indexHtmlPath = join(targetDir, 'index.html')
124
+ if (existsSync(indexHtmlPath)) {
125
+ await rm(indexHtmlPath)
126
+ }
127
+
128
+ console.log(
129
+ chalk.bold.cyan(
130
+ `Creating a new Netlify TanStack Start app in ${chalk.white(targetDir)}...`,
131
+ ),
132
+ )
133
+ console.log(chalk.gray(`Using starter: ${starterId}`))
134
+
135
+ // Fetch manifest to resolve frameworkId for this starter
136
+ type StarterEntry = { id: string; framework?: string }
137
+ const manifest = await fetch(MANIFEST_URL).then((r) => r.json()) as { starters: StarterEntry[] }
138
+ const starterEntry = manifest.starters.find((s) => s.id === starterId)
139
+ const frameworkId = starterEntry?.framework
140
+
141
+ // Sparse clone the template repo into a temp directory
142
+ const tmpDir = await mkdtemp(join(tmpdir(), 'netlify-cta-'))
143
+ try {
144
+ console.log(chalk.gray('⟳ Fetching template...'))
145
+ const sparsePaths = [`starters/${starterId}`, ...(frameworkId ? [`frameworks/${frameworkId}`] : [])]
146
+ execSync(
147
+ `git clone --depth=1 --filter=blob:none --sparse ${GITHUB_REPO} ${tmpDir}`,
148
+ { stdio: 'pipe' },
149
+ )
150
+ execSync(
151
+ `git -C ${tmpDir} sparse-checkout set ${sparsePaths.join(' ')}`,
152
+ { stdio: 'pipe' },
223
153
  )
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
154
 
230
- if (addOn.dependsOn && addOn.dependsOn.length > 0) {
231
- console.log(
232
- `${chalk.bold('Dependencies:')} ${addOn.dependsOn.join(', ')}`,
155
+ const starterPath = join(tmpDir, 'starters', starterId)
156
+ if (!existsSync(starterPath)) {
157
+ console.error(
158
+ chalk.red(
159
+ `Starter "${starterId}" not found in the template repo. Run --list-addons-json to see available starters.`,
160
+ ),
233
161
  )
162
+ process.exit(1)
234
163
  }
235
164
 
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
- }
165
+ // Copy starter files to target directory
166
+ await cp(starterPath, targetDir, { recursive: true })
167
+
168
+ // Copy framework overlay files if they exist
169
+ if (frameworkId) {
170
+ const frameworkPath = join(tmpDir, 'frameworks', frameworkId)
171
+ if (existsSync(frameworkPath)) {
172
+ console.log(chalk.gray(`⟳ Applying framework overlay (${frameworkId})...`))
173
+ await cp(frameworkPath, targetDir, { recursive: true })
174
+ console.log(chalk.green(`✓ Framework overlay applied (${frameworkId})`))
175
+ } else {
176
+ console.log(chalk.yellow(`⚠ Framework overlay "${frameworkId}" not found in repo, skipping`))
255
177
  }
256
- } else {
257
- console.log(`\n${chalk.gray('No configuration options available')}`)
258
178
  }
259
179
 
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}`)
180
+ // Update package.json name if a project name was provided
181
+ if (projectName && projectName !== '.') {
182
+ const pkgPath = join(targetDir, 'package.json')
183
+ if (existsSync(pkgPath)) {
184
+ const pkg = JSON.parse(await readFile(pkgPath, 'utf-8'))
185
+ pkg.name = resolvedProjectName
186
+ await writeFile(pkgPath, JSON.stringify(pkg, null, 2) + '\n')
265
187
  }
266
188
  }
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())
273
-
274
- let resolvedProjectName: string
275
- if (projectName) {
276
- resolvedProjectName =
277
- projectName === '.'
278
- ? sanitizePackageName(getCurrentDirectoryName())
279
- : sanitizePackageName(projectName)
280
- } else if (isCurrentDir) {
281
- resolvedProjectName = sanitizePackageName(getCurrentDirectoryName())
282
- } else {
283
- console.error('Project name is required when --target-dir is not the current directory')
284
- program.help()
285
- return
286
- }
287
189
 
288
- const { valid, error } = validateProjectName(resolvedProjectName)
289
- if (!valid) {
290
- console.error(error)
291
- process.exit(1)
190
+ console.log(chalk.green(`✓ Template copied`))
191
+ } finally {
192
+ await rm(tmpDir, { recursive: true, force: true })
292
193
  }
293
194
 
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)
195
+ // Initialize git repository
196
+ if (options.git !== false) {
197
+ try {
198
+ execSync('git init', { cwd: targetDir, stdio: 'pipe' })
199
+ console.log(chalk.green('✓ Initialized git repository'))
200
+ } catch {
201
+ console.warn(chalk.yellow('⚠ Could not initialize git repository'))
299
202
  }
300
203
  }
301
204
 
302
- const chosenAddOns = await finalizeAddOns(
303
- framework,
304
- defaultMode,
305
- Array.from(selectedAddOns),
306
- )
307
-
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
- }
327
-
328
- environment.intro(`Creating a new ${appName} app in ${resolvedProjectName}...`)
329
- await createApp(environment, finalOptions)
330
-
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
- }
346
- }
205
+ // Install dependencies
206
+ if (options.install !== false) {
207
+ const pm =
208
+ options.packageManager ??
209
+ getPackageManagerFromUserAgent() ??
210
+ 'pnpm'
211
+ console.log(chalk.gray(`⟳ Installing dependencies with ${pm}...`))
212
+ try {
213
+ execSync(`${pm} install`, { cwd: targetDir, stdio: 'inherit' })
214
+ console.log(chalk.green('✓ Dependencies installed'))
215
+ } catch {
216
+ console.error(
217
+ chalk.red(
218
+ `Failed to install dependencies. Run \`${pm} install\` manually.`,
219
+ ),
220
+ )
347
221
  }
348
222
  }
349
223
 
350
- // Generate an AGENTS.md file in the root of the project
351
- await generateAgentsMd(targetDir, resolvedProjectName, chosenAddOns as Array<{ packageFileDescriptions?: Record<string, string> }>)
224
+ console.log(chalk.bold.green('\nDone! Your project is ready.'))
225
+ console.log(chalk.white('\nNext steps:'))
226
+ console.log(chalk.cyan(` cd ${basename(targetDir)}`))
227
+ if (options.install === false) {
228
+ const pm =
229
+ options.packageManager ??
230
+ getPackageManagerFromUserAgent() ??
231
+ 'pnpm'
232
+ console.log(chalk.cyan(` ${pm} install`))
233
+ }
234
+ console.log(chalk.cyan(' pnpm dev'))
352
235
  })
353
236
 
354
237
  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
  }