@tanstack/cli 0.0.7 → 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.
Files changed (83) hide show
  1. package/dist/bin.js +7 -0
  2. package/dist/cli.js +481 -0
  3. package/dist/command-line.js +174 -0
  4. package/dist/dev-watch.js +290 -0
  5. package/dist/file-syncer.js +148 -0
  6. package/dist/index.js +1 -0
  7. package/dist/mcp/api.js +31 -0
  8. package/dist/mcp/tools.js +250 -0
  9. package/dist/mcp/types.js +37 -0
  10. package/dist/mcp.js +121 -0
  11. package/dist/options.js +162 -0
  12. package/dist/types/bin.d.ts +2 -0
  13. package/dist/types/cli.d.ts +16 -0
  14. package/dist/types/command-line.d.ts +10 -0
  15. package/dist/types/dev-watch.d.ts +27 -0
  16. package/dist/types/file-syncer.d.ts +18 -0
  17. package/dist/types/index.d.ts +1 -0
  18. package/dist/types/mcp/api.d.ts +4 -0
  19. package/dist/types/mcp/tools.d.ts +2 -0
  20. package/dist/types/mcp/types.d.ts +217 -0
  21. package/dist/types/mcp.d.ts +6 -0
  22. package/dist/types/options.d.ts +8 -0
  23. package/dist/types/types.d.ts +25 -0
  24. package/dist/types/ui-environment.d.ts +2 -0
  25. package/dist/types/ui-prompts.d.ts +12 -0
  26. package/dist/types/utils.d.ts +8 -0
  27. package/dist/types.js +1 -0
  28. package/dist/ui-environment.js +52 -0
  29. package/dist/ui-prompts.js +244 -0
  30. package/dist/utils.js +30 -0
  31. package/package.json +46 -46
  32. package/src/bin.ts +6 -93
  33. package/src/cli.ts +692 -0
  34. package/src/command-line.ts +236 -0
  35. package/src/dev-watch.ts +430 -0
  36. package/src/file-syncer.ts +205 -0
  37. package/src/index.ts +1 -85
  38. package/src/mcp.ts +190 -0
  39. package/src/options.ts +260 -0
  40. package/src/types.ts +27 -0
  41. package/src/ui-environment.ts +74 -0
  42. package/src/ui-prompts.ts +322 -0
  43. package/src/utils.ts +38 -0
  44. package/tests/command-line.test.ts +304 -0
  45. package/tests/index.test.ts +9 -0
  46. package/tests/mcp.test.ts +225 -0
  47. package/tests/options.test.ts +304 -0
  48. package/tests/setupVitest.ts +6 -0
  49. package/tests/ui-environment.test.ts +97 -0
  50. package/tests/ui-prompts.test.ts +238 -0
  51. package/tsconfig.json +17 -0
  52. package/vitest.config.js +7 -0
  53. package/dist/bin.cjs +0 -761
  54. package/dist/bin.d.cts +0 -1
  55. package/dist/bin.d.mts +0 -1
  56. package/dist/bin.mjs +0 -760
  57. package/dist/index.cjs +0 -36
  58. package/dist/index.d.cts +0 -1172
  59. package/dist/index.d.mts +0 -1172
  60. package/dist/index.mjs +0 -3
  61. package/dist/template-CkAkdP8n.mjs +0 -2545
  62. package/dist/template-Cup47s9h.cjs +0 -2783
  63. package/src/api/fetch.test.ts +0 -114
  64. package/src/api/fetch.ts +0 -249
  65. package/src/cache/index.ts +0 -89
  66. package/src/commands/create.ts +0 -463
  67. package/src/commands/mcp.test.ts +0 -152
  68. package/src/commands/mcp.ts +0 -203
  69. package/src/engine/compile-with-addons.test.ts +0 -302
  70. package/src/engine/compile.test.ts +0 -404
  71. package/src/engine/compile.ts +0 -551
  72. package/src/engine/config-file.test.ts +0 -118
  73. package/src/engine/config-file.ts +0 -61
  74. package/src/engine/custom-addons/integration.ts +0 -323
  75. package/src/engine/custom-addons/shared.test.ts +0 -98
  76. package/src/engine/custom-addons/shared.ts +0 -281
  77. package/src/engine/custom-addons/template.test.ts +0 -288
  78. package/src/engine/custom-addons/template.ts +0 -124
  79. package/src/engine/template.test.ts +0 -256
  80. package/src/engine/template.ts +0 -269
  81. package/src/engine/types.ts +0 -336
  82. package/src/parse-gitignore.d.ts +0 -5
  83. package/src/templates/base.ts +0 -891
@@ -1,463 +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 { 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
- writeFileSync(fullPath, content, 'utf-8')
387
- }
388
-
389
- // Write config file for integration/template creation
390
- await writeConfigFile(targetDir, compileOptions)
391
-
392
- s.stop('Files written')
393
-
394
- // Initialize git
395
- if (options.git !== false) {
396
- s.start('Initializing git repository...')
397
- try {
398
- const { execSync } = await import('node:child_process')
399
- execSync('git init', { cwd: targetDir, stdio: 'ignore' })
400
- s.stop('Git initialized')
401
- } catch {
402
- s.stop('Git initialization skipped')
403
- }
404
- }
405
-
406
- // Install dependencies
407
- if (options.install !== false) {
408
- s.start(`Installing dependencies with ${packageManager}...`)
409
- const startTime = Date.now()
410
- const interval = setInterval(() => {
411
- const elapsed = Math.floor((Date.now() - startTime) / 1000)
412
- s.message(`Installing dependencies with ${packageManager}... (${elapsed}s)`)
413
- }, 1000)
414
-
415
- try {
416
- const { execSync } = await import('node:child_process')
417
- const installCmd =
418
- packageManager === 'yarn' ? 'yarn' : `${packageManager} install`
419
- execSync(installCmd, { cwd: targetDir, stdio: 'ignore' })
420
- clearInterval(interval)
421
- const total = Math.floor((Date.now() - startTime) / 1000)
422
- s.stop(`Dependencies installed (${total}s)`)
423
- } catch {
424
- clearInterval(interval)
425
- s.stop('Dependency installation failed')
426
- log.warning(`Run "${packageManager} install" manually to install dependencies.`)
427
- }
428
- }
429
-
430
- // Show next steps
431
- const relativePath = resolve(process.cwd()) === targetDir ? '.' : name
432
-
433
- outro(chalk.green('Project created successfully!'))
434
-
435
- console.log()
436
- console.log('Next steps:')
437
- console.log()
438
- if (relativePath !== '.') {
439
- console.log(` ${chalk.cyan('cd')} ${relativePath}`)
440
- }
441
- if (options.install === false) {
442
- console.log(` ${chalk.cyan(packageManager)} install`)
443
- }
444
- console.log(` ${chalk.cyan(packageManager)} ${packageManager === 'npm' ? 'run ' : ''}dev`)
445
- console.log()
446
-
447
- // Show env vars if any
448
- if (output.envVars.length > 0) {
449
- console.log(chalk.yellow('Environment variables needed:'))
450
- console.log()
451
- for (const envVar of output.envVars) {
452
- console.log(` ${chalk.cyan(envVar.name)} - ${envVar.description}`)
453
- }
454
- console.log()
455
- console.log(` Add these to your ${chalk.cyan('.env.local')} file.`)
456
- console.log()
457
- }
458
-
459
- // Show warnings
460
- for (const warning of output.warnings) {
461
- log.warning(warning)
462
- }
463
- }
@@ -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
- })