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.
- package/.claude/settings.local.json +7 -0
- package/dist/cli.js +104 -207
- package/dist/index.js +1 -31
- package/dist/types/cli.d.ts +1 -8
- package/dist/types/types.d.ts +7 -12
- package/package.json +1 -2
- package/src/cli.ts +129 -270
- package/src/index.ts +1 -47
- package/src/types.ts +7 -13
- package/CONTEXT.md +0 -139
- package/add-ons/ai/README.md +0 -34
- package/add-ons/ai/assets/AGENTS.md.append +0 -24
- package/add-ons/ai/assets/_dot_env.local.append +0 -13
- package/add-ons/ai/assets/src/components/AIAssistant.tsx +0 -149
- package/add-ons/ai/assets/src/lib/ai-hook.ts +0 -21
- package/add-ons/ai/assets/src/lib/weather-tools.ts +0 -30
- package/add-ons/ai/assets/src/routes/api.chat.ts +0 -94
- package/add-ons/ai/assets/src/routes/chat.css +0 -175
- package/add-ons/ai/assets/src/routes/chat.tsx +0 -141
- package/add-ons/ai/info.json +0 -21
- package/add-ons/ai/package.json +0 -17
- package/add-ons/ai/small-logo.svg +0 -8
- package/add-ons/db/assets/AGENTS.md.append +0 -22
- package/add-ons/db/assets/DB-SETUP.md +0 -65
- package/add-ons/db/assets/_dot_env.local.append +0 -2
- package/add-ons/db/assets/drizzle.config.ts +0 -10
- package/add-ons/db/assets/src/db/index.ts +0 -8
- package/add-ons/db/assets/src/db/schema.ts +0 -8
- package/add-ons/db/assets/src/routes/db-example.tsx +0 -118
- package/add-ons/db/assets/src/server/guestbook.functions.ts +0 -23
- package/add-ons/db/info.json +0 -21
- package/add-ons/db/package.json +0 -10
- package/add-ons/forms/assets/AGENTS.md.append +0 -13
- package/add-ons/forms/assets/public/form-example.html +0 -14
- package/add-ons/forms/assets/src/routes/form-example.tsx +0 -76
- package/add-ons/forms/info.json +0 -17
- package/add-ons/forms/package.json +0 -3
- package/examples/ai-chat/README.md +0 -36
- package/examples/ai-chat/assets/AGENTS.md.append +0 -24
- package/examples/ai-chat/assets/src/lib/ai-hook.ts +0 -21
- package/examples/ai-chat/assets/src/lib/weather-tools.ts +0 -30
- package/examples/ai-chat/assets/src/routes/__root.tsx +0 -57
- package/examples/ai-chat/assets/src/routes/api.chat.ts +0 -94
- package/examples/ai-chat/assets/src/routes/index.tsx +0 -141
- package/examples/ai-chat/assets/src/styles.css +0 -165
- package/examples/ai-chat/info.json +0 -24
- package/examples/ai-chat/package.json +0 -14
- package/examples/blog/README.md +0 -60
- package/examples/blog/assets/AGENTS.md.append +0 -18
- package/examples/blog/assets/content/posts/beach.md +0 -12
- package/examples/blog/assets/content/posts/jungle.md.ejs +0 -12
- package/examples/blog/assets/content/posts/mountains.md.ejs +0 -12
- package/examples/blog/assets/content/posts/snorkeling.md.ejs +0 -12
- package/examples/blog/assets/content/posts/waterfall.md.ejs +0 -12
- package/examples/blog/assets/content-collections.ts +0 -30
- package/examples/blog/assets/public/beach.jpg +0 -0
- package/examples/blog/assets/public/jungle.jpg +0 -0
- package/examples/blog/assets/public/mountains.jpg +0 -0
- package/examples/blog/assets/public/snorkeling.jpg +0 -0
- package/examples/blog/assets/public/waterfall.jpg +0 -0
- package/examples/blog/assets/src/components/Header.tsx +0 -52
- package/examples/blog/assets/src/components/VacayAssistant.tsx +0 -205
- package/examples/blog/assets/src/components/blog-posts.tsx +0 -78
- package/examples/blog/assets/src/components/ui/card.tsx +0 -92
- package/examples/blog/assets/src/lib/blog-ai-hook.ts +0 -25
- package/examples/blog/assets/src/lib/blog-tools.ts +0 -111
- package/examples/blog/assets/src/lib/utils.ts +0 -6
- package/examples/blog/assets/src/routes/__root.tsx +0 -57
- package/examples/blog/assets/src/routes/api.blog-chat.ts +0 -117
- package/examples/blog/assets/src/routes/category.$category.tsx +0 -19
- package/examples/blog/assets/src/routes/index.tsx +0 -19
- package/examples/blog/assets/src/routes/posts.$slug.tsx +0 -63
- package/examples/blog/assets/src/styles.css +0 -138
- package/examples/blog/info.json +0 -39
- package/examples/blog/package.json +0 -23
- package/examples/calculator/README.md +0 -16
- package/examples/calculator/assets/AGENTS.md.append +0 -9
- package/examples/calculator/assets/src/components/Calculator.tsx +0 -238
- package/examples/calculator/assets/src/components/Header.tsx +0 -17
- package/examples/calculator/assets/src/routes/__root.tsx +0 -57
- package/examples/calculator/assets/src/routes/index.tsx +0 -14
- package/examples/calculator/info.json +0 -19
- package/examples/calculator/package.json +0 -1
- package/examples/dashboard/README.md +0 -17
- package/examples/dashboard/assets/AGENTS.md.append +0 -12
- package/examples/dashboard/assets/src/components/Header.tsx +0 -17
- package/examples/dashboard/assets/src/routes/__root.tsx +0 -57
- package/examples/dashboard/assets/src/routes/index.tsx +0 -158
- package/examples/dashboard/info.json +0 -18
- package/examples/dashboard/package.json +0 -6
- package/examples/ecommerce/README.md +0 -48
- package/examples/ecommerce/assets/AGENTS.md.append +0 -22
- package/examples/ecommerce/assets/public/logo.png +0 -0
- package/examples/ecommerce/assets/public/motorcycle-scooter.jpg +0 -0
- package/examples/ecommerce/assets/src/components/BuyButton.tsx +0 -35
- package/examples/ecommerce/assets/src/components/Header.tsx +0 -36
- package/examples/ecommerce/assets/src/components/MotorcycleAIAssistant.tsx +0 -162
- package/examples/ecommerce/assets/src/components/MotorcycleRecommendation.tsx +0 -53
- package/examples/ecommerce/assets/src/data/motorcycles.ts +0 -27
- package/examples/ecommerce/assets/src/lib/motorcycle-ai-hook.ts +0 -24
- package/examples/ecommerce/assets/src/lib/motorcycle-tools.ts +0 -42
- package/examples/ecommerce/assets/src/lib/stripe.server.ts +0 -39
- package/examples/ecommerce/assets/src/routes/__root.tsx +0 -57
- package/examples/ecommerce/assets/src/routes/api.motorcycle-chat.ts +0 -78
- package/examples/ecommerce/assets/src/routes/checkout/cancel.tsx +0 -25
- package/examples/ecommerce/assets/src/routes/checkout/success.tsx +0 -25
- package/examples/ecommerce/assets/src/routes/index.tsx +0 -76
- package/examples/ecommerce/assets/src/routes/motorcycles/$motorcycleId.tsx +0 -55
- package/examples/ecommerce/assets/src/store/motorcycle-assistant.ts +0 -3
- package/examples/ecommerce/assets/src/styles.css +0 -212
- package/examples/ecommerce/info.json +0 -37
- package/examples/ecommerce/package.json +0 -13
- package/examples/events/README.md +0 -110
- package/examples/events/assets/AGENTS.md.append +0 -21
- package/examples/events/assets/content/speakers/andre-costa.md +0 -22
- package/examples/events/assets/content/speakers/hans-mueller.md.ejs +0 -22
- package/examples/events/assets/content/speakers/isabella-martinez.md.ejs +0 -22
- package/examples/events/assets/content/speakers/kenji-nakamura.md.ejs +0 -22
- package/examples/events/assets/content/speakers/marie-dubois.md.ejs +0 -20
- package/examples/events/assets/content/speakers/priya-sharma.md.ejs +0 -22
- package/examples/events/assets/content/talks/croissant-lamination-secrets.md +0 -39
- package/examples/events/assets/content/talks/french-macaron-mastery.md.ejs +0 -39
- package/examples/events/assets/content/talks/neapolitan-pizza-tradition-meets-innovation.md.ejs +0 -39
- package/examples/events/assets/content/talks/savory-breads-of-the-mediterranean.md.ejs +0 -39
- package/examples/events/assets/content/talks/sourdough-from-starter-to-masterpiece.md.ejs +0 -36
- package/examples/events/assets/content/talks/the-art-of-the-perfect-tart.md.ejs +0 -32
- package/examples/events/assets/content/talks/the-science-of-sugar.md.ejs +0 -39
- package/examples/events/assets/content/talks/umami-in-pastry-east-meets-west.md.ejs +0 -39
- package/examples/events/assets/content-collections.ts +0 -56
- package/examples/events/assets/public/background-1.jpg +0 -0
- package/examples/events/assets/public/background-2.jpg +0 -0
- package/examples/events/assets/public/background-3.jpg +0 -0
- package/examples/events/assets/public/background-4.jpg +0 -0
- package/examples/events/assets/public/conference-logo.png +0 -0
- package/examples/events/assets/public/favicon.ico +0 -0
- package/examples/events/assets/public/speakers/andre-costa.jpg +0 -0
- package/examples/events/assets/public/speakers/hans-mueller.jpg +0 -0
- package/examples/events/assets/public/speakers/isabella-martinez.jpg +0 -0
- package/examples/events/assets/public/speakers/kenji-nakamura.jpg +0 -0
- package/examples/events/assets/public/speakers/marie-dubois.jpg +0 -0
- package/examples/events/assets/public/speakers/priya-sharma.jpg +0 -0
- package/examples/events/assets/public/talks/croissant-lamination-secrets.jpg +0 -0
- package/examples/events/assets/public/talks/french-macaron-mastery.jpg +0 -0
- package/examples/events/assets/public/talks/neapolitan-pizza-tradition-meets-innovation.jpg +0 -0
- package/examples/events/assets/public/talks/savory-breads-of-the-mediterranean.jpg +0 -0
- package/examples/events/assets/public/talks/sourdough-from-starter-to-masterpiece.jpg +0 -0
- package/examples/events/assets/public/talks/the-art-of-the-perfect-tart.jpg +0 -0
- package/examples/events/assets/public/talks/the-science-of-sugar.jpg +0 -0
- package/examples/events/assets/public/talks/umami-in-pastry-east-meets-west.jpg +0 -0
- package/examples/events/assets/public/tanstack-circle-logo.png +0 -0
- package/examples/events/assets/public/tanstack-word-logo-white.svg +0 -1
- package/examples/events/assets/src/components/Header.tsx +0 -59
- package/examples/events/assets/src/components/HeaderNav.tsx +0 -67
- package/examples/events/assets/src/components/HeroCarousel.tsx +0 -61
- package/examples/events/assets/src/components/RemyAssistant.tsx +0 -207
- package/examples/events/assets/src/components/SpeakerCard.tsx +0 -67
- package/examples/events/assets/src/components/TalkCard.tsx +0 -77
- package/examples/events/assets/src/components/ui/card.tsx +0 -92
- package/examples/events/assets/src/lib/conference-ai-hook.ts +0 -26
- package/examples/events/assets/src/lib/conference-tools.ts +0 -210
- package/examples/events/assets/src/lib/model-selection.ts +0 -1
- package/examples/events/assets/src/lib/utils.ts +0 -6
- package/examples/events/assets/src/routes/__root.tsx +0 -70
- package/examples/events/assets/src/routes/api.remy-chat.ts +0 -119
- package/examples/events/assets/src/routes/index.tsx +0 -192
- package/examples/events/assets/src/routes/schedule.index.tsx +0 -274
- package/examples/events/assets/src/routes/speakers.$slug.tsx +0 -122
- package/examples/events/assets/src/routes/speakers.index.tsx +0 -40
- package/examples/events/assets/src/routes/talks.$slug.tsx +0 -116
- package/examples/events/assets/src/routes/talks.index.tsx +0 -40
- package/examples/events/assets/src/styles.css +0 -182
- package/examples/events/info.json +0 -60
- package/examples/events/package.json +0 -23
- package/examples/marketing/README.md +0 -60
- package/examples/marketing/assets/AGENTS.md.append +0 -15
- package/examples/marketing/assets/public/logo.png +0 -0
- package/examples/marketing/assets/public/motorcycle-adventure.jpg +0 -0
- package/examples/marketing/assets/public/motorcycle-cruiser.jpg +0 -0
- package/examples/marketing/assets/public/motorcycle-scooter.jpg +0 -0
- package/examples/marketing/assets/public/motorcycle-sport.jpg +0 -0
- package/examples/marketing/assets/public/motorcycle-supersport.jpg +0 -0
- package/examples/marketing/assets/src/components/Header.tsx +0 -36
- package/examples/marketing/assets/src/components/MotorcycleAIAssistant.tsx +0 -162
- package/examples/marketing/assets/src/components/MotorcycleRecommendation.tsx +0 -53
- package/examples/marketing/assets/src/data/motorcycles.ts.ejs +0 -77
- package/examples/marketing/assets/src/lib/motorcycle-ai-hook.ts +0 -24
- package/examples/marketing/assets/src/lib/motorcycle-tools.ts +0 -42
- package/examples/marketing/assets/src/routes/__root.tsx +0 -57
- package/examples/marketing/assets/src/routes/api.motorcycle-chat.ts +0 -78
- package/examples/marketing/assets/src/routes/index.tsx +0 -72
- package/examples/marketing/assets/src/routes/motorcycles/$motorcycleId.tsx +0 -56
- package/examples/marketing/assets/src/store/motorcycle-assistant.ts +0 -3
- package/examples/marketing/assets/src/styles.css +0 -212
- package/examples/marketing/info.json +0 -32
- package/examples/marketing/package.json +0 -14
- package/examples/portfolio/README.md +0 -49
- package/examples/portfolio/assets/AGENTS.md.append +0 -21
- package/examples/portfolio/assets/content/blog/getting-started-with-tanstack.md +0 -53
- package/examples/portfolio/assets/content/blog/react-19-features.md +0 -78
- package/examples/portfolio/assets/content/blog/tailwind-css-v4-guide.md +0 -60
- package/examples/portfolio/assets/content/education/code-school.md +0 -17
- package/examples/portfolio/assets/content/jobs/initech-junior.md +0 -20
- package/examples/portfolio/assets/content/projects/portfolio-site.md +0 -15
- package/examples/portfolio/assets/content/projects/task-manager.md +0 -15
- package/examples/portfolio/assets/content-collections.ts +0 -65
- package/examples/portfolio/assets/public/contact.html +0 -6
- package/examples/portfolio/assets/public/headshot-on-white.jpg +0 -0
- package/examples/portfolio/assets/src/components/Header.tsx +0 -33
- package/examples/portfolio/assets/src/components/ResumeAssistant.tsx +0 -193
- package/examples/portfolio/assets/src/components/ui/badge.tsx +0 -46
- package/examples/portfolio/assets/src/components/ui/card.tsx +0 -92
- package/examples/portfolio/assets/src/components/ui/checkbox.tsx +0 -30
- package/examples/portfolio/assets/src/components/ui/hover-card.tsx +0 -44
- package/examples/portfolio/assets/src/components/ui/separator.tsx +0 -26
- package/examples/portfolio/assets/src/lib/resume-ai-hook.ts +0 -21
- package/examples/portfolio/assets/src/lib/resume-tools.ts +0 -165
- package/examples/portfolio/assets/src/lib/utils.ts +0 -6
- package/examples/portfolio/assets/src/routes/__root.tsx +0 -57
- package/examples/portfolio/assets/src/routes/api.resume-chat.ts +0 -116
- package/examples/portfolio/assets/src/routes/blog/$slug.tsx +0 -73
- package/examples/portfolio/assets/src/routes/contact.tsx +0 -121
- package/examples/portfolio/assets/src/routes/index.tsx +0 -55
- package/examples/portfolio/assets/src/routes/projects.tsx +0 -62
- package/examples/portfolio/assets/src/routes/resume.tsx +0 -220
- package/examples/portfolio/assets/src/styles.css +0 -138
- package/examples/portfolio/info.json +0 -49
- package/examples/portfolio/package.json +0 -26
- package/examples/resume/README.md +0 -82
- package/examples/resume/assets/AGENTS.md.append +0 -19
- package/examples/resume/assets/content/education/code-school.md +0 -17
- package/examples/resume/assets/content/jobs/freelance.md.ejs +0 -13
- package/examples/resume/assets/content/jobs/initech-junior.md +0 -20
- package/examples/resume/assets/content/jobs/initech-lead.md.ejs +0 -29
- package/examples/resume/assets/content/jobs/initrode-senior.md.ejs +0 -28
- package/examples/resume/assets/content-collections.ts +0 -36
- package/examples/resume/assets/public/headshot-on-white.jpg +0 -0
- package/examples/resume/assets/src/components/Header.tsx +0 -33
- package/examples/resume/assets/src/components/ResumeAssistant.tsx +0 -193
- package/examples/resume/assets/src/components/ui/badge.tsx +0 -46
- package/examples/resume/assets/src/components/ui/card.tsx +0 -92
- package/examples/resume/assets/src/components/ui/checkbox.tsx +0 -30
- package/examples/resume/assets/src/components/ui/hover-card.tsx +0 -44
- package/examples/resume/assets/src/components/ui/separator.tsx +0 -26
- package/examples/resume/assets/src/lib/resume-ai-hook.ts +0 -21
- package/examples/resume/assets/src/lib/resume-tools.ts +0 -165
- package/examples/resume/assets/src/lib/utils.ts +0 -6
- package/examples/resume/assets/src/routes/api.resume-chat.ts +0 -110
- package/examples/resume/assets/src/routes/index.tsx +0 -220
- package/examples/resume/assets/src/styles.css +0 -138
- package/examples/resume/info.json +0 -34
- package/examples/resume/package.json +0 -26
- package/examples/saas/README.md +0 -16
- package/examples/saas/assets/AGENTS.md.append +0 -9
- package/examples/saas/assets/src/components/Header.tsx +0 -17
- package/examples/saas/assets/src/routes/__root.tsx +0 -57
- package/examples/saas/assets/src/routes/faq.tsx +0 -94
- package/examples/saas/assets/src/routes/index.tsx +0 -197
- package/examples/saas/info.json +0 -22
- package/examples/saas/package.json +0 -1
- package/examples/survey/README.md +0 -20
- package/examples/survey/assets/AGENTS.md.append +0 -9
- package/examples/survey/assets/public/form-survey.html +0 -15
- package/examples/survey/assets/src/components/SurveyForm.tsx +0 -128
- package/examples/survey/assets/src/routes/index.tsx +0 -14
- package/examples/survey/info.json +0 -18
- package/examples/survey/package.json +0 -1
- package/project/base/AGENTS.md +0 -86
- package/project/base/_dot_claude/skills/content-collections/SKILL.md +0 -505
- package/project/base/_dot_claude/skills/netlify-blobs/SKILL.md +0 -410
- package/project/base/_dot_claude/skills/netlify-db/SKILL.md +0 -424
- package/project/base/_dot_claude/skills/netlify-debugging/SKILL.md +0 -419
- package/project/base/_dot_claude/skills/netlify-forms/SKILL.md +0 -243
- package/project/base/_dot_claude/skills/netlify-functions/SKILL.md +0 -372
- package/project/base/_dot_claude/skills/tanstack-start-api-routes/SKILL.md +0 -421
- package/project/base/_dot_claude/skills/tanstack-start-loaders/SKILL.md +0 -426
- package/project/base/_dot_claude/skills/tanstack-start-project-setup/SKILL.md +0 -493
- package/project/base/_dot_claude/skills/tanstack-start-routes/SKILL.md +0 -430
- package/project/base/_dot_claude/skills/tanstack-start-server-functions/SKILL.md +0 -445
- package/project/base/_dot_claude/skills/tanstack-start-typesafe-routing/SKILL.md +0 -494
- package/project/base/_dot_gitignore +0 -8
- package/project/base/netlify.toml +0 -7
- package/project/base/package.json +0 -33
- package/project/base/public/favicon.ico +0 -0
- package/project/base/public/tanstack-circle-logo.png +0 -0
- package/project/base/public/tanstack-word-logo-white.svg +0 -1
- package/project/base/src/components/Header.tsx +0 -17
- package/project/base/src/components/HeaderNav.tsx.ejs +0 -179
- package/project/base/src/router.tsx +0 -15
- package/project/base/src/routes/__root.tsx +0 -57
- package/project/base/src/routes/index.tsx +0 -48
- package/project/base/src/styles.css +0 -15
- package/project/base/tsconfig.json +0 -28
- package/project/base/vite.config.ts.ejs +0 -25
- package/project/info.json +0 -17
- package/project/packages.json +0 -22
- package/scripts/check-outdated-packages.js +0 -421
- package/src/agents-md.ts +0 -139
package/src/cli.ts
CHANGED
|
@@ -1,29 +1,19 @@
|
|
|
1
|
-
import { resolve, basename
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
'--
|
|
142
|
-
'
|
|
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
|
|
179
|
-
|
|
180
|
-
|
|
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
|
-
//
|
|
193
|
-
|
|
194
|
-
|
|
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
|
-
//
|
|
212
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
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
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
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
|
-
|
|
328
|
-
|
|
152
|
+
// Copy starter files to target directory
|
|
153
|
+
await cp(starterPath, targetDir, { recursive: true })
|
|
329
154
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
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
|
-
|
|
350
|
-
|
|
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
|
-
|
|
5
|
-
packageManager
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|