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