kaddidlehopper 0.1.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/CONTEXT.md +139 -0
- package/README.md +47 -0
- package/add-ons/ai/README.md +34 -0
- package/add-ons/ai/assets/_dot_env.local.append +13 -0
- package/add-ons/ai/assets/src/components/AIAssistant.tsx +149 -0
- package/add-ons/ai/assets/src/lib/ai-hook.ts +21 -0
- package/add-ons/ai/assets/src/lib/weather-tools.ts +30 -0
- package/add-ons/ai/assets/src/routes/api.chat.ts +94 -0
- package/add-ons/ai/assets/src/routes/chat.css +175 -0
- package/add-ons/ai/assets/src/routes/chat.tsx +141 -0
- package/add-ons/ai/info.json +27 -0
- package/add-ons/ai/package.json +17 -0
- package/add-ons/ai/small-logo.svg +8 -0
- package/dist/cli.js +251 -0
- package/dist/index.js +33 -0
- package/dist/types/cli.d.ts +8 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/types.d.ts +14 -0
- package/dist/types.js +1 -0
- package/examples/blog/README.md +60 -0
- package/examples/blog/assets/content/posts/beach.md +12 -0
- package/examples/blog/assets/content/posts/jungle.md.ejs +12 -0
- package/examples/blog/assets/content/posts/mountains.md.ejs +12 -0
- package/examples/blog/assets/content/posts/snorkeling.md.ejs +12 -0
- package/examples/blog/assets/content/posts/waterfall.md.ejs +12 -0
- package/examples/blog/assets/content-collections.ts +30 -0
- 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 +52 -0
- package/examples/blog/assets/src/components/VacayAssistant.tsx +205 -0
- package/examples/blog/assets/src/components/blog-posts.tsx +78 -0
- package/examples/blog/assets/src/components/ui/card.tsx +92 -0
- package/examples/blog/assets/src/lib/blog-ai-hook.ts +25 -0
- package/examples/blog/assets/src/lib/blog-tools.ts +111 -0
- package/examples/blog/assets/src/lib/utils.ts +6 -0
- package/examples/blog/assets/src/routes/__root.tsx +57 -0
- package/examples/blog/assets/src/routes/api.blog-chat.ts +117 -0
- package/examples/blog/assets/src/routes/category.$category.tsx +19 -0
- package/examples/blog/assets/src/routes/index.tsx +19 -0
- package/examples/blog/assets/src/routes/posts.$slug.tsx +63 -0
- package/examples/blog/assets/src/styles.css +138 -0
- package/examples/blog/info.json +43 -0
- package/examples/blog/package.json +23 -0
- package/examples/events/README.md +110 -0
- package/examples/events/assets/content/speakers/andre-costa.md +22 -0
- package/examples/events/assets/content/speakers/hans-mueller.md.ejs +22 -0
- package/examples/events/assets/content/speakers/isabella-martinez.md.ejs +22 -0
- package/examples/events/assets/content/speakers/kenji-nakamura.md.ejs +22 -0
- package/examples/events/assets/content/speakers/marie-dubois.md.ejs +20 -0
- package/examples/events/assets/content/speakers/priya-sharma.md.ejs +22 -0
- package/examples/events/assets/content/talks/croissant-lamination-secrets.md +39 -0
- package/examples/events/assets/content/talks/french-macaron-mastery.md.ejs +39 -0
- package/examples/events/assets/content/talks/neapolitan-pizza-tradition-meets-innovation.md.ejs +39 -0
- package/examples/events/assets/content/talks/savory-breads-of-the-mediterranean.md.ejs +39 -0
- package/examples/events/assets/content/talks/sourdough-from-starter-to-masterpiece.md.ejs +36 -0
- package/examples/events/assets/content/talks/the-art-of-the-perfect-tart.md.ejs +32 -0
- package/examples/events/assets/content/talks/the-science-of-sugar.md.ejs +39 -0
- package/examples/events/assets/content/talks/umami-in-pastry-east-meets-west.md.ejs +39 -0
- package/examples/events/assets/content-collections.ts +56 -0
- 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 +1 -0
- package/examples/events/assets/src/components/Header.tsx +59 -0
- package/examples/events/assets/src/components/HeaderNav.tsx +67 -0
- package/examples/events/assets/src/components/HeroCarousel.tsx +61 -0
- package/examples/events/assets/src/components/RemyAssistant.tsx +207 -0
- package/examples/events/assets/src/components/SpeakerCard.tsx +67 -0
- package/examples/events/assets/src/components/TalkCard.tsx +77 -0
- package/examples/events/assets/src/components/ui/card.tsx +92 -0
- package/examples/events/assets/src/lib/conference-ai-hook.ts +26 -0
- package/examples/events/assets/src/lib/conference-tools.ts +210 -0
- package/examples/events/assets/src/lib/model-selection.ts +1 -0
- package/examples/events/assets/src/lib/utils.ts +6 -0
- package/examples/events/assets/src/routes/__root.tsx +70 -0
- package/examples/events/assets/src/routes/api.remy-chat.ts +119 -0
- package/examples/events/assets/src/routes/index.tsx +192 -0
- package/examples/events/assets/src/routes/schedule.index.tsx +274 -0
- package/examples/events/assets/src/routes/speakers.$slug.tsx +122 -0
- package/examples/events/assets/src/routes/speakers.index.tsx +40 -0
- package/examples/events/assets/src/routes/talks.$slug.tsx +116 -0
- package/examples/events/assets/src/routes/talks.index.tsx +40 -0
- package/examples/events/assets/src/styles.css +182 -0
- package/examples/events/info.json +74 -0
- package/examples/events/package.json +23 -0
- package/examples/marketing/README.md +60 -0
- 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 +36 -0
- package/examples/marketing/assets/src/components/MotorcycleAIAssistant.tsx +162 -0
- package/examples/marketing/assets/src/components/MotorcycleRecommendation.tsx +53 -0
- package/examples/marketing/assets/src/data/motorcycles.ts.ejs +77 -0
- package/examples/marketing/assets/src/lib/motorcycle-ai-hook.ts +24 -0
- package/examples/marketing/assets/src/lib/motorcycle-tools.ts +42 -0
- package/examples/marketing/assets/src/routes/__root.tsx +57 -0
- package/examples/marketing/assets/src/routes/api.motorcycle-chat.ts +78 -0
- package/examples/marketing/assets/src/routes/index.tsx +72 -0
- package/examples/marketing/assets/src/routes/motorcycles/$motorcycleId.tsx +56 -0
- package/examples/marketing/assets/src/store/motorcycle-assistant.ts +3 -0
- package/examples/marketing/assets/src/styles.css +212 -0
- package/examples/marketing/info.json +38 -0
- package/examples/marketing/package.json +14 -0
- package/examples/resume/README.md +82 -0
- package/examples/resume/assets/content/education/code-school.md +17 -0
- package/examples/resume/assets/content/jobs/freelance.md.ejs +13 -0
- package/examples/resume/assets/content/jobs/initech-junior.md +20 -0
- package/examples/resume/assets/content/jobs/initech-lead.md.ejs +29 -0
- package/examples/resume/assets/content/jobs/initrode-senior.md.ejs +28 -0
- package/examples/resume/assets/content-collections.ts +36 -0
- package/examples/resume/assets/public/headshot-on-white.jpg +0 -0
- package/examples/resume/assets/src/components/Header.tsx +33 -0
- package/examples/resume/assets/src/components/ResumeAssistant.tsx +193 -0
- package/examples/resume/assets/src/components/ui/badge.tsx +46 -0
- package/examples/resume/assets/src/components/ui/card.tsx +92 -0
- package/examples/resume/assets/src/components/ui/checkbox.tsx +30 -0
- package/examples/resume/assets/src/components/ui/hover-card.tsx +44 -0
- package/examples/resume/assets/src/components/ui/separator.tsx +26 -0
- package/examples/resume/assets/src/lib/resume-ai-hook.ts +21 -0
- package/examples/resume/assets/src/lib/resume-tools.ts +165 -0
- package/examples/resume/assets/src/lib/utils.ts +6 -0
- package/examples/resume/assets/src/routes/api.resume-chat.ts +110 -0
- package/examples/resume/assets/src/routes/index.tsx +220 -0
- package/examples/resume/assets/src/styles.css +138 -0
- package/examples/resume/info.json +25 -0
- package/examples/resume/package.json +26 -0
- package/package.json +39 -0
- package/project/base/_dot_claude/skills/content-collections/SKILL.md +505 -0
- package/project/base/_dot_claude/skills/netlify-blobs/SKILL.md +410 -0
- package/project/base/_dot_claude/skills/netlify-db/SKILL.md +424 -0
- package/project/base/_dot_claude/skills/netlify-debugging/SKILL.md +419 -0
- package/project/base/_dot_claude/skills/netlify-forms/SKILL.md +243 -0
- package/project/base/_dot_claude/skills/netlify-functions/SKILL.md +372 -0
- package/project/base/_dot_claude/skills/tanstack-start-api-routes/SKILL.md +421 -0
- package/project/base/_dot_claude/skills/tanstack-start-loaders/SKILL.md +426 -0
- package/project/base/_dot_claude/skills/tanstack-start-project-setup/SKILL.md +493 -0
- package/project/base/_dot_claude/skills/tanstack-start-routes/SKILL.md +430 -0
- package/project/base/_dot_claude/skills/tanstack-start-server-functions/SKILL.md +445 -0
- package/project/base/_dot_claude/skills/tanstack-start-typesafe-routing/SKILL.md +494 -0
- package/project/base/_dot_gitignore +8 -0
- package/project/base/netlify.toml +7 -0
- package/project/base/package.json +33 -0
- 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 +1 -0
- package/project/base/src/components/Header.tsx +17 -0
- package/project/base/src/components/HeaderNav.tsx.ejs +179 -0
- package/project/base/src/router.tsx +15 -0
- package/project/base/src/routes/__root.tsx +57 -0
- package/project/base/src/routes/index.tsx +48 -0
- package/project/base/src/styles.css +15 -0
- package/project/base/tsconfig.json +28 -0
- package/project/base/vite.config.ts.ejs +25 -0
- package/project/packages.json +22 -0
- package/scripts/check-outdated-packages.js +421 -0
- package/src/cli.ts +343 -0
- package/src/index.ts +49 -0
- package/src/types.ts +15 -0
- package/tsconfig.json +17 -0
package/src/cli.ts
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import { resolve, basename, join } from 'node:path'
|
|
2
|
+
import { unlink } from 'node:fs/promises'
|
|
3
|
+
import { Command, InvalidArgumentError } from 'commander'
|
|
4
|
+
import chalk from 'chalk'
|
|
5
|
+
import validatePackageName from 'validate-npm-package-name'
|
|
6
|
+
|
|
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 type { CliOptions } from './types.js'
|
|
20
|
+
import type {
|
|
21
|
+
Options,
|
|
22
|
+
PackageManager,
|
|
23
|
+
} from '@tanstack/cta-engine'
|
|
24
|
+
|
|
25
|
+
// Utility functions
|
|
26
|
+
function sanitizePackageName(name: string): string {
|
|
27
|
+
return name
|
|
28
|
+
.toLowerCase()
|
|
29
|
+
.replace(/\s+/g, '-')
|
|
30
|
+
.replace(/_/g, '-')
|
|
31
|
+
.replace(/[^a-z0-9-]/g, '')
|
|
32
|
+
.replace(/^[^a-z]+/, '')
|
|
33
|
+
.replace(/-+/g, '-')
|
|
34
|
+
.replace(/-$/, '')
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function getCurrentDirectoryName(): string {
|
|
38
|
+
return basename(process.cwd())
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function validateProjectName(name: string) {
|
|
42
|
+
const { validForNewPackages, validForOldPackages, errors, warnings } =
|
|
43
|
+
validatePackageName(name)
|
|
44
|
+
const error = errors?.[0] || warnings?.[0]
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
valid: validForNewPackages && validForOldPackages,
|
|
48
|
+
error:
|
|
49
|
+
error?.replace(/name/g, 'Project name') ||
|
|
50
|
+
'Project name does not meet npm package naming requirements',
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Create environment with UI functions for console output
|
|
55
|
+
function createEnvironment(appName: string) {
|
|
56
|
+
const env = createDefaultEnvironment()
|
|
57
|
+
env.appName = appName
|
|
58
|
+
env.intro = (message: string) => console.log(chalk.bold.cyan(message))
|
|
59
|
+
env.outro = (message: string) => console.log(chalk.bold.green(message))
|
|
60
|
+
env.info = (title?: string, message?: string) => {
|
|
61
|
+
if (title && message) {
|
|
62
|
+
console.log(chalk.blue(`${title}: ${message}`))
|
|
63
|
+
} else if (title) {
|
|
64
|
+
console.log(chalk.blue(title))
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
env.error = (title?: string, message?: string) => {
|
|
68
|
+
if (title && message) {
|
|
69
|
+
console.error(chalk.red(`${title}: ${message}`))
|
|
70
|
+
} else if (title) {
|
|
71
|
+
console.error(chalk.red(title))
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
env.warn = (title?: string, message?: string) => {
|
|
75
|
+
if (title && message) {
|
|
76
|
+
console.warn(chalk.yellow(`${title}: ${message}`))
|
|
77
|
+
} else if (title) {
|
|
78
|
+
console.warn(chalk.yellow(title))
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
env.spinner = () => ({
|
|
82
|
+
start: (message: string) => console.log(chalk.gray(`⟳ ${message}`)),
|
|
83
|
+
stop: (message: string) => console.log(chalk.green(`✓ ${message}`)),
|
|
84
|
+
})
|
|
85
|
+
return env
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface CliConfig {
|
|
89
|
+
name: string
|
|
90
|
+
appName: string
|
|
91
|
+
defaultFramework: string
|
|
92
|
+
forcedMode?: string
|
|
93
|
+
forcedAddOns?: Array<string>
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function cli({
|
|
97
|
+
name,
|
|
98
|
+
appName,
|
|
99
|
+
defaultFramework,
|
|
100
|
+
forcedMode,
|
|
101
|
+
forcedAddOns = [],
|
|
102
|
+
}: CliConfig) {
|
|
103
|
+
const environment = createEnvironment(appName)
|
|
104
|
+
const program = new Command()
|
|
105
|
+
|
|
106
|
+
const defaultMode = forcedMode || 'file-router'
|
|
107
|
+
|
|
108
|
+
program.name(name).description(`CLI to create a new ${appName} application`)
|
|
109
|
+
|
|
110
|
+
program.argument('[project-name]', 'name of the project')
|
|
111
|
+
|
|
112
|
+
program
|
|
113
|
+
.option('--no-install', 'skip installing dependencies')
|
|
114
|
+
.option<PackageManager>(
|
|
115
|
+
`--package-manager <${SUPPORTED_PACKAGE_MANAGERS.join('|')}>`,
|
|
116
|
+
`Explicitly tell the CLI to use this package manager`,
|
|
117
|
+
(value) => {
|
|
118
|
+
if (!SUPPORTED_PACKAGE_MANAGERS.includes(value as PackageManager)) {
|
|
119
|
+
throw new InvalidArgumentError(
|
|
120
|
+
`Invalid package manager: ${value}. The following are allowed: ${SUPPORTED_PACKAGE_MANAGERS.join(', ')}`,
|
|
121
|
+
)
|
|
122
|
+
}
|
|
123
|
+
return value as PackageManager
|
|
124
|
+
},
|
|
125
|
+
)
|
|
126
|
+
.option<Array<string> | boolean>(
|
|
127
|
+
'--add-ons [...add-ons]',
|
|
128
|
+
'pick from a list of available add-ons (comma separated list)',
|
|
129
|
+
(value: string) => {
|
|
130
|
+
let addOns: Array<string> | boolean = !!value
|
|
131
|
+
if (typeof value === 'string') {
|
|
132
|
+
addOns = value.split(',').map((addon) => addon.trim())
|
|
133
|
+
}
|
|
134
|
+
return addOns
|
|
135
|
+
},
|
|
136
|
+
)
|
|
137
|
+
.option('--list-add-ons', 'list all available add-ons', false)
|
|
138
|
+
.option('--list-addons-json', 'list all available add-ons as JSON', false)
|
|
139
|
+
.option(
|
|
140
|
+
'--addon-details <addon-id>',
|
|
141
|
+
'show detailed information about a specific add-on',
|
|
142
|
+
)
|
|
143
|
+
.option('--no-git', 'do not create a git repository')
|
|
144
|
+
.option(
|
|
145
|
+
'--target-dir <path>',
|
|
146
|
+
'the target directory for the application root',
|
|
147
|
+
)
|
|
148
|
+
.option(
|
|
149
|
+
'-f, --force',
|
|
150
|
+
'force project creation even if the target directory is not empty',
|
|
151
|
+
false,
|
|
152
|
+
)
|
|
153
|
+
.option(
|
|
154
|
+
'--bare-bones',
|
|
155
|
+
'create minimal scaffolding for LLM modification',
|
|
156
|
+
false,
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
program.action(async (projectName: string, options: CliOptions) => {
|
|
160
|
+
const framework = getFrameworkByName(defaultFramework)
|
|
161
|
+
if (!framework) {
|
|
162
|
+
console.error(`Framework '${defaultFramework}' not found`)
|
|
163
|
+
process.exit(1)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Handle --list-addons-json
|
|
167
|
+
if (options.listAddonsJson) {
|
|
168
|
+
const addOns = await getAllAddOns(framework, defaultMode)
|
|
169
|
+
const serialized = addOns
|
|
170
|
+
.filter((a) => !forcedAddOns.includes(a.id))
|
|
171
|
+
.map((addon) => ({
|
|
172
|
+
id: addon.id,
|
|
173
|
+
name: addon.name,
|
|
174
|
+
description: addon.description,
|
|
175
|
+
type: addon.type,
|
|
176
|
+
options: addon.options,
|
|
177
|
+
}))
|
|
178
|
+
console.log(JSON.stringify(serialized, null, 2))
|
|
179
|
+
return
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Handle --list-add-ons (text format)
|
|
183
|
+
if (options.listAddOns) {
|
|
184
|
+
const addOns = await getAllAddOns(framework, defaultMode)
|
|
185
|
+
let hasConfigurableAddOns = false
|
|
186
|
+
for (const addOn of addOns.filter((a) => !forcedAddOns.includes(a.id))) {
|
|
187
|
+
const hasOptions =
|
|
188
|
+
addOn.options && Object.keys(addOn.options).length > 0
|
|
189
|
+
const optionMarker = hasOptions ? '*' : ' '
|
|
190
|
+
if (hasOptions) hasConfigurableAddOns = true
|
|
191
|
+
console.log(
|
|
192
|
+
`${optionMarker} ${chalk.bold(addOn.id)}: ${addOn.description}`,
|
|
193
|
+
)
|
|
194
|
+
}
|
|
195
|
+
if (hasConfigurableAddOns) {
|
|
196
|
+
console.log('\n* = has configuration options')
|
|
197
|
+
}
|
|
198
|
+
return
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Handle --addon-details
|
|
202
|
+
if (options.addonDetails) {
|
|
203
|
+
const addOns = await getAllAddOns(framework, defaultMode)
|
|
204
|
+
const addOn = addOns.find((a) => a.id === options.addonDetails)
|
|
205
|
+
if (!addOn) {
|
|
206
|
+
console.error(`Add-on '${options.addonDetails}' not found`)
|
|
207
|
+
process.exit(1)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
console.log(
|
|
211
|
+
`${chalk.bold.cyan('Add-on Details:')} ${chalk.bold(addOn.name)}`,
|
|
212
|
+
)
|
|
213
|
+
console.log(`${chalk.bold('ID:')} ${addOn.id}`)
|
|
214
|
+
console.log(`${chalk.bold('Description:')} ${addOn.description}`)
|
|
215
|
+
console.log(`${chalk.bold('Type:')} ${addOn.type}`)
|
|
216
|
+
console.log(`${chalk.bold('Phase:')} ${addOn.phase}`)
|
|
217
|
+
console.log(`${chalk.bold('Supported Modes:')} ${addOn.modes.join(', ')}`)
|
|
218
|
+
|
|
219
|
+
if (addOn.dependsOn && addOn.dependsOn.length > 0) {
|
|
220
|
+
console.log(
|
|
221
|
+
`${chalk.bold('Dependencies:')} ${addOn.dependsOn.join(', ')}`,
|
|
222
|
+
)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (addOn.options && Object.keys(addOn.options).length > 0) {
|
|
226
|
+
console.log(`\n${chalk.bold.yellow('Configuration Options:')}`)
|
|
227
|
+
for (const [optionName, option] of Object.entries(addOn.options)) {
|
|
228
|
+
if (option && typeof option === 'object' && 'type' in option) {
|
|
229
|
+
const opt = option as { label: string; description?: string; type: string; default: unknown; options?: Array<{ value: string; label: string }> }
|
|
230
|
+
console.log(` ${chalk.bold(optionName)}:`)
|
|
231
|
+
console.log(` Label: ${opt.label}`)
|
|
232
|
+
if (opt.description) {
|
|
233
|
+
console.log(` Description: ${opt.description}`)
|
|
234
|
+
}
|
|
235
|
+
console.log(` Type: ${opt.type}`)
|
|
236
|
+
console.log(` Default: ${opt.default}`)
|
|
237
|
+
if (opt.type === 'select' && opt.options) {
|
|
238
|
+
console.log(` Available values:`)
|
|
239
|
+
for (const choice of opt.options) {
|
|
240
|
+
console.log(` - ${choice.value}: ${choice.label}`)
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
} else {
|
|
246
|
+
console.log(`\n${chalk.gray('No configuration options available')}`)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (addOn.routes && addOn.routes.length > 0) {
|
|
250
|
+
console.log(`\n${chalk.bold.green('Routes:')}`)
|
|
251
|
+
for (const route of addOn.routes) {
|
|
252
|
+
console.log(` ${chalk.bold(route.url)} (${route.name})`)
|
|
253
|
+
console.log(` File: ${route.path}`)
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Handle project creation
|
|
260
|
+
if (!projectName) {
|
|
261
|
+
console.error('Project name is required')
|
|
262
|
+
program.help()
|
|
263
|
+
return
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
let resolvedProjectName = projectName
|
|
267
|
+
let targetDir: string
|
|
268
|
+
|
|
269
|
+
// Handle "." as project name - use current directory
|
|
270
|
+
if (projectName === '.') {
|
|
271
|
+
resolvedProjectName = sanitizePackageName(getCurrentDirectoryName())
|
|
272
|
+
targetDir = resolve(process.cwd())
|
|
273
|
+
} else {
|
|
274
|
+
targetDir = options.targetDir
|
|
275
|
+
? resolve(options.targetDir)
|
|
276
|
+
: resolve(process.cwd(), projectName)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const { valid, error } = validateProjectName(resolvedProjectName)
|
|
280
|
+
if (!valid) {
|
|
281
|
+
console.error(error)
|
|
282
|
+
process.exit(1)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Determine selected add-ons
|
|
286
|
+
const selectedAddOns = new Set<string>([...forcedAddOns])
|
|
287
|
+
if (options.addOns && Array.isArray(options.addOns)) {
|
|
288
|
+
for (const a of options.addOns) {
|
|
289
|
+
selectedAddOns.add(a)
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const chosenAddOns = await finalizeAddOns(
|
|
294
|
+
framework,
|
|
295
|
+
defaultMode,
|
|
296
|
+
Array.from(selectedAddOns),
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
const finalOptions: Options = {
|
|
300
|
+
projectName: resolvedProjectName,
|
|
301
|
+
targetDir,
|
|
302
|
+
framework,
|
|
303
|
+
mode: defaultMode,
|
|
304
|
+
typescript: true,
|
|
305
|
+
tailwind: true,
|
|
306
|
+
packageManager:
|
|
307
|
+
options.packageManager ||
|
|
308
|
+
getPackageManager() ||
|
|
309
|
+
DEFAULT_PACKAGE_MANAGER,
|
|
310
|
+
git: options.git !== false,
|
|
311
|
+
install: options.install !== false,
|
|
312
|
+
chosenAddOns,
|
|
313
|
+
addOnOptions: {
|
|
314
|
+
...populateAddOnOptionsDefaults(chosenAddOns),
|
|
315
|
+
project: { bareBones: options.bareBones ?? false },
|
|
316
|
+
},
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
environment.intro(`Creating a new ${appName} app in ${resolvedProjectName}...`)
|
|
320
|
+
await createApp(environment, finalOptions)
|
|
321
|
+
|
|
322
|
+
// Delete files specified in bareBones.deleteFiles for each add-on when in bare-bones mode
|
|
323
|
+
if (options.bareBones) {
|
|
324
|
+
for (const addOn of chosenAddOns) {
|
|
325
|
+
const addOnWithBareBones = addOn as typeof addOn & {
|
|
326
|
+
bareBones?: { deleteFiles?: Array<string> }
|
|
327
|
+
}
|
|
328
|
+
if (addOnWithBareBones.bareBones?.deleteFiles) {
|
|
329
|
+
for (const file of addOnWithBareBones.bareBones.deleteFiles) {
|
|
330
|
+
const filePath = join(targetDir, file)
|
|
331
|
+
try {
|
|
332
|
+
await unlink(filePath)
|
|
333
|
+
} catch {
|
|
334
|
+
// File may not exist, ignore errors
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
program.parse()
|
|
343
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
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
|
+
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
|
+
})
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { PackageManager } from '@tanstack/cta-engine'
|
|
2
|
+
|
|
3
|
+
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
|
|
15
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "ES2020",
|
|
5
|
+
"outDir": "./dist",
|
|
6
|
+
"rootDir": "./src",
|
|
7
|
+
"strict": true,
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"forceConsistentCasingInFileNames": true,
|
|
11
|
+
"moduleResolution": "bundler",
|
|
12
|
+
"declaration": true,
|
|
13
|
+
"declarationDir": "./dist/types"
|
|
14
|
+
},
|
|
15
|
+
"include": ["./src/**/*.ts"],
|
|
16
|
+
"exclude": ["node_modules", "dist"]
|
|
17
|
+
}
|