@tanstack/cli 0.48.7 → 0.59.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/cli.ts CHANGED
@@ -24,13 +24,16 @@ import { launchUI } from '@tanstack/create-ui'
24
24
  import { runMCPServer } from './mcp.js'
25
25
 
26
26
  import { promptForAddOns, promptForCreateOptions } from './options.js'
27
- import { normalizeOptions, validateDevWatchOptions } from './command-line.js'
27
+ import {
28
+ normalizeOptions,
29
+ validateDevWatchOptions,
30
+ validateLegacyCreateFlags,
31
+ } from './command-line.js'
28
32
 
29
33
  import { createUIEnvironment } from './ui-environment.js'
30
- import { convertTemplateToMode } from './utils.js'
31
34
  import { DevWatchManager } from './dev-watch.js'
32
35
 
33
- import type { CliOptions, TemplateOptions } from './types.js'
36
+ import type { CliOptions } from './types.js'
34
37
  import type {
35
38
  FrameworkDefinition,
36
39
  Options,
@@ -45,12 +48,9 @@ const VERSION = packageJson.version
45
48
  export function cli({
46
49
  name,
47
50
  appName,
48
- forcedMode,
49
51
  forcedAddOns = [],
50
- defaultTemplate = 'javascript',
51
52
  forcedDeployment,
52
53
  defaultFramework,
53
- craCompatible = false,
54
54
  webBase,
55
55
  frameworkDefinitionInitializers,
56
56
  showDeploymentOptions = false,
@@ -58,12 +58,9 @@ export function cli({
58
58
  }: {
59
59
  name: string
60
60
  appName: string
61
- forcedMode?: string
62
61
  forcedAddOns?: Array<string>
63
62
  forcedDeployment?: string
64
- defaultTemplate?: TemplateOptions
65
63
  defaultFramework?: string
66
- craCompatible?: boolean
67
64
  webBase?: string
68
65
  frameworkDefinitionInitializers?: Array<() => FrameworkDefinition>
69
66
  showDeploymentOptions?: boolean
@@ -93,23 +90,8 @@ export function cli({
93
90
  }
94
91
  }
95
92
 
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
- }
93
+ // Mode is always file-router (TanStack Start)
94
+ const defaultMode = 'file-router'
113
95
 
114
96
  program
115
97
  .name(name)
@@ -118,11 +100,20 @@ export function cli({
118
100
 
119
101
  // Helper to create the create command action handler
120
102
  async function handleCreate(projectName: string, options: CliOptions) {
103
+ const legacyCreateFlags = validateLegacyCreateFlags(options)
104
+ if (legacyCreateFlags.error) {
105
+ log.error(legacyCreateFlags.error)
106
+ process.exit(1)
107
+ }
108
+
109
+ for (const warning of legacyCreateFlags.warnings) {
110
+ log.warn(warning)
111
+ }
112
+
121
113
  if (options.listAddOns) {
122
114
  const addOns = await getAllAddOns(
123
115
  getFrameworkByName(options.framework || defaultFramework || 'React')!,
124
- defaultMode ||
125
- convertTemplateToMode(options.template || defaultTemplate),
116
+ defaultMode,
126
117
  )
127
118
  let hasConfigurableAddOns = false
128
119
  for (const addOn of addOns.filter((a) => !forcedAddOns.includes(a.id))) {
@@ -143,10 +134,14 @@ export function cli({
143
134
  if (options.addonDetails) {
144
135
  const addOns = await getAllAddOns(
145
136
  getFrameworkByName(options.framework || defaultFramework || 'React')!,
146
- defaultMode ||
147
- convertTemplateToMode(options.template || defaultTemplate),
137
+ defaultMode,
148
138
  )
149
- const addOn = addOns.find((a) => a.id === options.addonDetails)
139
+ const addOn =
140
+ addOns.find((a) => a.id === options.addonDetails) ??
141
+ addOns.find(
142
+ (a) =>
143
+ a.id.toLowerCase() === options.addonDetails!.toLowerCase(),
144
+ )
150
145
  if (!addOn) {
151
146
  console.error(`Add-on '${options.addonDetails}' not found`)
152
147
  process.exit(1)
@@ -239,7 +234,6 @@ export function cli({
239
234
  projectName,
240
235
  framework: framework.id,
241
236
  },
242
- defaultMode,
243
237
  forcedAddOns,
244
238
  )
245
239
 
@@ -285,33 +279,12 @@ export function cli({
285
279
  options.framework || defaultFramework || 'React',
286
280
  )!.id
287
281
 
288
- if (defaultMode) {
289
- cliOptions.template = defaultMode as TemplateOptions
290
- }
291
-
292
- // Default to Start unless --router-only is specified
293
- // Skip this if forcedAddOns already includes 'start' (e.g., from cli-aliases)
294
- if (!options.routerOnly && !forcedAddOns.includes('start')) {
295
- if (Array.isArray(cliOptions.addOns)) {
296
- if (!cliOptions.addOns.includes('start')) {
297
- cliOptions.addOns = [...cliOptions.addOns, 'start']
298
- }
299
- } else if (cliOptions.addOns !== true) {
300
- cliOptions.addOns = ['start']
301
- }
302
- // Also set template to file-router for Start
303
- if (!cliOptions.template) {
304
- cliOptions.template = 'file-router'
305
- }
306
- }
307
-
308
282
  let finalOptions: Options | undefined
309
283
  if (cliOptions.interactive) {
310
284
  cliOptions.addOns = true
311
285
  } else {
312
286
  finalOptions = await normalizeOptions(
313
287
  cliOptions,
314
- defaultMode,
315
288
  forcedAddOns,
316
289
  { forcedDeployment },
317
290
  )
@@ -320,7 +293,6 @@ export function cli({
320
293
  if (options.ui) {
321
294
  const optionsFromCLI = await normalizeOptions(
322
295
  cliOptions,
323
- defaultMode,
324
296
  forcedAddOns,
325
297
  { disableNameCheck: true, forcedDeployment },
326
298
  )
@@ -346,7 +318,6 @@ export function cli({
346
318
  } else {
347
319
  intro(`Let's configure your ${appName} application`)
348
320
  finalOptions = await promptForCreateOptions(cliOptions, {
349
- forcedMode: defaultMode,
350
321
  forcedAddOns,
351
322
  showDeploymentOptions,
352
323
  })
@@ -384,25 +355,6 @@ export function cli({
384
355
  function configureCreateCommand(cmd: Command) {
385
356
  cmd.argument('[project-name]', 'name of the project')
386
357
 
387
- if (!defaultMode && craCompatible) {
388
- cmd.option<'typescript' | 'javascript' | 'file-router'>(
389
- '--template <type>',
390
- 'project template (typescript, javascript, file-router)',
391
- (value) => {
392
- if (
393
- value !== 'typescript' &&
394
- value !== 'javascript' &&
395
- value !== 'file-router'
396
- ) {
397
- throw new InvalidArgumentError(
398
- `Invalid template: ${value}. Only the following are allowed: typescript, javascript, file-router`,
399
- )
400
- }
401
- return value
402
- },
403
- )
404
- }
405
-
406
358
  if (!defaultFramework) {
407
359
  cmd.option<string>(
408
360
  '--framework <type>',
@@ -448,6 +400,14 @@ export function cli({
448
400
  '--dev-watch <path>',
449
401
  'Watch a framework directory for changes and auto-rebuild',
450
402
  )
403
+ .option(
404
+ '--router-only',
405
+ 'Deprecated: compatibility flag from create-tsrouter-app',
406
+ )
407
+ .option(
408
+ '--template <type>',
409
+ 'Deprecated: compatibility flag from create-tsrouter-app',
410
+ )
451
411
 
452
412
  if (deployments.size > 0) {
453
413
  cmd.option<string>(
@@ -486,10 +446,7 @@ export function cli({
486
446
  }
487
447
 
488
448
  cmd
489
- .option('--router-only', 'create a Router-only SPA without TanStack Start (SSR)', false)
490
449
  .option('--interactive', 'interactive mode', false)
491
- .option('--tailwind', 'add Tailwind CSS')
492
- .option('--no-tailwind', 'skip Tailwind CSS')
493
450
  .option<Array<string> | boolean>(
494
451
  '--add-ons [...add-ons]',
495
452
  'pick from a list of available add-ons (comma separated list)',
@@ -526,7 +483,7 @@ export function cli({
526
483
  }
527
484
 
528
485
  // === CREATE SUBCOMMAND ===
529
- // By default creates a TanStack Start app. Use --router-only for SPA without Start.
486
+ // Creates a TanStack Start app (file-router mode).
530
487
  const createCommand = program
531
488
  .command('create')
532
489
  .description(`Create a new TanStack Start application`)
@@ -541,7 +498,6 @@ export function cli({
541
498
  .option('--sse', 'Run in SSE mode instead of stdio', false)
542
499
  .action(async (options: { sse: boolean }) => {
543
500
  await runMCPServer(options.sse, {
544
- forcedMode: defaultMode,
545
501
  forcedAddOns,
546
502
  appName,
547
503
  })
@@ -19,9 +19,54 @@ import type { Options } from '@tanstack/create'
19
19
 
20
20
  import type { CliOptions } from './types.js'
21
21
 
22
+ const SUPPORTED_LEGACY_TEMPLATES = new Set([
23
+ 'file-router',
24
+ 'typescript',
25
+ 'tsx',
26
+ ])
27
+
28
+ export function validateLegacyCreateFlags(cliOptions: CliOptions): {
29
+ warnings: Array<string>
30
+ error?: string
31
+ } {
32
+ const warnings: Array<string> = []
33
+
34
+ if (cliOptions.routerOnly) {
35
+ warnings.push(
36
+ 'The --router-only flag is deprecated and ignored. `tanstack create` already creates router-based apps.',
37
+ )
38
+ }
39
+
40
+ if (!cliOptions.template) {
41
+ return { warnings }
42
+ }
43
+
44
+ const template = cliOptions.template.toLowerCase().trim()
45
+
46
+ if (template === 'javascript' || template === 'js' || template === 'jsx') {
47
+ return {
48
+ warnings,
49
+ error:
50
+ 'JavaScript/JSX templates are not supported. TanStack Start file-router templates are TypeScript-only.',
51
+ }
52
+ }
53
+
54
+ if (!SUPPORTED_LEGACY_TEMPLATES.has(template)) {
55
+ return {
56
+ warnings,
57
+ error: `Invalid --template value: ${cliOptions.template}. Supported values are: file-router, typescript, tsx.`,
58
+ }
59
+ }
60
+
61
+ warnings.push(
62
+ 'The --template flag is deprecated. TypeScript/TSX is the default and only supported template.',
63
+ )
64
+
65
+ return { warnings }
66
+ }
67
+
22
68
  export async function normalizeOptions(
23
69
  cliOptions: CliOptions,
24
- forcedMode?: string,
25
70
  forcedAddOns?: Array<string>,
26
71
  opts?: {
27
72
  disableNameCheck?: boolean
@@ -51,42 +96,24 @@ export async function normalizeOptions(
51
96
  }
52
97
  }
53
98
 
54
- let tailwind = !!cliOptions.tailwind
55
-
56
- let mode: string =
57
- forcedMode ||
58
- (cliOptions.template === 'file-router' ? 'file-router' : 'code-router')
99
+ // Mode is always file-router (TanStack Start)
100
+ let mode = 'file-router'
59
101
 
60
102
  const starter = cliOptions.starter
61
103
  ? await loadStarter(cliOptions.starter)
62
104
  : undefined
63
105
 
64
- // TODO: Make this declarative
65
- let typescript =
66
- cliOptions.template === 'typescript' ||
67
- cliOptions.template === 'file-router' ||
68
- cliOptions.framework === 'solid'
106
+ // TypeScript and Tailwind are always enabled with TanStack Start
107
+ const typescript = true
108
+ const tailwind = true
69
109
 
70
110
  if (starter) {
71
- tailwind = starter.tailwind
72
- typescript = starter.typescript
73
111
  cliOptions.framework = starter.framework
74
112
  mode = starter.mode
75
113
  }
76
114
 
77
115
  const framework = getFrameworkById(cliOptions.framework || 'react-cra')!
78
116
 
79
- if (
80
- forcedMode &&
81
- framework.supportedModes?.[forcedMode]?.forceTypescript !== undefined
82
- ) {
83
- typescript = true
84
- }
85
-
86
- if (cliOptions.framework === 'solid') {
87
- tailwind = true
88
- }
89
-
90
117
  async function selectAddOns() {
91
118
  // Edge case for Windows Powershell
92
119
  if (Array.isArray(cliOptions.addOns) && cliOptions.addOns.length === 1) {
@@ -131,28 +158,6 @@ export async function normalizeOptions(
131
158
 
132
159
  const chosenAddOns = await selectAddOns()
133
160
 
134
- if (chosenAddOns.length) {
135
- typescript = true
136
-
137
- // Check if any add-on explicitly requires tailwind
138
- const addOnsRequireTailwind = chosenAddOns.some(
139
- (addOn) => addOn.tailwind === true,
140
- )
141
-
142
- // Only set tailwind to true if:
143
- // 1. An add-on explicitly requires it, OR
144
- // 2. User explicitly set it via CLI
145
- if (addOnsRequireTailwind) {
146
- tailwind = true
147
- } else if (cliOptions.tailwind === true) {
148
- tailwind = true
149
- } else if (cliOptions.tailwind === false) {
150
- tailwind = false
151
- }
152
- // If cliOptions.tailwind is undefined and no add-ons require it,
153
- // leave tailwind as is (will be prompted in interactive mode)
154
- }
155
-
156
161
  // Handle add-on configuration option
157
162
  let addOnOptionsFromCLI = {}
158
163
  if (cliOptions.addOnConfig) {
package/src/mcp.ts CHANGED
@@ -55,6 +55,11 @@ function createServer({
55
55
  id: addOn.id,
56
56
  name: addOn.name,
57
57
  description: addOn.description,
58
+ type: addOn.type,
59
+ category: addOn.category,
60
+ link: addOn.link,
61
+ warning: addOn.warning,
62
+ exclusive: addOn.exclusive,
58
63
  options: addOn.options,
59
64
  dependsOn: addOn.dependsOn,
60
65
  })),
@@ -65,6 +70,75 @@ function createServer({
65
70
  },
66
71
  )
67
72
 
73
+ server.tool(
74
+ 'getAddOnDetails',
75
+ 'Get detailed information about a specific add-on including implementation patterns, routes, dependencies, and documentation',
76
+ {
77
+ framework: z
78
+ .string()
79
+ .describe(
80
+ `The framework to use. Available frameworks: ${frameworkNames.join(', ')}`,
81
+ ),
82
+ addOnId: z
83
+ .string()
84
+ .describe('The ID of the add-on to get details for'),
85
+ },
86
+ async ({ framework: frameworkName, addOnId }) => {
87
+ const framework = getFrameworkByName(frameworkName)!
88
+ const allAddOns = framework.getAddOns()
89
+ const addOn =
90
+ allAddOns.find((a) => a.id === addOnId) ??
91
+ allAddOns.find(
92
+ (a) => a.id.toLowerCase() === addOnId.toLowerCase(),
93
+ )
94
+
95
+ if (!addOn) {
96
+ return {
97
+ content: [
98
+ {
99
+ type: 'text',
100
+ text: JSON.stringify({ error: `Add-on '${addOnId}' not found` }),
101
+ },
102
+ ],
103
+ }
104
+ }
105
+
106
+ // Get file list for context
107
+ const files = await addOn.getFiles()
108
+
109
+ return {
110
+ content: [
111
+ {
112
+ type: 'text',
113
+ text: JSON.stringify({
114
+ id: addOn.id,
115
+ name: addOn.name,
116
+ description: addOn.description,
117
+ type: addOn.type,
118
+ category: addOn.category,
119
+ phase: addOn.phase,
120
+ modes: addOn.modes,
121
+ link: addOn.link,
122
+ warning: addOn.warning,
123
+ exclusive: addOn.exclusive,
124
+ dependsOn: addOn.dependsOn,
125
+ options: addOn.options,
126
+ routes: addOn.routes,
127
+ packageAdditions: addOn.packageAdditions,
128
+ shadcnComponents: addOn.shadcnComponents,
129
+ integrations: addOn.integrations,
130
+ readme: addOn.readme,
131
+ files,
132
+ author: addOn.author,
133
+ version: addOn.version,
134
+ license: addOn.license,
135
+ }),
136
+ },
137
+ ],
138
+ }
139
+ },
140
+ )
141
+
68
142
  server.tool(
69
143
  'createTanStackApplication',
70
144
  'Create a new TanStack application',
@@ -156,7 +230,6 @@ export async function runMCPServer(
156
230
  appName,
157
231
  name,
158
232
  }: {
159
- forcedMode?: string
160
233
  forcedAddOns?: Array<string>
161
234
  appName?: string
162
235
  name?: string
package/src/options.ts CHANGED
@@ -16,10 +16,7 @@ import {
16
16
  selectDeployment,
17
17
  selectGit,
18
18
  selectPackageManager,
19
- selectRouterType,
20
- selectTailwind,
21
19
  selectToolchain,
22
- selectTypescript,
23
20
  } from './ui-prompts.js'
24
21
 
25
22
  import {
@@ -35,11 +32,9 @@ export async function promptForCreateOptions(
35
32
  cliOptions: CliOptions,
36
33
  {
37
34
  forcedAddOns = [],
38
- forcedMode,
39
35
  showDeploymentOptions = false,
40
36
  }: {
41
37
  forcedAddOns?: Array<string>
42
- forcedMode?: string
43
38
  showDeploymentOptions?: boolean
44
39
  },
45
40
  ): Promise<Required<Options> | undefined> {
@@ -81,29 +76,11 @@ export async function promptForCreateOptions(
81
76
  }
82
77
  }
83
78
 
84
- // Router type selection
85
- if (forcedMode) {
86
- options.mode = forcedMode
87
- } else if (cliOptions.template) {
88
- options.mode =
89
- cliOptions.template === 'file-router' ? 'file-router' : 'code-router'
90
- } else {
91
- options.mode = await selectRouterType()
92
- }
79
+ // Mode is always file-router (TanStack Start)
80
+ options.mode = 'file-router'
93
81
 
94
- // TypeScript selection (if using Code Router)
95
- // TODO: Make this declarative
96
- options.typescript =
97
- options.mode === 'file-router' || options.framework.id === 'solid'
98
- if (
99
- forcedMode &&
100
- options.framework.supportedModes[forcedMode].forceTypescript
101
- ) {
102
- options.typescript = true
103
- }
104
- if (!options.typescript && options.mode === 'code-router') {
105
- options.typescript = await selectTypescript()
106
- }
82
+ // TypeScript is always enabled with file-router
83
+ options.typescript = true
107
84
 
108
85
  // Package manager selection
109
86
  if (cliOptions.packageManager) {
@@ -170,29 +147,8 @@ export async function promptForCreateOptions(
170
147
  await finalizeAddOns(options.framework, options.mode, Array.from(addOns)),
171
148
  )
172
149
 
173
- if (options.chosenAddOns.length) {
174
- options.typescript = true
175
- }
176
-
177
- // Tailwind selection
178
- // Only treat add-ons as requiring tailwind if they explicitly have "tailwind": true
179
- const addOnsRequireTailwind = options.chosenAddOns.some(
180
- (addOn) => addOn.tailwind === true,
181
- )
182
-
183
- if (addOnsRequireTailwind) {
184
- // If any add-on explicitly requires tailwind, enable it automatically
185
- options.tailwind = true
186
- } else if (cliOptions.tailwind !== undefined) {
187
- // User explicitly provided a CLI flag, respect it
188
- options.tailwind = !!cliOptions.tailwind
189
- } else if (options.framework.id === 'react-cra') {
190
- // Only show prompt for react-cra when no CLI flag and no add-ons require it
191
- options.tailwind = await selectTailwind()
192
- } else {
193
- // For other frameworks (like solid), default to true
194
- options.tailwind = true
195
- }
150
+ // Tailwind is always enabled
151
+ options.tailwind = true
196
152
 
197
153
  // Prompt for add-on options in interactive mode
198
154
  if (Array.isArray(cliOptions.addOns)) {
package/src/types.ts CHANGED
@@ -1,11 +1,7 @@
1
1
  import type { PackageManager } from '@tanstack/create'
2
2
 
3
- export type TemplateOptions = 'typescript' | 'javascript' | 'file-router'
4
-
5
3
  export interface CliOptions {
6
- template?: TemplateOptions
7
4
  framework?: string
8
- tailwind?: boolean
9
5
  packageManager?: PackageManager
10
6
  toolchain?: string | false
11
7
  deployment?: string
@@ -25,4 +21,5 @@ export interface CliOptions {
25
21
  addOnConfig?: string
26
22
  force?: boolean
27
23
  routerOnly?: boolean
24
+ template?: string
28
25
  }
package/src/ui-prompts.ts CHANGED
@@ -43,56 +43,6 @@ export async function getProjectName(): Promise<string> {
43
43
  return value
44
44
  }
45
45
 
46
- export async function selectRouterType(): Promise<string> {
47
- const routerType = await select({
48
- message: 'Select the router type:',
49
- options: [
50
- {
51
- value: 'file-router',
52
- label: 'File Router - File-based routing structure',
53
- },
54
- {
55
- value: 'code-router',
56
- label: 'Code Router - Traditional code-based routing',
57
- },
58
- ],
59
- initialValue: 'file-router',
60
- })
61
-
62
- if (isCancel(routerType)) {
63
- cancel('Operation cancelled.')
64
- process.exit(0)
65
- }
66
-
67
- return routerType
68
- }
69
-
70
- export async function selectTypescript(): Promise<boolean> {
71
- const typescriptEnable = await confirm({
72
- message: 'Would you like to use TypeScript?',
73
- initialValue: true,
74
- })
75
- if (isCancel(typescriptEnable)) {
76
- cancel('Operation cancelled.')
77
- process.exit(0)
78
- }
79
- return typescriptEnable
80
- }
81
-
82
- export async function selectTailwind(): Promise<boolean> {
83
- const tailwind = await confirm({
84
- message: 'Would you like to use Tailwind CSS?',
85
- initialValue: true,
86
- })
87
-
88
- if (isCancel(tailwind)) {
89
- cancel('Operation cancelled.')
90
- process.exit(0)
91
- }
92
-
93
- return tailwind
94
- }
95
-
96
46
  export async function selectPackageManager(): Promise<PackageManager> {
97
47
  const packageManager = await select({
98
48
  message: 'Select package manager:',
package/src/utils.ts CHANGED
@@ -1,13 +1,5 @@
1
1
  import { basename } from 'node:path'
2
2
  import validatePackageName from 'validate-npm-package-name'
3
- import type { TemplateOptions } from './types.js'
4
-
5
- export function convertTemplateToMode(template: TemplateOptions): string {
6
- if (template === 'typescript' || template === 'javascript') {
7
- return 'code-router'
8
- }
9
- return 'file-router'
10
- }
11
3
 
12
4
  export function sanitizePackageName(name: string): string {
13
5
  return name