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.
- package/dist/cli.js +123 -206
- 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 +152 -269
- 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 -22
- 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 -22
- 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 -18
- 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 -25
- 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 -40
- 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 -20
- 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 -19
- 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 -38
- 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 -61
- 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 -33
- 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 -50
- 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 -35
- 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 -23
- 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 -19
- 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/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
|
-
|
|
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
|
-
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
|
-
//
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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
|
-
//
|
|
213
|
-
if (
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
-
|
|
222
|
-
|
|
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
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
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
|
-
|
|
289
|
-
|
|
290
|
-
|
|
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
|
-
//
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
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
|
-
|
|
351
|
-
|
|
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
|
-
|
|
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
|
}
|