@tanstack/cli 0.0.8 → 0.48.3

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 (88) hide show
  1. package/LICENSE +21 -0
  2. package/dist/bin.js +7 -0
  3. package/dist/cli.js +481 -0
  4. package/dist/command-line.js +174 -0
  5. package/dist/dev-watch.js +290 -0
  6. package/dist/file-syncer.js +148 -0
  7. package/dist/index.js +1 -0
  8. package/dist/mcp/api.js +31 -0
  9. package/dist/mcp/tools.js +250 -0
  10. package/dist/mcp/types.js +37 -0
  11. package/dist/mcp.js +121 -0
  12. package/dist/options.js +162 -0
  13. package/dist/types/bin.d.ts +2 -0
  14. package/dist/types/cli.d.ts +16 -0
  15. package/dist/types/command-line.d.ts +10 -0
  16. package/dist/types/dev-watch.d.ts +27 -0
  17. package/dist/types/file-syncer.d.ts +18 -0
  18. package/dist/types/index.d.ts +1 -0
  19. package/dist/types/mcp/api.d.ts +4 -0
  20. package/dist/types/mcp/tools.d.ts +2 -0
  21. package/dist/types/mcp/types.d.ts +217 -0
  22. package/dist/types/mcp.d.ts +6 -0
  23. package/dist/types/options.d.ts +8 -0
  24. package/dist/types/types.d.ts +25 -0
  25. package/dist/types/ui-environment.d.ts +2 -0
  26. package/dist/types/ui-prompts.d.ts +12 -0
  27. package/dist/types/utils.d.ts +8 -0
  28. package/dist/types.js +1 -0
  29. package/dist/ui-environment.js +52 -0
  30. package/dist/ui-prompts.js +244 -0
  31. package/dist/utils.js +30 -0
  32. package/package.json +46 -47
  33. package/src/bin.ts +6 -93
  34. package/src/cli.ts +692 -0
  35. package/src/command-line.ts +236 -0
  36. package/src/dev-watch.ts +430 -0
  37. package/src/file-syncer.ts +205 -0
  38. package/src/index.ts +1 -85
  39. package/src/mcp.ts +190 -0
  40. package/src/options.ts +260 -0
  41. package/src/types.ts +27 -0
  42. package/src/ui-environment.ts +74 -0
  43. package/src/ui-prompts.ts +322 -0
  44. package/src/utils.ts +38 -0
  45. package/tests/command-line.test.ts +304 -0
  46. package/tests/index.test.ts +9 -0
  47. package/tests/mcp.test.ts +225 -0
  48. package/tests/options.test.ts +304 -0
  49. package/tests/setupVitest.ts +6 -0
  50. package/tests/ui-environment.test.ts +97 -0
  51. package/tests/ui-prompts.test.ts +238 -0
  52. package/tsconfig.json +17 -0
  53. package/vitest.config.js +7 -0
  54. package/dist/bin.cjs +0 -769
  55. package/dist/bin.d.cts +0 -1
  56. package/dist/bin.d.mts +0 -1
  57. package/dist/bin.mjs +0 -768
  58. package/dist/fetch-CbFFGJEw.cjs +0 -3
  59. package/dist/fetch-DG5dLrsb.cjs +0 -522
  60. package/dist/fetch-DhlVXS6S.mjs +0 -390
  61. package/dist/fetch-I_OVg8JX.mjs +0 -3
  62. package/dist/index.cjs +0 -37
  63. package/dist/index.d.cts +0 -1172
  64. package/dist/index.d.mts +0 -1172
  65. package/dist/index.mjs +0 -4
  66. package/dist/template-Szi7-AZJ.mjs +0 -2202
  67. package/dist/template-lWrIZhCQ.cjs +0 -2314
  68. package/src/api/fetch.test.ts +0 -114
  69. package/src/api/fetch.ts +0 -278
  70. package/src/cache/index.ts +0 -89
  71. package/src/commands/create.ts +0 -470
  72. package/src/commands/mcp.test.ts +0 -152
  73. package/src/commands/mcp.ts +0 -211
  74. package/src/engine/compile-with-addons.test.ts +0 -302
  75. package/src/engine/compile.test.ts +0 -404
  76. package/src/engine/compile.ts +0 -569
  77. package/src/engine/config-file.test.ts +0 -118
  78. package/src/engine/config-file.ts +0 -61
  79. package/src/engine/custom-addons/integration.ts +0 -323
  80. package/src/engine/custom-addons/shared.test.ts +0 -98
  81. package/src/engine/custom-addons/shared.ts +0 -281
  82. package/src/engine/custom-addons/template.test.ts +0 -288
  83. package/src/engine/custom-addons/template.ts +0 -124
  84. package/src/engine/template.test.ts +0 -256
  85. package/src/engine/template.ts +0 -269
  86. package/src/engine/types.ts +0 -336
  87. package/src/parse-gitignore.d.ts +0 -5
  88. package/src/templates/base.ts +0 -883
package/src/cli.ts ADDED
@@ -0,0 +1,692 @@
1
+ import fs from 'node:fs'
2
+ import { resolve } from 'node:path'
3
+ import { Command, InvalidArgumentError } from 'commander'
4
+ import { intro, log } from '@clack/prompts'
5
+ import chalk from 'chalk'
6
+ import semver from 'semver'
7
+
8
+ import {
9
+ SUPPORTED_PACKAGE_MANAGERS,
10
+ addToApp,
11
+ compileAddOn,
12
+ compileStarter,
13
+ createApp,
14
+ createSerializedOptions,
15
+ getAllAddOns,
16
+ getFrameworkByName,
17
+ getFrameworks,
18
+ initAddOn,
19
+ initStarter,
20
+ } from '@tanstack/create'
21
+
22
+ import { launchUI } from '@tanstack/create-ui'
23
+
24
+ import { runMCPServer } from './mcp.js'
25
+
26
+ import { promptForAddOns, promptForCreateOptions } from './options.js'
27
+ import { normalizeOptions, validateDevWatchOptions } from './command-line.js'
28
+
29
+ import { createUIEnvironment } from './ui-environment.js'
30
+ import { convertTemplateToMode } from './utils.js'
31
+ import { DevWatchManager } from './dev-watch.js'
32
+
33
+ import type { CliOptions, TemplateOptions } from './types.js'
34
+ import type {
35
+ FrameworkDefinition,
36
+ Options,
37
+ PackageManager,
38
+ } from '@tanstack/create'
39
+
40
+ // Read version from package.json
41
+ const packageJsonPath = new URL('../package.json', import.meta.url)
42
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
43
+ const VERSION = packageJson.version
44
+
45
+ export function cli({
46
+ name,
47
+ appName,
48
+ forcedMode,
49
+ forcedAddOns = [],
50
+ defaultTemplate = 'javascript',
51
+ forcedDeployment,
52
+ defaultFramework,
53
+ craCompatible = false,
54
+ webBase,
55
+ frameworkDefinitionInitializers,
56
+ showDeploymentOptions = false,
57
+ legacyAutoCreate = false,
58
+ }: {
59
+ name: string
60
+ appName: string
61
+ forcedMode?: string
62
+ forcedAddOns?: Array<string>
63
+ forcedDeployment?: string
64
+ defaultTemplate?: TemplateOptions
65
+ defaultFramework?: string
66
+ craCompatible?: boolean
67
+ webBase?: string
68
+ frameworkDefinitionInitializers?: Array<() => FrameworkDefinition>
69
+ showDeploymentOptions?: boolean
70
+ legacyAutoCreate?: boolean
71
+ }) {
72
+ const environment = createUIEnvironment(appName, false)
73
+
74
+ const program = new Command()
75
+
76
+ const availableFrameworks = getFrameworks().map((f) => f.name)
77
+
78
+ const toolchains = new Set<string>()
79
+ for (const framework of getFrameworks()) {
80
+ for (const addOn of framework.getAddOns()) {
81
+ if (addOn.type === 'toolchain') {
82
+ toolchains.add(addOn.id)
83
+ }
84
+ }
85
+ }
86
+
87
+ const deployments = new Set<string>()
88
+ for (const framework of getFrameworks()) {
89
+ for (const addOn of framework.getAddOns()) {
90
+ if (addOn.type === 'deployment') {
91
+ deployments.add(addOn.id)
92
+ }
93
+ }
94
+ }
95
+
96
+ let defaultMode: string | undefined = forcedMode
97
+ const supportedModes = new Set<string>()
98
+ for (const framework of getFrameworks()) {
99
+ for (const mode of Object.keys(framework.supportedModes)) {
100
+ supportedModes.add(mode)
101
+ }
102
+ }
103
+ if (defaultMode && !supportedModes.has(defaultMode)) {
104
+ throw new InvalidArgumentError(
105
+ `Invalid mode: ${defaultMode}. The following are allowed: ${Array.from(
106
+ supportedModes,
107
+ ).join(', ')}`,
108
+ )
109
+ }
110
+ if (supportedModes.size < 2) {
111
+ defaultMode = Array.from(supportedModes)[0]
112
+ }
113
+
114
+ program
115
+ .name(name)
116
+ .description(`${appName} CLI`)
117
+ .version(VERSION, '-v, --version', 'output the current version')
118
+
119
+ // Helper to create the create command action handler
120
+ async function handleCreate(projectName: string, options: CliOptions) {
121
+ if (options.listAddOns) {
122
+ const addOns = await getAllAddOns(
123
+ getFrameworkByName(options.framework || defaultFramework || 'React')!,
124
+ defaultMode ||
125
+ convertTemplateToMode(options.template || defaultTemplate),
126
+ )
127
+ let hasConfigurableAddOns = false
128
+ for (const addOn of addOns.filter((a) => !forcedAddOns.includes(a.id))) {
129
+ const hasOptions =
130
+ addOn.options && Object.keys(addOn.options).length > 0
131
+ const optionMarker = hasOptions ? '*' : ' '
132
+ if (hasOptions) hasConfigurableAddOns = true
133
+ console.log(
134
+ `${optionMarker} ${chalk.bold(addOn.id)}: ${addOn.description}`,
135
+ )
136
+ }
137
+ if (hasConfigurableAddOns) {
138
+ console.log('\n* = has configuration options')
139
+ }
140
+ return
141
+ }
142
+
143
+ if (options.addonDetails) {
144
+ const addOns = await getAllAddOns(
145
+ getFrameworkByName(options.framework || defaultFramework || 'React')!,
146
+ defaultMode ||
147
+ convertTemplateToMode(options.template || defaultTemplate),
148
+ )
149
+ const addOn = addOns.find((a) => a.id === options.addonDetails)
150
+ if (!addOn) {
151
+ console.error(`Add-on '${options.addonDetails}' not found`)
152
+ process.exit(1)
153
+ }
154
+
155
+ console.log(
156
+ `${chalk.bold.cyan('Add-on Details:')} ${chalk.bold(addOn.name)}`,
157
+ )
158
+ console.log(`${chalk.bold('ID:')} ${addOn.id}`)
159
+ console.log(`${chalk.bold('Description:')} ${addOn.description}`)
160
+ console.log(`${chalk.bold('Type:')} ${addOn.type}`)
161
+ console.log(`${chalk.bold('Phase:')} ${addOn.phase}`)
162
+ console.log(`${chalk.bold('Supported Modes:')} ${addOn.modes.join(', ')}`)
163
+
164
+ if (addOn.link) {
165
+ console.log(`${chalk.bold('Link:')} ${chalk.blue(addOn.link)}`)
166
+ }
167
+
168
+ if (addOn.dependsOn && addOn.dependsOn.length > 0) {
169
+ console.log(
170
+ `${chalk.bold('Dependencies:')} ${addOn.dependsOn.join(', ')}`,
171
+ )
172
+ }
173
+
174
+ if (addOn.options && Object.keys(addOn.options).length > 0) {
175
+ console.log(`\n${chalk.bold.yellow('Configuration Options:')}`)
176
+ for (const [optionName, option] of Object.entries(addOn.options)) {
177
+ if (option && typeof option === 'object' && 'type' in option) {
178
+ const opt = option as any
179
+ console.log(` ${chalk.bold(optionName)}:`)
180
+ console.log(` Label: ${opt.label}`)
181
+ if (opt.description) {
182
+ console.log(` Description: ${opt.description}`)
183
+ }
184
+ console.log(` Type: ${opt.type}`)
185
+ console.log(` Default: ${opt.default}`)
186
+ if (opt.type === 'select' && opt.options) {
187
+ console.log(` Available values:`)
188
+ for (const choice of opt.options) {
189
+ console.log(` - ${choice.value}: ${choice.label}`)
190
+ }
191
+ }
192
+ }
193
+ }
194
+ } else {
195
+ console.log(`\n${chalk.gray('No configuration options available')}`)
196
+ }
197
+
198
+ if (addOn.routes && addOn.routes.length > 0) {
199
+ console.log(`\n${chalk.bold.green('Routes:')}`)
200
+ for (const route of addOn.routes) {
201
+ console.log(` ${chalk.bold(route.url)} (${route.name})`)
202
+ console.log(` File: ${route.path}`)
203
+ }
204
+ }
205
+ return
206
+ }
207
+
208
+ if (options.devWatch) {
209
+ // Validate dev watch options
210
+ const validation = validateDevWatchOptions({ ...options, projectName })
211
+ if (!validation.valid) {
212
+ console.error(validation.error)
213
+ process.exit(1)
214
+ }
215
+
216
+ // Enter dev watch mode
217
+ if (!projectName && !options.targetDir) {
218
+ console.error(
219
+ 'Project name/target directory is required for dev watch mode',
220
+ )
221
+ process.exit(1)
222
+ }
223
+
224
+ if (!options.framework) {
225
+ console.error('Failed to detect framework')
226
+ process.exit(1)
227
+ }
228
+
229
+ const framework = getFrameworkByName(options.framework)
230
+ if (!framework) {
231
+ console.error('Failed to detect framework')
232
+ process.exit(1)
233
+ }
234
+
235
+ // First, create the app normally using the standard flow
236
+ const normalizedOpts = await normalizeOptions(
237
+ {
238
+ ...options,
239
+ projectName,
240
+ framework: framework.id,
241
+ },
242
+ defaultMode,
243
+ forcedAddOns,
244
+ )
245
+
246
+ if (!normalizedOpts) {
247
+ throw new Error('Failed to normalize options')
248
+ }
249
+
250
+ normalizedOpts.targetDir =
251
+ options.targetDir || resolve(process.cwd(), projectName)
252
+
253
+ // Create the initial app with minimal output for dev watch mode
254
+ console.log(chalk.bold('\ndev-watch'))
255
+ console.log(chalk.gray('├─') + ' ' + `creating initial ${appName} app...`)
256
+ if (normalizedOpts.install !== false) {
257
+ console.log(chalk.gray('├─') + ' ' + chalk.yellow('⟳') + ' installing packages...')
258
+ }
259
+ const silentEnvironment = createUIEnvironment(appName, true)
260
+ await createApp(silentEnvironment, normalizedOpts)
261
+ console.log(chalk.gray('└─') + ' ' + chalk.green('✓') + ` app created`)
262
+
263
+ // Now start the dev watch mode
264
+ const manager = new DevWatchManager({
265
+ watchPath: options.devWatch,
266
+ targetDir: normalizedOpts.targetDir,
267
+ framework,
268
+ cliOptions: normalizedOpts,
269
+ packageManager: normalizedOpts.packageManager,
270
+ environment,
271
+ frameworkDefinitionInitializers,
272
+ })
273
+
274
+ await manager.start()
275
+ return
276
+ }
277
+
278
+ try {
279
+ const cliOptions = {
280
+ projectName,
281
+ ...options,
282
+ } as CliOptions
283
+
284
+ cliOptions.framework = getFrameworkByName(
285
+ options.framework || defaultFramework || 'React',
286
+ )!.id
287
+
288
+ if (defaultMode) {
289
+ cliOptions.template = defaultMode as TemplateOptions
290
+ }
291
+
292
+ let finalOptions: Options | undefined
293
+ if (cliOptions.interactive) {
294
+ cliOptions.addOns = true
295
+ } else {
296
+ finalOptions = await normalizeOptions(
297
+ cliOptions,
298
+ defaultMode,
299
+ forcedAddOns,
300
+ { forcedDeployment },
301
+ )
302
+ }
303
+
304
+ if (options.ui) {
305
+ const optionsFromCLI = await normalizeOptions(
306
+ cliOptions,
307
+ defaultMode,
308
+ forcedAddOns,
309
+ { disableNameCheck: true, forcedDeployment },
310
+ )
311
+ const uiOptions = {
312
+ ...createSerializedOptions(optionsFromCLI!),
313
+ projectName: 'my-app',
314
+ targetDir: resolve(process.cwd(), 'my-app'),
315
+ }
316
+ launchUI({
317
+ mode: 'setup',
318
+ options: uiOptions,
319
+ forcedRouterMode: defaultMode,
320
+ forcedAddOns,
321
+ environmentFactory: () => createUIEnvironment(appName, false),
322
+ webBase,
323
+ showDeploymentOptions,
324
+ })
325
+ return
326
+ }
327
+
328
+ if (finalOptions) {
329
+ intro(`Creating a new ${appName} app in ${projectName}...`)
330
+ } else {
331
+ intro(`Let's configure your ${appName} application`)
332
+ finalOptions = await promptForCreateOptions(cliOptions, {
333
+ forcedMode: defaultMode,
334
+ forcedAddOns,
335
+ showDeploymentOptions,
336
+ })
337
+ }
338
+
339
+ if (!finalOptions) {
340
+ throw new Error('No options were provided')
341
+ }
342
+
343
+ // Determine target directory:
344
+ // 1. Use --target-dir if provided
345
+ // 2. Use targetDir from normalizeOptions if set (handles "." case)
346
+ // 3. If original projectName was ".", use current directory
347
+ // 4. Otherwise, use project name as subdirectory
348
+ if (options.targetDir) {
349
+ finalOptions.targetDir = options.targetDir
350
+ } else if (finalOptions.targetDir) {
351
+ // Keep the targetDir from normalizeOptions (handles "." case)
352
+ } else if (projectName === '.') {
353
+ finalOptions.targetDir = resolve(process.cwd())
354
+ } else {
355
+ finalOptions.targetDir = resolve(process.cwd(), finalOptions.projectName)
356
+ }
357
+
358
+ await createApp(environment, finalOptions)
359
+ } catch (error) {
360
+ log.error(
361
+ error instanceof Error ? error.message : 'An unknown error occurred',
362
+ )
363
+ process.exit(1)
364
+ }
365
+ }
366
+
367
+ // Helper to configure create command options
368
+ function configureCreateCommand(cmd: Command) {
369
+ cmd.argument('[project-name]', 'name of the project')
370
+
371
+ if (!defaultMode && craCompatible) {
372
+ cmd.option<'typescript' | 'javascript' | 'file-router'>(
373
+ '--template <type>',
374
+ 'project template (typescript, javascript, file-router)',
375
+ (value) => {
376
+ if (
377
+ value !== 'typescript' &&
378
+ value !== 'javascript' &&
379
+ value !== 'file-router'
380
+ ) {
381
+ throw new InvalidArgumentError(
382
+ `Invalid template: ${value}. Only the following are allowed: typescript, javascript, file-router`,
383
+ )
384
+ }
385
+ return value
386
+ },
387
+ )
388
+ }
389
+
390
+ if (!defaultFramework) {
391
+ cmd.option<string>(
392
+ '--framework <type>',
393
+ `project framework (${availableFrameworks.join(', ')})`,
394
+ (value) => {
395
+ if (
396
+ !availableFrameworks.some(
397
+ (f) => f.toLowerCase() === value.toLowerCase(),
398
+ )
399
+ ) {
400
+ throw new InvalidArgumentError(
401
+ `Invalid framework: ${value}. Only the following are allowed: ${availableFrameworks.join(', ')}`,
402
+ )
403
+ }
404
+ return value
405
+ },
406
+ defaultFramework || 'React',
407
+ )
408
+ }
409
+
410
+ cmd
411
+ .option(
412
+ '--starter [url]',
413
+ 'initialize this project from a starter URL',
414
+ false,
415
+ )
416
+ .option('--no-install', 'skip installing dependencies')
417
+ .option<PackageManager>(
418
+ `--package-manager <${SUPPORTED_PACKAGE_MANAGERS.join('|')}>`,
419
+ `Explicitly tell the CLI to use this package manager`,
420
+ (value) => {
421
+ if (!SUPPORTED_PACKAGE_MANAGERS.includes(value as PackageManager)) {
422
+ throw new InvalidArgumentError(
423
+ `Invalid package manager: ${value}. The following are allowed: ${SUPPORTED_PACKAGE_MANAGERS.join(
424
+ ', ',
425
+ )}`,
426
+ )
427
+ }
428
+ return value as PackageManager
429
+ },
430
+ )
431
+ .option(
432
+ '--dev-watch <path>',
433
+ 'Watch a framework directory for changes and auto-rebuild',
434
+ )
435
+
436
+ if (deployments.size > 0) {
437
+ cmd.option<string>(
438
+ `--deployment <${Array.from(deployments).join('|')}>`,
439
+ `Explicitly tell the CLI to use this deployment adapter`,
440
+ (value) => {
441
+ if (!deployments.has(value)) {
442
+ throw new InvalidArgumentError(
443
+ `Invalid adapter: ${value}. The following are allowed: ${Array.from(
444
+ deployments,
445
+ ).join(', ')}`,
446
+ )
447
+ }
448
+ return value
449
+ },
450
+ )
451
+ }
452
+
453
+ if (toolchains.size > 0) {
454
+ cmd
455
+ .option<string>(
456
+ `--toolchain <${Array.from(toolchains).join('|')}>`,
457
+ `Explicitly tell the CLI to use this toolchain`,
458
+ (value) => {
459
+ if (!toolchains.has(value)) {
460
+ throw new InvalidArgumentError(
461
+ `Invalid toolchain: ${value}. The following are allowed: ${Array.from(
462
+ toolchains,
463
+ ).join(', ')}`,
464
+ )
465
+ }
466
+ return value
467
+ },
468
+ )
469
+ .option('--no-toolchain', 'skip toolchain selection')
470
+ }
471
+
472
+ cmd
473
+ .option('--interactive', 'interactive mode', false)
474
+ .option('--tailwind', 'add Tailwind CSS')
475
+ .option('--no-tailwind', 'skip Tailwind CSS')
476
+ .option<Array<string> | boolean>(
477
+ '--add-ons [...add-ons]',
478
+ 'pick from a list of available add-ons (comma separated list)',
479
+ (value: string) => {
480
+ let addOns: Array<string> | boolean = !!value
481
+ if (typeof value === 'string') {
482
+ addOns = value.split(',').map((addon) => addon.trim())
483
+ }
484
+ return addOns
485
+ },
486
+ )
487
+ .option('--list-add-ons', 'list all available add-ons', false)
488
+ .option(
489
+ '--addon-details <addon-id>',
490
+ 'show detailed information about a specific add-on',
491
+ )
492
+ .option('--no-git', 'do not create a git repository')
493
+ .option(
494
+ '--target-dir <path>',
495
+ 'the target directory for the application root',
496
+ )
497
+ .option('--ui', 'Launch the UI for project creation')
498
+ .option(
499
+ '--add-on-config <config>',
500
+ 'JSON string with add-on configuration options',
501
+ )
502
+ .option(
503
+ '-f, --force',
504
+ 'force project creation even if the target directory is not empty',
505
+ false,
506
+ )
507
+
508
+ return cmd
509
+ }
510
+
511
+ // === CREATE SUBCOMMAND ===
512
+ const createCommand = program
513
+ .command('create')
514
+ .description(`Create a new ${appName} application`)
515
+
516
+ configureCreateCommand(createCommand)
517
+ createCommand.action(handleCreate)
518
+
519
+ // === MCP SUBCOMMAND ===
520
+ program
521
+ .command('mcp')
522
+ .description('Run the MCP (Model Context Protocol) server')
523
+ .option('--sse', 'Run in SSE mode instead of stdio', false)
524
+ .action(async (options: { sse: boolean }) => {
525
+ await runMCPServer(options.sse, {
526
+ forcedMode: defaultMode,
527
+ forcedAddOns,
528
+ appName,
529
+ })
530
+ })
531
+
532
+ // === PIN-VERSIONS SUBCOMMAND ===
533
+ program
534
+ .command('pin-versions')
535
+ .description('Pin versions of the TanStack libraries')
536
+ .action(async () => {
537
+ if (!fs.existsSync('package.json')) {
538
+ console.error('package.json not found')
539
+ return
540
+ }
541
+ const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8'))
542
+
543
+ const packages: Record<string, string> = {
544
+ '@tanstack/react-router': '',
545
+ '@tanstack/router-generator': '',
546
+ '@tanstack/react-router-devtools': '',
547
+ '@tanstack/react-start': '',
548
+ '@tanstack/react-start-config': '',
549
+ '@tanstack/router-plugin': '',
550
+ '@tanstack/react-start-client': '',
551
+ '@tanstack/react-start-plugin': '1.115.0',
552
+ '@tanstack/react-start-server': '',
553
+ '@tanstack/start-server-core': '1.115.0',
554
+ }
555
+
556
+ function sortObject(obj: Record<string, string>): Record<string, string> {
557
+ return Object.keys(obj)
558
+ .sort()
559
+ .reduce<Record<string, string>>((acc, key) => {
560
+ acc[key] = obj[key]
561
+ return acc
562
+ }, {})
563
+ }
564
+
565
+ if (!packageJson.dependencies['@tanstack/react-start']) {
566
+ console.error('@tanstack/react-start not found in dependencies')
567
+ return
568
+ }
569
+ let changed = 0
570
+ const startVersion = packageJson.dependencies[
571
+ '@tanstack/react-start'
572
+ ].replace(/^\^/, '')
573
+ for (const pkg of Object.keys(packages)) {
574
+ if (!packageJson.dependencies[pkg]) {
575
+ packageJson.dependencies[pkg] = packages[pkg].length
576
+ ? semver.maxSatisfying(
577
+ [startVersion, packages[pkg]],
578
+ `^${packages[pkg]}`,
579
+ )!
580
+ : startVersion
581
+ changed++
582
+ } else {
583
+ if (packageJson.dependencies[pkg].startsWith('^')) {
584
+ packageJson.dependencies[pkg] = packageJson.dependencies[
585
+ pkg
586
+ ].replace(/^\^/, '')
587
+ changed++
588
+ }
589
+ }
590
+ }
591
+ packageJson.dependencies = sortObject(packageJson.dependencies)
592
+ if (changed > 0) {
593
+ fs.writeFileSync('package.json', JSON.stringify(packageJson, null, 2))
594
+ console.log(
595
+ `${changed} packages updated.
596
+
597
+ Remove your node_modules directory and package lock file and re-install.`,
598
+ )
599
+ } else {
600
+ console.log(
601
+ 'No changes needed. The relevant TanStack packages are already pinned.',
602
+ )
603
+ }
604
+ })
605
+
606
+ // === ADD SUBCOMMAND ===
607
+ program
608
+ .command('add')
609
+ .argument(
610
+ '[add-on...]',
611
+ 'Name of the add-ons (or add-ons separated by spaces or commas)',
612
+ )
613
+ .option('--forced', 'Force the add-on to be added', false)
614
+ .option('--ui', 'Add with the UI')
615
+ .action(async (addOns: Array<string>, options: { forced: boolean; ui: boolean }) => {
616
+ const parsedAddOns: Array<string> = []
617
+ for (const addOn of addOns) {
618
+ if (addOn.includes(',') || addOn.includes(' ')) {
619
+ parsedAddOns.push(
620
+ ...addOn.split(/[\s,]+/).map((addon) => addon.trim()),
621
+ )
622
+ } else {
623
+ parsedAddOns.push(addOn.trim())
624
+ }
625
+ }
626
+ if (options.ui) {
627
+ launchUI({
628
+ mode: 'add',
629
+ addOns: parsedAddOns,
630
+ projectPath: resolve(process.cwd()),
631
+ forcedRouterMode: defaultMode,
632
+ forcedAddOns,
633
+ environmentFactory: () => createUIEnvironment(appName, false),
634
+ webBase,
635
+ showDeploymentOptions,
636
+ })
637
+ } else if (parsedAddOns.length < 1) {
638
+ const selectedAddOns = await promptForAddOns()
639
+ if (selectedAddOns.length) {
640
+ await addToApp(environment, selectedAddOns, resolve(process.cwd()), {
641
+ forced: options.forced,
642
+ })
643
+ }
644
+ } else {
645
+ await addToApp(environment, parsedAddOns, resolve(process.cwd()), {
646
+ forced: options.forced,
647
+ })
648
+ }
649
+ })
650
+
651
+ // === ADD-ON SUBCOMMAND ===
652
+ const addOnCommand = program.command('add-on')
653
+ addOnCommand
654
+ .command('init')
655
+ .description('Initialize an add-on from the current project')
656
+ .action(async () => {
657
+ await initAddOn(environment)
658
+ })
659
+ addOnCommand
660
+ .command('compile')
661
+ .description('Update add-on from the current project')
662
+ .action(async () => {
663
+ await compileAddOn(environment)
664
+ })
665
+
666
+ // === STARTER SUBCOMMAND ===
667
+ const starterCommand = program.command('starter')
668
+ starterCommand
669
+ .command('init')
670
+ .description('Initialize a project starter from the current project')
671
+ .action(async () => {
672
+ await initStarter(environment)
673
+ })
674
+ starterCommand
675
+ .command('compile')
676
+ .description('Compile the starter JSON file for the current project')
677
+ .action(async () => {
678
+ await compileStarter(environment)
679
+ })
680
+
681
+ // === LEGACY AUTO-CREATE MODE ===
682
+ // For backward compatibility with cli-aliases (create-tsrouter-app, etc.)
683
+ // If legacyAutoCreate is true and no subcommand is provided, treat the first
684
+ // argument as a project name and auto-invoke create behavior
685
+ if (legacyAutoCreate) {
686
+ // Configure the main program with create options for legacy mode
687
+ configureCreateCommand(program)
688
+ program.action(handleCreate)
689
+ }
690
+
691
+ program.parse()
692
+ }