@tanstack/cli 0.0.8 → 0.48.2
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/bin.js +7 -0
- package/dist/cli.js +481 -0
- package/dist/command-line.js +174 -0
- package/dist/dev-watch.js +290 -0
- package/dist/file-syncer.js +148 -0
- package/dist/index.js +1 -0
- package/dist/mcp/api.js +31 -0
- package/dist/mcp/tools.js +250 -0
- package/dist/mcp/types.js +37 -0
- package/dist/mcp.js +121 -0
- package/dist/options.js +162 -0
- package/dist/types/bin.d.ts +2 -0
- package/dist/types/cli.d.ts +16 -0
- package/dist/types/command-line.d.ts +10 -0
- package/dist/types/dev-watch.d.ts +27 -0
- package/dist/types/file-syncer.d.ts +18 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/mcp/api.d.ts +4 -0
- package/dist/types/mcp/tools.d.ts +2 -0
- package/dist/types/mcp/types.d.ts +217 -0
- package/dist/types/mcp.d.ts +6 -0
- package/dist/types/options.d.ts +8 -0
- package/dist/types/types.d.ts +25 -0
- package/dist/types/ui-environment.d.ts +2 -0
- package/dist/types/ui-prompts.d.ts +12 -0
- package/dist/types/utils.d.ts +8 -0
- package/dist/types.js +1 -0
- package/dist/ui-environment.js +52 -0
- package/dist/ui-prompts.js +244 -0
- package/dist/utils.js +30 -0
- package/package.json +46 -46
- package/src/bin.ts +6 -93
- package/src/cli.ts +692 -0
- package/src/command-line.ts +236 -0
- package/src/dev-watch.ts +430 -0
- package/src/file-syncer.ts +205 -0
- package/src/index.ts +1 -85
- package/src/mcp.ts +190 -0
- package/src/options.ts +260 -0
- package/src/types.ts +27 -0
- package/src/ui-environment.ts +74 -0
- package/src/ui-prompts.ts +322 -0
- package/src/utils.ts +38 -0
- package/tests/command-line.test.ts +304 -0
- package/tests/index.test.ts +9 -0
- package/tests/mcp.test.ts +225 -0
- package/tests/options.test.ts +304 -0
- package/tests/setupVitest.ts +6 -0
- package/tests/ui-environment.test.ts +97 -0
- package/tests/ui-prompts.test.ts +238 -0
- package/tsconfig.json +17 -0
- package/vitest.config.js +7 -0
- package/dist/bin.cjs +0 -769
- package/dist/bin.d.cts +0 -1
- package/dist/bin.d.mts +0 -1
- package/dist/bin.mjs +0 -768
- package/dist/fetch-CbFFGJEw.cjs +0 -3
- package/dist/fetch-DG5dLrsb.cjs +0 -522
- package/dist/fetch-DhlVXS6S.mjs +0 -390
- package/dist/fetch-I_OVg8JX.mjs +0 -3
- package/dist/index.cjs +0 -37
- package/dist/index.d.cts +0 -1172
- package/dist/index.d.mts +0 -1172
- package/dist/index.mjs +0 -4
- package/dist/template-Szi7-AZJ.mjs +0 -2202
- package/dist/template-lWrIZhCQ.cjs +0 -2314
- package/src/api/fetch.test.ts +0 -114
- package/src/api/fetch.ts +0 -278
- package/src/cache/index.ts +0 -89
- package/src/commands/create.ts +0 -470
- package/src/commands/mcp.test.ts +0 -152
- package/src/commands/mcp.ts +0 -211
- package/src/engine/compile-with-addons.test.ts +0 -302
- package/src/engine/compile.test.ts +0 -404
- package/src/engine/compile.ts +0 -569
- package/src/engine/config-file.test.ts +0 -118
- package/src/engine/config-file.ts +0 -61
- package/src/engine/custom-addons/integration.ts +0 -323
- package/src/engine/custom-addons/shared.test.ts +0 -98
- package/src/engine/custom-addons/shared.ts +0 -281
- package/src/engine/custom-addons/template.test.ts +0 -288
- package/src/engine/custom-addons/template.ts +0 -124
- package/src/engine/template.test.ts +0 -256
- package/src/engine/template.ts +0 -269
- package/src/engine/types.ts +0 -336
- package/src/parse-gitignore.d.ts +0 -5
- package/src/templates/base.ts +0 -883
package/src/commands/create.ts
DELETED
|
@@ -1,470 +0,0 @@
|
|
|
1
|
-
import { resolve } from 'node:path'
|
|
2
|
-
import { existsSync, mkdirSync, writeFileSync } from 'node:fs'
|
|
3
|
-
import {
|
|
4
|
-
cancel,
|
|
5
|
-
confirm,
|
|
6
|
-
intro,
|
|
7
|
-
isCancel,
|
|
8
|
-
log,
|
|
9
|
-
multiselect,
|
|
10
|
-
note,
|
|
11
|
-
outro,
|
|
12
|
-
select,
|
|
13
|
-
spinner,
|
|
14
|
-
text,
|
|
15
|
-
} from '@clack/prompts'
|
|
16
|
-
import chalk from 'chalk'
|
|
17
|
-
import { BINARY_PREFIX, fetchIntegrations, fetchManifest } from '../api/fetch.js'
|
|
18
|
-
import { compile } from '../engine/compile.js'
|
|
19
|
-
import { writeConfigFile } from '../engine/config-file.js'
|
|
20
|
-
import { loadTemplate } from '../engine/custom-addons/template.js'
|
|
21
|
-
import type {
|
|
22
|
-
CustomTemplateCompiled,
|
|
23
|
-
IntegrationCompiled,
|
|
24
|
-
ManifestIntegration,
|
|
25
|
-
PackageManager,
|
|
26
|
-
RouterMode,
|
|
27
|
-
} from '../engine/types.js'
|
|
28
|
-
|
|
29
|
-
interface CreateOptions {
|
|
30
|
-
template?: string
|
|
31
|
-
packageManager?: PackageManager
|
|
32
|
-
integrations?: string
|
|
33
|
-
install?: boolean
|
|
34
|
-
git?: boolean
|
|
35
|
-
tailwind?: boolean
|
|
36
|
-
yes?: boolean
|
|
37
|
-
targetDir?: string
|
|
38
|
-
integrationsPath?: string
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const PACKAGE_MANAGERS: Array<PackageManager> = [
|
|
42
|
-
'pnpm',
|
|
43
|
-
'npm',
|
|
44
|
-
'yarn',
|
|
45
|
-
'bun',
|
|
46
|
-
'deno',
|
|
47
|
-
]
|
|
48
|
-
|
|
49
|
-
function detectPackageManager(): PackageManager {
|
|
50
|
-
const userAgent = process.env.npm_config_user_agent
|
|
51
|
-
if (userAgent) {
|
|
52
|
-
if (userAgent.includes('pnpm')) return 'pnpm'
|
|
53
|
-
if (userAgent.includes('yarn')) return 'yarn'
|
|
54
|
-
if (userAgent.includes('bun')) return 'bun'
|
|
55
|
-
if (userAgent.includes('deno')) return 'deno'
|
|
56
|
-
}
|
|
57
|
-
return 'npm'
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function validateProjectName(name: string): { valid: boolean; error?: string } {
|
|
61
|
-
if (!name) {
|
|
62
|
-
return { valid: false, error: 'Project name is required' }
|
|
63
|
-
}
|
|
64
|
-
if (!/^[a-z0-9-_]+$/.test(name)) {
|
|
65
|
-
return {
|
|
66
|
-
valid: false,
|
|
67
|
-
error:
|
|
68
|
-
'Project name can only contain lowercase letters, numbers, hyphens, and underscores',
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
return { valid: true }
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function groupIntegrationsByCategory(
|
|
75
|
-
integrations: Array<ManifestIntegration>,
|
|
76
|
-
): Record<string, Array<ManifestIntegration>> {
|
|
77
|
-
const groups: Record<string, Array<ManifestIntegration>> = {}
|
|
78
|
-
for (const integration of integrations) {
|
|
79
|
-
const category = integration.category ?? 'other'
|
|
80
|
-
groups[category] ??= []
|
|
81
|
-
groups[category].push(integration)
|
|
82
|
-
}
|
|
83
|
-
return groups
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const CATEGORY_LABELS: Record<string, string> = {
|
|
87
|
-
tanstack: 'TanStack',
|
|
88
|
-
database: 'Database',
|
|
89
|
-
orm: 'ORM',
|
|
90
|
-
auth: 'Authentication',
|
|
91
|
-
deploy: 'Deployment',
|
|
92
|
-
tooling: 'Tooling',
|
|
93
|
-
monitoring: 'Monitoring',
|
|
94
|
-
api: 'API',
|
|
95
|
-
i18n: 'Internationalization',
|
|
96
|
-
cms: 'CMS',
|
|
97
|
-
other: 'Other',
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const CATEGORY_ORDER = [
|
|
101
|
-
'tanstack',
|
|
102
|
-
'database',
|
|
103
|
-
'orm',
|
|
104
|
-
'auth',
|
|
105
|
-
'deploy',
|
|
106
|
-
'api',
|
|
107
|
-
'monitoring',
|
|
108
|
-
'tooling',
|
|
109
|
-
'i18n',
|
|
110
|
-
'cms',
|
|
111
|
-
'other',
|
|
112
|
-
]
|
|
113
|
-
|
|
114
|
-
export async function runCreate(
|
|
115
|
-
projectName: string | undefined,
|
|
116
|
-
options: CreateOptions,
|
|
117
|
-
): Promise<void> {
|
|
118
|
-
intro(chalk.bgCyan(chalk.black(' TanStack Start ')))
|
|
119
|
-
|
|
120
|
-
const s = spinner()
|
|
121
|
-
|
|
122
|
-
// Determine integrations source
|
|
123
|
-
const integrationsPath = options.integrationsPath
|
|
124
|
-
|
|
125
|
-
// Fetch manifest
|
|
126
|
-
s.start('Fetching available integrations...')
|
|
127
|
-
let manifest
|
|
128
|
-
try {
|
|
129
|
-
manifest = await fetchManifest(integrationsPath)
|
|
130
|
-
s.stop('Integrations loaded')
|
|
131
|
-
} catch (error) {
|
|
132
|
-
s.stop('Failed to fetch integrations')
|
|
133
|
-
log.error(
|
|
134
|
-
'Could not fetch integration manifest. Check your internet connection.',
|
|
135
|
-
)
|
|
136
|
-
process.exit(1)
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// Project name
|
|
140
|
-
let name = projectName
|
|
141
|
-
if (!name && !options.yes) {
|
|
142
|
-
const nameInput = await text({
|
|
143
|
-
message: 'Project name:',
|
|
144
|
-
placeholder: 'my-tanstack-app',
|
|
145
|
-
defaultValue: 'my-tanstack-app',
|
|
146
|
-
validate: (value) => {
|
|
147
|
-
const result = validateProjectName(value)
|
|
148
|
-
return result.valid ? undefined : result.error
|
|
149
|
-
},
|
|
150
|
-
})
|
|
151
|
-
|
|
152
|
-
if (isCancel(nameInput)) {
|
|
153
|
-
cancel('Operation cancelled.')
|
|
154
|
-
process.exit(0)
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
name = nameInput
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
name = name ?? 'my-tanstack-app'
|
|
161
|
-
const { valid, error } = validateProjectName(name)
|
|
162
|
-
if (!valid) {
|
|
163
|
-
log.error(error ?? 'Invalid project name')
|
|
164
|
-
process.exit(1)
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// Target directory
|
|
168
|
-
const targetDir = options.targetDir
|
|
169
|
-
? resolve(options.targetDir)
|
|
170
|
-
: resolve(process.cwd(), name)
|
|
171
|
-
|
|
172
|
-
if (existsSync(targetDir)) {
|
|
173
|
-
if (!options.yes) {
|
|
174
|
-
const overwrite = await confirm({
|
|
175
|
-
message: `Directory ${chalk.cyan(name)} already exists. Overwrite?`,
|
|
176
|
-
initialValue: false,
|
|
177
|
-
})
|
|
178
|
-
|
|
179
|
-
if (isCancel(overwrite) || !overwrite) {
|
|
180
|
-
cancel('Operation cancelled.')
|
|
181
|
-
process.exit(0)
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// Package manager
|
|
187
|
-
let packageManager: PackageManager = options.packageManager ?? detectPackageManager()
|
|
188
|
-
if (!options.packageManager && !options.yes) {
|
|
189
|
-
const pmChoice = await select({
|
|
190
|
-
message: 'Package manager:',
|
|
191
|
-
options: PACKAGE_MANAGERS.map((pm) => ({
|
|
192
|
-
value: pm,
|
|
193
|
-
label: pm,
|
|
194
|
-
})),
|
|
195
|
-
initialValue: packageManager,
|
|
196
|
-
})
|
|
197
|
-
|
|
198
|
-
if (isCancel(pmChoice)) {
|
|
199
|
-
cancel('Operation cancelled.')
|
|
200
|
-
process.exit(0)
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
packageManager = pmChoice
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// Tailwind
|
|
207
|
-
let tailwind = options.tailwind ?? true
|
|
208
|
-
if (options.tailwind === undefined && !options.yes) {
|
|
209
|
-
const twChoice = await confirm({
|
|
210
|
-
message: 'Include Tailwind CSS?',
|
|
211
|
-
initialValue: true,
|
|
212
|
-
})
|
|
213
|
-
|
|
214
|
-
if (isCancel(twChoice)) {
|
|
215
|
-
cancel('Operation cancelled.')
|
|
216
|
-
process.exit(0)
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
tailwind = twChoice
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// Integrations selection
|
|
223
|
-
let selectedIntegrationIds: Array<string> = []
|
|
224
|
-
|
|
225
|
-
if (options.integrations) {
|
|
226
|
-
selectedIntegrationIds = options.integrations.split(',').map((s) => s.trim())
|
|
227
|
-
} else if (!options.yes) {
|
|
228
|
-
// Group integrations by category for display
|
|
229
|
-
const availableIntegrations = manifest.integrations.filter(
|
|
230
|
-
(a) => a.type === 'integration' && a.modes.includes('file-router'),
|
|
231
|
-
)
|
|
232
|
-
const grouped = groupIntegrationsByCategory(availableIntegrations)
|
|
233
|
-
|
|
234
|
-
// Show category selection first
|
|
235
|
-
const categoryOptions = CATEGORY_ORDER.filter((cat) => grouped[cat]?.length)
|
|
236
|
-
.map((cat) => ({
|
|
237
|
-
value: cat,
|
|
238
|
-
label: CATEGORY_LABELS[cat] ?? cat,
|
|
239
|
-
hint: `${grouped[cat]!.length} integrations`,
|
|
240
|
-
}))
|
|
241
|
-
|
|
242
|
-
note(
|
|
243
|
-
'Use arrow keys to navigate, space to select/deselect, enter to confirm.',
|
|
244
|
-
'Integration Selection',
|
|
245
|
-
)
|
|
246
|
-
|
|
247
|
-
const selectedCategories = await multiselect({
|
|
248
|
-
message: 'Which categories would you like to explore?',
|
|
249
|
-
options: categoryOptions,
|
|
250
|
-
required: false,
|
|
251
|
-
})
|
|
252
|
-
|
|
253
|
-
if (isCancel(selectedCategories)) {
|
|
254
|
-
cancel('Operation cancelled.')
|
|
255
|
-
process.exit(0)
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
// For each selected category, show integration selection
|
|
259
|
-
for (const category of selectedCategories) {
|
|
260
|
-
const categoryIntegrations = grouped[category]
|
|
261
|
-
if (!categoryIntegrations?.length) continue
|
|
262
|
-
|
|
263
|
-
const integrationChoices = await multiselect({
|
|
264
|
-
message: `Select ${CATEGORY_LABELS[category]} integrations:`,
|
|
265
|
-
options: categoryIntegrations.map((integration) => ({
|
|
266
|
-
value: integration.id,
|
|
267
|
-
label: integration.name,
|
|
268
|
-
hint: integration.description,
|
|
269
|
-
})),
|
|
270
|
-
required: false,
|
|
271
|
-
})
|
|
272
|
-
|
|
273
|
-
if (isCancel(integrationChoices)) {
|
|
274
|
-
cancel('Operation cancelled.')
|
|
275
|
-
process.exit(0)
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
selectedIntegrationIds.push(...(integrationChoices))
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
// Resolve dependencies
|
|
283
|
-
const integrationMap = new Map<string, ManifestIntegration>(
|
|
284
|
-
manifest.integrations.map((a) => [a.id, a]),
|
|
285
|
-
)
|
|
286
|
-
const resolvedIds = new Set(selectedIntegrationIds)
|
|
287
|
-
|
|
288
|
-
for (const id of selectedIntegrationIds) {
|
|
289
|
-
const integration = integrationMap.get(id)
|
|
290
|
-
if (integration?.dependsOn) {
|
|
291
|
-
for (const dep of integration.dependsOn) {
|
|
292
|
-
resolvedIds.add(dep)
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// Show summary
|
|
298
|
-
if (resolvedIds.size > 0) {
|
|
299
|
-
const integrationList = Array.from(resolvedIds)
|
|
300
|
-
.map((id) => {
|
|
301
|
-
const integration = integrationMap.get(id)
|
|
302
|
-
const isDep =
|
|
303
|
-
!selectedIntegrationIds.includes(id) && resolvedIds.has(id)
|
|
304
|
-
return ` ${isDep ? chalk.dim('(auto)') : chalk.green('+')} ${integration?.name ?? id}`
|
|
305
|
-
})
|
|
306
|
-
.join('\n')
|
|
307
|
-
|
|
308
|
-
note(integrationList, 'Selected Integrations')
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
// Confirm
|
|
312
|
-
if (!options.yes) {
|
|
313
|
-
const proceed = await confirm({
|
|
314
|
-
message: 'Create project?',
|
|
315
|
-
initialValue: true,
|
|
316
|
-
})
|
|
317
|
-
|
|
318
|
-
if (isCancel(proceed) || !proceed) {
|
|
319
|
-
cancel('Operation cancelled.')
|
|
320
|
-
process.exit(0)
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
// Fetch full addon definitions
|
|
325
|
-
s.start('Preparing project...')
|
|
326
|
-
|
|
327
|
-
// Load custom template if provided
|
|
328
|
-
let customTemplate: CustomTemplateCompiled | undefined
|
|
329
|
-
if (options.template) {
|
|
330
|
-
try {
|
|
331
|
-
customTemplate = await loadTemplate(options.template)
|
|
332
|
-
// Add template's integrations to resolved integrations
|
|
333
|
-
for (const integrationId of customTemplate.integrations) {
|
|
334
|
-
resolvedIds.add(integrationId)
|
|
335
|
-
}
|
|
336
|
-
// Use template's settings as defaults
|
|
337
|
-
tailwind = customTemplate.tailwind
|
|
338
|
-
} catch (error) {
|
|
339
|
-
s.stop('Failed to load template')
|
|
340
|
-
log.error(
|
|
341
|
-
error instanceof Error
|
|
342
|
-
? error.message
|
|
343
|
-
: 'Could not load template from URL.',
|
|
344
|
-
)
|
|
345
|
-
process.exit(1)
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
let chosenIntegrations: Array<IntegrationCompiled> = []
|
|
350
|
-
if (resolvedIds.size > 0) {
|
|
351
|
-
try {
|
|
352
|
-
chosenIntegrations = await fetchIntegrations(Array.from(resolvedIds), integrationsPath)
|
|
353
|
-
} catch (error) {
|
|
354
|
-
s.stop('Failed to fetch integration details')
|
|
355
|
-
log.error('Could not fetch integration definitions.')
|
|
356
|
-
process.exit(1)
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
// Compile project - use custom template settings if provided
|
|
361
|
-
const compileOptions = {
|
|
362
|
-
projectName: name,
|
|
363
|
-
framework: customTemplate?.framework ?? 'react',
|
|
364
|
-
mode: customTemplate?.mode ?? ('file-router' as RouterMode),
|
|
365
|
-
typescript: customTemplate?.typescript ?? true,
|
|
366
|
-
tailwind,
|
|
367
|
-
packageManager,
|
|
368
|
-
chosenIntegrations,
|
|
369
|
-
integrationOptions: customTemplate?.integrationOptions ?? {},
|
|
370
|
-
customTemplate,
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
const output = compile(compileOptions)
|
|
374
|
-
|
|
375
|
-
s.stop('Project prepared')
|
|
376
|
-
|
|
377
|
-
// Write files
|
|
378
|
-
s.start('Writing files...')
|
|
379
|
-
|
|
380
|
-
mkdirSync(targetDir, { recursive: true })
|
|
381
|
-
|
|
382
|
-
for (const [filePath, content] of Object.entries(output.files)) {
|
|
383
|
-
const fullPath = resolve(targetDir, filePath)
|
|
384
|
-
const dir = resolve(fullPath, '..')
|
|
385
|
-
mkdirSync(dir, { recursive: true })
|
|
386
|
-
|
|
387
|
-
// Handle binary files (base64 encoded with prefix)
|
|
388
|
-
if (content.startsWith(BINARY_PREFIX)) {
|
|
389
|
-
const base64Data = content.slice(BINARY_PREFIX.length)
|
|
390
|
-
writeFileSync(fullPath, Buffer.from(base64Data, 'base64'))
|
|
391
|
-
} else {
|
|
392
|
-
writeFileSync(fullPath, content, 'utf-8')
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
// Write config file for integration/template creation
|
|
397
|
-
await writeConfigFile(targetDir, compileOptions)
|
|
398
|
-
|
|
399
|
-
s.stop('Files written')
|
|
400
|
-
|
|
401
|
-
// Initialize git
|
|
402
|
-
if (options.git !== false) {
|
|
403
|
-
s.start('Initializing git repository...')
|
|
404
|
-
try {
|
|
405
|
-
const { execSync } = await import('node:child_process')
|
|
406
|
-
execSync('git init', { cwd: targetDir, stdio: 'ignore' })
|
|
407
|
-
s.stop('Git initialized')
|
|
408
|
-
} catch {
|
|
409
|
-
s.stop('Git initialization skipped')
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
// Install dependencies
|
|
414
|
-
if (options.install !== false) {
|
|
415
|
-
s.start(`Installing dependencies with ${packageManager}...`)
|
|
416
|
-
const startTime = Date.now()
|
|
417
|
-
const interval = setInterval(() => {
|
|
418
|
-
const elapsed = Math.floor((Date.now() - startTime) / 1000)
|
|
419
|
-
s.message(`Installing dependencies with ${packageManager}... (${elapsed}s)`)
|
|
420
|
-
}, 1000)
|
|
421
|
-
|
|
422
|
-
try {
|
|
423
|
-
const { execSync } = await import('node:child_process')
|
|
424
|
-
const installCmd =
|
|
425
|
-
packageManager === 'yarn' ? 'yarn' : `${packageManager} install`
|
|
426
|
-
execSync(installCmd, { cwd: targetDir, stdio: 'ignore' })
|
|
427
|
-
clearInterval(interval)
|
|
428
|
-
const total = Math.floor((Date.now() - startTime) / 1000)
|
|
429
|
-
s.stop(`Dependencies installed (${total}s)`)
|
|
430
|
-
} catch {
|
|
431
|
-
clearInterval(interval)
|
|
432
|
-
s.stop('Dependency installation failed')
|
|
433
|
-
log.warning(`Run "${packageManager} install" manually to install dependencies.`)
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
// Show next steps
|
|
438
|
-
const relativePath = resolve(process.cwd()) === targetDir ? '.' : name
|
|
439
|
-
|
|
440
|
-
outro(chalk.green('Project created successfully!'))
|
|
441
|
-
|
|
442
|
-
console.log()
|
|
443
|
-
console.log('Next steps:')
|
|
444
|
-
console.log()
|
|
445
|
-
if (relativePath !== '.') {
|
|
446
|
-
console.log(` ${chalk.cyan('cd')} ${relativePath}`)
|
|
447
|
-
}
|
|
448
|
-
if (options.install === false) {
|
|
449
|
-
console.log(` ${chalk.cyan(packageManager)} install`)
|
|
450
|
-
}
|
|
451
|
-
console.log(` ${chalk.cyan(packageManager)} ${packageManager === 'npm' ? 'run ' : ''}dev`)
|
|
452
|
-
console.log()
|
|
453
|
-
|
|
454
|
-
// Show env vars if any
|
|
455
|
-
if (output.envVars.length > 0) {
|
|
456
|
-
console.log(chalk.yellow('Environment variables needed:'))
|
|
457
|
-
console.log()
|
|
458
|
-
for (const envVar of output.envVars) {
|
|
459
|
-
console.log(` ${chalk.cyan(envVar.name)} - ${envVar.description}`)
|
|
460
|
-
}
|
|
461
|
-
console.log()
|
|
462
|
-
console.log(` Add these to your ${chalk.cyan('.env.local')} file.`)
|
|
463
|
-
console.log()
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
// Show warnings
|
|
467
|
-
for (const warning of output.warnings) {
|
|
468
|
-
log.warning(warning)
|
|
469
|
-
}
|
|
470
|
-
}
|
package/src/commands/mcp.test.ts
DELETED
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
import { mkdirSync, readFileSync, rmSync } from 'node:fs'
|
|
2
|
-
import { resolve } from 'node:path'
|
|
3
|
-
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
|
4
|
-
import { compile } from '../engine/compile.js'
|
|
5
|
-
import { fetchIntegrations, fetchManifest } from '../api/fetch.js'
|
|
6
|
-
import type { RouterMode } from '../engine/types.js'
|
|
7
|
-
|
|
8
|
-
const INTEGRATIONS_PATH = resolve(__dirname, '../../../../integrations')
|
|
9
|
-
const TEST_DIR = resolve(__dirname, '__test_mcp__')
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* These tests verify the core logic used by the MCP server tools
|
|
13
|
-
* without testing the actual MCP transport layer.
|
|
14
|
-
*/
|
|
15
|
-
describe('MCP server tools logic', () => {
|
|
16
|
-
beforeEach(() => {
|
|
17
|
-
mkdirSync(TEST_DIR, { recursive: true })
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
afterEach(() => {
|
|
21
|
-
rmSync(TEST_DIR, { recursive: true, force: true })
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
describe('listTanStackIntegrations logic', () => {
|
|
25
|
-
it('should list integrations from manifest', async () => {
|
|
26
|
-
const manifest = await fetchManifest(INTEGRATIONS_PATH)
|
|
27
|
-
const integrations = manifest.integrations
|
|
28
|
-
.filter((a) => a.modes.includes('file-router'))
|
|
29
|
-
.map((integration) => ({
|
|
30
|
-
id: integration.id,
|
|
31
|
-
name: integration.name,
|
|
32
|
-
description: integration.description,
|
|
33
|
-
category: integration.category,
|
|
34
|
-
dependsOn: integration.dependsOn,
|
|
35
|
-
exclusive: integration.exclusive,
|
|
36
|
-
hasOptions: integration.hasOptions,
|
|
37
|
-
}))
|
|
38
|
-
|
|
39
|
-
expect(integrations.length).toBeGreaterThan(0)
|
|
40
|
-
|
|
41
|
-
const query = integrations.find((i) => i.id === 'tanstack-query')
|
|
42
|
-
expect(query).toBeDefined()
|
|
43
|
-
expect(query!.name).toBe('TanStack Query')
|
|
44
|
-
expect(query!.category).toBe('tanstack')
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
it('should include dependency and exclusive type info', async () => {
|
|
48
|
-
const manifest = await fetchManifest(INTEGRATIONS_PATH)
|
|
49
|
-
const integrations = manifest.integrations.filter((a) =>
|
|
50
|
-
a.modes.includes('file-router'),
|
|
51
|
-
)
|
|
52
|
-
|
|
53
|
-
const trpc = integrations.find((i) => i.id === 'trpc')
|
|
54
|
-
expect(trpc?.dependsOn).toContain('tanstack-query')
|
|
55
|
-
|
|
56
|
-
const clerk = integrations.find((i) => i.id === 'clerk')
|
|
57
|
-
expect(clerk?.exclusive).toContain('auth')
|
|
58
|
-
})
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
describe('createTanStackApplication logic', () => {
|
|
62
|
-
it('should compile project without integrations', () => {
|
|
63
|
-
const output = compile({
|
|
64
|
-
projectName: 'test-app',
|
|
65
|
-
framework: 'react',
|
|
66
|
-
mode: 'file-router' as RouterMode,
|
|
67
|
-
typescript: true,
|
|
68
|
-
tailwind: true,
|
|
69
|
-
packageManager: 'pnpm',
|
|
70
|
-
chosenIntegrations: [],
|
|
71
|
-
integrationOptions: {},
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
expect(output.files).toHaveProperty('package.json')
|
|
75
|
-
expect(output.files).toHaveProperty('vite.config.ts')
|
|
76
|
-
expect(output.files).toHaveProperty('src/routes/__root.tsx')
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
it('should compile project with integrations', async () => {
|
|
80
|
-
const chosenIntegrations = await fetchIntegrations(
|
|
81
|
-
['tanstack-query'],
|
|
82
|
-
INTEGRATIONS_PATH,
|
|
83
|
-
)
|
|
84
|
-
|
|
85
|
-
const output = compile({
|
|
86
|
-
projectName: 'test-app',
|
|
87
|
-
framework: 'react',
|
|
88
|
-
mode: 'file-router' as RouterMode,
|
|
89
|
-
typescript: true,
|
|
90
|
-
tailwind: true,
|
|
91
|
-
packageManager: 'pnpm',
|
|
92
|
-
chosenIntegrations,
|
|
93
|
-
integrationOptions: {},
|
|
94
|
-
})
|
|
95
|
-
|
|
96
|
-
// Should include integration files
|
|
97
|
-
expect(output.files).toHaveProperty('src/integrations/query/provider.tsx')
|
|
98
|
-
|
|
99
|
-
// Should have provider in __root.tsx
|
|
100
|
-
expect(output.files['src/routes/__root.tsx']).toContain('QueryProvider')
|
|
101
|
-
})
|
|
102
|
-
|
|
103
|
-
it('should return env vars from integrations', async () => {
|
|
104
|
-
const chosenIntegrations = await fetchIntegrations(
|
|
105
|
-
['clerk'],
|
|
106
|
-
INTEGRATIONS_PATH,
|
|
107
|
-
)
|
|
108
|
-
|
|
109
|
-
const output = compile({
|
|
110
|
-
projectName: 'test-app',
|
|
111
|
-
framework: 'react',
|
|
112
|
-
mode: 'file-router' as RouterMode,
|
|
113
|
-
typescript: true,
|
|
114
|
-
tailwind: true,
|
|
115
|
-
packageManager: 'pnpm',
|
|
116
|
-
chosenIntegrations,
|
|
117
|
-
integrationOptions: {},
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
expect(output.envVars.length).toBeGreaterThan(0)
|
|
121
|
-
expect(output.envVars.some((e) => e.name.includes('CLERK'))).toBe(true)
|
|
122
|
-
})
|
|
123
|
-
|
|
124
|
-
it('should write files to target directory', async () => {
|
|
125
|
-
const output = compile({
|
|
126
|
-
projectName: 'test-app',
|
|
127
|
-
framework: 'react',
|
|
128
|
-
mode: 'file-router' as RouterMode,
|
|
129
|
-
typescript: true,
|
|
130
|
-
tailwind: false,
|
|
131
|
-
packageManager: 'npm',
|
|
132
|
-
chosenIntegrations: [],
|
|
133
|
-
integrationOptions: {},
|
|
134
|
-
})
|
|
135
|
-
|
|
136
|
-
// Simulate writing files
|
|
137
|
-
const { writeFileSync } = await import('node:fs')
|
|
138
|
-
const { resolve: pathResolve } = await import('node:path')
|
|
139
|
-
|
|
140
|
-
for (const [filePath, content] of Object.entries(output.files)) {
|
|
141
|
-
const fullPath = pathResolve(TEST_DIR, filePath)
|
|
142
|
-
const dir = pathResolve(fullPath, '..')
|
|
143
|
-
mkdirSync(dir, { recursive: true })
|
|
144
|
-
writeFileSync(fullPath, content, 'utf-8')
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Verify files were written
|
|
148
|
-
const pkg = JSON.parse(readFileSync(resolve(TEST_DIR, 'package.json'), 'utf-8'))
|
|
149
|
-
expect(pkg.name).toBe('test-app')
|
|
150
|
-
})
|
|
151
|
-
})
|
|
152
|
-
})
|