@tanstack/cta-cli 0.15.3 → 0.15.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -9,14 +9,8 @@ import { promptForAddOns, promptForCreateOptions } from './options.js';
9
9
  import { normalizeOptions } from './command-line.js';
10
10
  import { createUIEnvironment } from './ui-environment.js';
11
11
  import { convertTemplateToMode } from './utils.js';
12
- async function listAddOns(options, { forcedMode, forcedAddOns, defaultTemplate, }) {
13
- const addOns = await getAllAddOns(getFrameworkById(options.framework || 'react-cra'), forcedMode ||
14
- convertTemplateToMode(options.template || defaultTemplate || 'javascript'));
15
- for (const addOn of addOns.filter((a) => !forcedAddOns.includes(a.id))) {
16
- console.log(`${chalk.bold(addOn.id)}: ${addOn.description}`);
17
- }
18
- }
19
- export function cli({ name, appName, forcedMode, forcedAddOns = [], defaultTemplate = 'javascript', }) {
12
+ // This CLI assumes that all of the registered frameworks have the same set of toolchains, modes, etc.
13
+ export function cli({ name, appName, forcedMode, forcedAddOns = [], defaultTemplate = 'javascript', defaultFramework, craCompatible = false, webBase, }) {
20
14
  const environment = createUIEnvironment(appName, false);
21
15
  const program = new Command();
22
16
  const availableFrameworks = getFrameworks().map((f) => f.name);
@@ -28,6 +22,19 @@ export function cli({ name, appName, forcedMode, forcedAddOns = [], defaultTempl
28
22
  }
29
23
  }
30
24
  }
25
+ let defaultMode = forcedMode;
26
+ const supportedModes = new Set();
27
+ for (const framework of getFrameworks()) {
28
+ for (const mode of Object.keys(framework.supportedModes)) {
29
+ supportedModes.add(mode);
30
+ }
31
+ }
32
+ if (defaultMode && !supportedModes.has(defaultMode)) {
33
+ throw new InvalidArgumentError(`Invalid mode: ${defaultMode}. The following are allowed: ${Array.from(supportedModes).join(', ')}`);
34
+ }
35
+ if (supportedModes.size < 2) {
36
+ defaultMode = Array.from(supportedModes)[0];
37
+ }
31
38
  program.name(name).description(`CLI to create a new ${appName} application`);
32
39
  program
33
40
  .command('add')
@@ -49,9 +56,10 @@ export function cli({ name, appName, forcedMode, forcedAddOns = [], defaultTempl
49
56
  mode: 'add',
50
57
  addOns: parsedAddOns,
51
58
  projectPath: resolve(process.cwd()),
52
- forcedRouterMode: forcedMode,
59
+ forcedRouterMode: defaultMode,
53
60
  forcedAddOns,
54
61
  environmentFactory: () => createUIEnvironment(appName, false),
62
+ webBase,
55
63
  });
56
64
  }
57
65
  else if (parsedAddOns.length < 1) {
@@ -95,7 +103,7 @@ export function cli({ name, appName, forcedMode, forcedAddOns = [], defaultTempl
95
103
  await compileStarter(environment);
96
104
  });
97
105
  program.argument('[project-name]', 'name of the project');
98
- if (!forcedMode) {
106
+ if (!defaultMode && craCompatible) {
99
107
  program.option('--template <type>', 'project template (typescript, javascript, file-router)', (value) => {
100
108
  if (value !== 'typescript' &&
101
109
  value !== 'javascript' &&
@@ -105,26 +113,31 @@ export function cli({ name, appName, forcedMode, forcedAddOns = [], defaultTempl
105
113
  return value;
106
114
  });
107
115
  }
116
+ if (!defaultFramework) {
117
+ program.option('--framework <type>', `project framework (${availableFrameworks.join(', ')})`, (value) => {
118
+ if (!availableFrameworks.includes(value)) {
119
+ throw new InvalidArgumentError(`Invalid framework: ${value}. Only the following are allowed: ${availableFrameworks.join(', ')}`);
120
+ }
121
+ return value;
122
+ }, defaultFramework || 'react');
123
+ }
108
124
  program
109
- .option('--framework <type>', `project framework (${availableFrameworks.join(', ')})`, (value) => {
110
- if (!availableFrameworks.includes(value)) {
111
- throw new InvalidArgumentError(`Invalid framework: ${value}. Only the following are allowed: ${availableFrameworks.join(', ')}`);
112
- }
113
- return value;
114
- }, 'react')
115
125
  .option('--starter [url]', 'initialize this project from a starter URL', false)
116
126
  .option(`--package-manager <${SUPPORTED_PACKAGE_MANAGERS.join('|')}>`, `Explicitly tell the CLI to use this package manager`, (value) => {
117
127
  if (!SUPPORTED_PACKAGE_MANAGERS.includes(value)) {
118
128
  throw new InvalidArgumentError(`Invalid package manager: ${value}. The following are allowed: ${SUPPORTED_PACKAGE_MANAGERS.join(', ')}`);
119
129
  }
120
130
  return value;
121
- })
122
- .option(`--toolchain <${Array.from(toolchains).join('|')}>`, `Explicitly tell the CLI to use this toolchain`, (value) => {
123
- if (!toolchains.has(value)) {
124
- throw new InvalidArgumentError(`Invalid toolchain: ${value}. The following are allowed: ${Array.from(toolchains).join(', ')}`);
125
- }
126
- return value;
127
- })
131
+ });
132
+ if (toolchains.size > 0) {
133
+ program.option(`--toolchain <${Array.from(toolchains).join('|')}>`, `Explicitly tell the CLI to use this toolchain`, (value) => {
134
+ if (!toolchains.has(value)) {
135
+ throw new InvalidArgumentError(`Invalid toolchain: ${value}. The following are allowed: ${Array.from(toolchains).join(', ')}`);
136
+ }
137
+ return value;
138
+ });
139
+ }
140
+ program
128
141
  .option('--interactive', 'interactive mode', false)
129
142
  .option('--tailwind', 'add Tailwind CSS', false)
130
143
  .option('--add-ons [...add-ons]', 'pick from a list of available add-ons (comma separated list)', (value) => {
@@ -142,15 +155,15 @@ export function cli({ name, appName, forcedMode, forcedAddOns = [], defaultTempl
142
155
  .option('--ui', 'Add with the UI');
143
156
  program.action(async (projectName, options) => {
144
157
  if (options.listAddOns) {
145
- await listAddOns(options, {
146
- forcedMode,
147
- forcedAddOns,
148
- defaultTemplate,
149
- });
158
+ const addOns = await getAllAddOns(getFrameworkById(options.framework || defaultFramework || 'react-cra'), defaultMode ||
159
+ convertTemplateToMode(options.template || defaultTemplate));
160
+ for (const addOn of addOns.filter((a) => !forcedAddOns.includes(a.id))) {
161
+ console.log(`${chalk.bold(addOn.id)}: ${addOn.description}`);
162
+ }
150
163
  }
151
164
  else if (options.mcp || options.mcpSse) {
152
165
  await runMCPServer(!!options.mcpSse, {
153
- forcedMode,
166
+ forcedMode: defaultMode,
154
167
  forcedAddOns,
155
168
  appName,
156
169
  });
@@ -161,19 +174,19 @@ export function cli({ name, appName, forcedMode, forcedAddOns = [], defaultTempl
161
174
  projectName,
162
175
  ...options,
163
176
  };
164
- cliOptions.framework = getFrameworkByName(options.framework || 'react').id;
165
- if (forcedMode) {
166
- cliOptions.template = forcedMode;
177
+ cliOptions.framework = getFrameworkByName(options.framework || defaultFramework || 'react').id;
178
+ if (defaultMode) {
179
+ cliOptions.template = defaultMode;
167
180
  }
168
181
  let finalOptions;
169
182
  if (cliOptions.interactive) {
170
183
  cliOptions.addOns = true;
171
184
  }
172
185
  else {
173
- finalOptions = await normalizeOptions(cliOptions, forcedMode, forcedAddOns);
186
+ finalOptions = await normalizeOptions(cliOptions, defaultMode, forcedAddOns);
174
187
  }
175
188
  if (options.ui) {
176
- const optionsFromCLI = await normalizeOptions(cliOptions, forcedMode, forcedAddOns, { disableNameCheck: true });
189
+ const optionsFromCLI = await normalizeOptions(cliOptions, defaultMode, forcedAddOns, { disableNameCheck: true });
177
190
  launchUI({
178
191
  mode: 'setup',
179
192
  options: {
@@ -181,9 +194,10 @@ export function cli({ name, appName, forcedMode, forcedAddOns = [], defaultTempl
181
194
  projectName: 'my-app',
182
195
  targetDir: resolve(process.cwd(), 'my-app'),
183
196
  },
184
- forcedRouterMode: forcedMode,
197
+ forcedRouterMode: defaultMode,
185
198
  forcedAddOns,
186
199
  environmentFactory: () => createUIEnvironment(appName, false),
200
+ webBase,
187
201
  });
188
202
  return;
189
203
  }
@@ -193,7 +207,7 @@ export function cli({ name, appName, forcedMode, forcedAddOns = [], defaultTempl
193
207
  else {
194
208
  intro(`Let's configure your ${appName} application`);
195
209
  finalOptions = await promptForCreateOptions(cliOptions, {
196
- forcedMode,
210
+ forcedMode: defaultMode,
197
211
  forcedAddOns,
198
212
  });
199
213
  }
@@ -1,23 +1,20 @@
1
1
  import { resolve } from 'node:path';
2
- import { CODE_ROUTER, DEFAULT_PACKAGE_MANAGER, FILE_ROUTER, finalizeAddOns, getFrameworkById, getPackageManager, loadStarter, } from '@tanstack/cta-engine';
2
+ import { DEFAULT_PACKAGE_MANAGER, finalizeAddOns, getFrameworkById, getPackageManager, loadStarter, } from '@tanstack/cta-engine';
3
3
  export async function normalizeOptions(cliOptions, forcedMode, forcedAddOns, opts) {
4
4
  const projectName = (cliOptions.projectName ?? '').trim();
5
5
  if (!projectName && !opts?.disableNameCheck) {
6
6
  return undefined;
7
7
  }
8
- let typescript = cliOptions.template === 'typescript' ||
9
- cliOptions.template === 'file-router' ||
10
- cliOptions.framework === 'solid';
11
8
  let tailwind = !!cliOptions.tailwind;
12
- if (cliOptions.framework === 'solid') {
13
- tailwind = true;
14
- }
15
- let mode = forcedMode || cliOptions.template === 'file-router'
16
- ? FILE_ROUTER
17
- : CODE_ROUTER;
9
+ let mode = forcedMode ||
10
+ (cliOptions.template === 'file-router' ? 'file-router' : 'code-router');
18
11
  const starter = cliOptions.starter
19
12
  ? await loadStarter(cliOptions.starter)
20
13
  : undefined;
14
+ // TODO: Make this declarative
15
+ let typescript = cliOptions.template === 'typescript' ||
16
+ cliOptions.template === 'file-router' ||
17
+ cliOptions.framework === 'solid';
21
18
  if (starter) {
22
19
  tailwind = starter.tailwind;
23
20
  typescript = starter.typescript;
@@ -25,6 +22,13 @@ export async function normalizeOptions(cliOptions, forcedMode, forcedAddOns, opt
25
22
  mode = starter.mode;
26
23
  }
27
24
  const framework = getFrameworkById(cliOptions.framework || 'react-cra');
25
+ if (forcedMode &&
26
+ framework.supportedModes?.[forcedMode]?.forceTypescript !== undefined) {
27
+ typescript = true;
28
+ }
29
+ if (cliOptions.framework === 'solid') {
30
+ tailwind = true;
31
+ }
28
32
  async function selectAddOns() {
29
33
  // Edge case for Windows Powershell
30
34
  if (Array.isArray(cliOptions.addOns) && cliOptions.addOns.length === 1) {
package/dist/options.js CHANGED
@@ -1,6 +1,6 @@
1
- import { CODE_ROUTER, FILE_ROUTER, finalizeAddOns, getFrameworkById, getPackageManager, readConfigFile, } from '@tanstack/cta-engine';
2
- import { getProjectName, selectAddOns, selectGit, selectPackageManager, selectRouterType, selectTailwind, selectToolchain, selectTypescript, } from './ui-prompts.js';
3
1
  import { intro } from '@clack/prompts';
2
+ import { finalizeAddOns, getFrameworkById, getPackageManager, readConfigFile, } from '@tanstack/cta-engine';
3
+ import { getProjectName, selectAddOns, selectGit, selectPackageManager, selectRouterType, selectTailwind, selectToolchain, selectTypescript, } from './ui-prompts.js';
4
4
  export async function promptForCreateOptions(cliOptions, { forcedAddOns = [], forcedMode, }) {
5
5
  const options = {};
6
6
  options.framework = getFrameworkById(cliOptions.framework || 'react-cra');
@@ -11,15 +11,20 @@ export async function promptForCreateOptions(cliOptions, { forcedAddOns = [], fo
11
11
  }
12
12
  else if (cliOptions.template) {
13
13
  options.mode =
14
- cliOptions.template === 'file-router' ? FILE_ROUTER : CODE_ROUTER;
14
+ cliOptions.template === 'file-router' ? 'file-router' : 'code-router';
15
15
  }
16
16
  else {
17
17
  options.mode = await selectRouterType();
18
18
  }
19
19
  // TypeScript selection (if using Code Router)
20
+ // TODO: Make this declarative
20
21
  options.typescript =
21
- options.mode === FILE_ROUTER || options.framework.id === 'solid';
22
- if (!options.typescript && options.mode === CODE_ROUTER) {
22
+ options.mode === 'file-router' || options.framework.id === 'solid';
23
+ if (forcedMode &&
24
+ options.framework.supportedModes[forcedMode].forceTypescript) {
25
+ options.typescript = true;
26
+ }
27
+ if (!options.typescript && options.mode === 'code-router') {
23
28
  options.typescript = await selectTypescript();
24
29
  }
25
30
  // Tailwind selection
@@ -1,9 +1,11 @@
1
- import type { Mode } from '@tanstack/cta-engine';
2
1
  import type { TemplateOptions } from './types.js';
3
- export declare function cli({ name, appName, forcedMode, forcedAddOns, defaultTemplate, }: {
2
+ export declare function cli({ name, appName, forcedMode, forcedAddOns, defaultTemplate, defaultFramework, craCompatible, webBase, }: {
4
3
  name: string;
5
4
  appName: string;
6
- forcedMode?: Mode;
5
+ forcedMode?: string;
7
6
  forcedAddOns?: Array<string>;
8
7
  defaultTemplate?: TemplateOptions;
8
+ defaultFramework?: string;
9
+ craCompatible?: boolean;
10
+ webBase?: string;
9
11
  }): void;
@@ -1,5 +1,5 @@
1
- import type { Mode, Options } from '@tanstack/cta-engine';
1
+ import type { Options } from '@tanstack/cta-engine';
2
2
  import type { CliOptions } from './types.js';
3
- export declare function normalizeOptions(cliOptions: CliOptions, forcedMode?: Mode, forcedAddOns?: Array<string>, opts?: {
3
+ export declare function normalizeOptions(cliOptions: CliOptions, forcedMode?: string, forcedAddOns?: Array<string>, opts?: {
4
4
  disableNameCheck?: boolean;
5
5
  }): Promise<Options | undefined>;
@@ -1,6 +1,5 @@
1
- import type { Mode } from '@tanstack/cta-engine';
2
1
  export declare function runMCPServer(sse: boolean, { forcedAddOns, appName, name, }: {
3
- forcedMode?: Mode;
2
+ forcedMode?: string;
4
3
  forcedAddOns?: Array<string>;
5
4
  appName?: string;
6
5
  name?: string;
@@ -1,7 +1,7 @@
1
- import type { Mode, Options } from '@tanstack/cta-engine';
1
+ import type { Options } from '@tanstack/cta-engine';
2
2
  import type { CliOptions } from './types.js';
3
3
  export declare function promptForCreateOptions(cliOptions: CliOptions, { forcedAddOns, forcedMode, }: {
4
4
  forcedAddOns?: Array<string>;
5
- forcedMode?: Mode;
5
+ forcedMode?: string;
6
6
  }): Promise<Required<Options> | undefined>;
7
7
  export declare function promptForAddOns(): Promise<Array<string>>;
@@ -1,10 +1,10 @@
1
- import type { Mode, PackageManager } from '@tanstack/cta-engine';
1
+ import type { PackageManager } from '@tanstack/cta-engine';
2
2
  import type { Framework } from '@tanstack/cta-engine/dist/types/types.js';
3
3
  export declare function getProjectName(): Promise<string>;
4
- export declare function selectRouterType(): Promise<Mode>;
4
+ export declare function selectRouterType(): Promise<string>;
5
5
  export declare function selectTypescript(): Promise<boolean>;
6
6
  export declare function selectTailwind(): Promise<boolean>;
7
7
  export declare function selectPackageManager(): Promise<PackageManager>;
8
- export declare function selectAddOns(framework: Framework, mode: Mode, type: string, message: string, forcedAddOns?: Array<string>): Promise<Array<string>>;
8
+ export declare function selectAddOns(framework: Framework, mode: string, type: string, message: string, forcedAddOns?: Array<string>): Promise<Array<string>>;
9
9
  export declare function selectGit(): Promise<boolean>;
10
10
  export declare function selectToolchain(framework: Framework, toolchain?: string): Promise<string | undefined>;
@@ -1,3 +1,2 @@
1
- import type { Mode } from '@tanstack/cta-engine';
2
1
  import type { TemplateOptions } from './types.js';
3
- export declare function convertTemplateToMode(template: TemplateOptions): Mode;
2
+ export declare function convertTemplateToMode(template: TemplateOptions): string;
@@ -1,5 +1,5 @@
1
1
  import { cancel, confirm, isCancel, multiselect, select, text, } from '@clack/prompts';
2
- import { CODE_ROUTER, DEFAULT_PACKAGE_MANAGER, FILE_ROUTER, SUPPORTED_PACKAGE_MANAGERS, getAllAddOns, } from '@tanstack/cta-engine';
2
+ import { DEFAULT_PACKAGE_MANAGER, SUPPORTED_PACKAGE_MANAGERS, getAllAddOns, } from '@tanstack/cta-engine';
3
3
  export async function getProjectName() {
4
4
  const value = await text({
5
5
  message: 'What would you like to name your project?',
@@ -21,15 +21,15 @@ export async function selectRouterType() {
21
21
  message: 'Select the router type:',
22
22
  options: [
23
23
  {
24
- value: FILE_ROUTER,
24
+ value: 'file-router',
25
25
  label: 'File Router - File-based routing structure',
26
26
  },
27
27
  {
28
- value: CODE_ROUTER,
28
+ value: 'code-router',
29
29
  label: 'Code Router - Traditional code-based routing',
30
30
  },
31
31
  ],
32
- initialValue: FILE_ROUTER,
32
+ initialValue: 'file-router',
33
33
  });
34
34
  if (isCancel(routerType)) {
35
35
  cancel('Operation cancelled.');
package/dist/utils.js CHANGED
@@ -1,7 +1,6 @@
1
- import { CODE_ROUTER, FILE_ROUTER } from '@tanstack/cta-engine';
2
1
  export function convertTemplateToMode(template) {
3
2
  if (template === 'typescript' || template === 'javascript') {
4
- return CODE_ROUTER;
3
+ return 'code-router';
5
4
  }
6
- return FILE_ROUTER;
5
+ return 'file-router';
7
6
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/cta-cli",
3
- "version": "0.15.3",
3
+ "version": "0.15.5",
4
4
  "description": "Tanstack Application Builder CLI",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -29,8 +29,8 @@
29
29
  "commander": "^13.1.0",
30
30
  "express": "^4.21.2",
31
31
  "zod": "^3.24.2",
32
- "@tanstack/cta-engine": "0.15.3",
33
- "@tanstack/cta-ui": "0.15.3"
32
+ "@tanstack/cta-engine": "0.15.5",
33
+ "@tanstack/cta-ui": "0.15.5"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@tanstack/config": "^0.16.2",
package/src/cli.ts CHANGED
@@ -28,33 +28,10 @@ import { normalizeOptions } from './command-line.js'
28
28
  import { createUIEnvironment } from './ui-environment.js'
29
29
  import { convertTemplateToMode } from './utils.js'
30
30
 
31
- import type { Mode, Options, PackageManager } from '@tanstack/cta-engine'
32
-
33
31
  import type { CliOptions, TemplateOptions } from './types.js'
32
+ import type { Options, PackageManager } from '@tanstack/cta-engine'
34
33
 
35
- async function listAddOns(
36
- options: CliOptions,
37
- {
38
- forcedMode,
39
- forcedAddOns,
40
- defaultTemplate,
41
- }: {
42
- forcedMode?: Mode
43
- forcedAddOns: Array<string>
44
- defaultTemplate?: TemplateOptions
45
- },
46
- ) {
47
- const addOns = await getAllAddOns(
48
- getFrameworkById(options.framework || 'react-cra')!,
49
- forcedMode ||
50
- convertTemplateToMode(
51
- options.template || defaultTemplate || 'javascript',
52
- ),
53
- )
54
- for (const addOn of addOns.filter((a) => !forcedAddOns.includes(a.id))) {
55
- console.log(`${chalk.bold(addOn.id)}: ${addOn.description}`)
56
- }
57
- }
34
+ // This CLI assumes that all of the registered frameworks have the same set of toolchains, modes, etc.
58
35
 
59
36
  export function cli({
60
37
  name,
@@ -62,12 +39,18 @@ export function cli({
62
39
  forcedMode,
63
40
  forcedAddOns = [],
64
41
  defaultTemplate = 'javascript',
42
+ defaultFramework,
43
+ craCompatible = false,
44
+ webBase,
65
45
  }: {
66
46
  name: string
67
47
  appName: string
68
- forcedMode?: Mode
48
+ forcedMode?: string
69
49
  forcedAddOns?: Array<string>
70
50
  defaultTemplate?: TemplateOptions
51
+ defaultFramework?: string
52
+ craCompatible?: boolean
53
+ webBase?: string
71
54
  }) {
72
55
  const environment = createUIEnvironment(appName, false)
73
56
 
@@ -84,6 +67,24 @@ export function cli({
84
67
  }
85
68
  }
86
69
 
70
+ let defaultMode: string | undefined = forcedMode
71
+ const supportedModes = new Set<string>()
72
+ for (const framework of getFrameworks()) {
73
+ for (const mode of Object.keys(framework.supportedModes)) {
74
+ supportedModes.add(mode)
75
+ }
76
+ }
77
+ if (defaultMode && !supportedModes.has(defaultMode)) {
78
+ throw new InvalidArgumentError(
79
+ `Invalid mode: ${defaultMode}. The following are allowed: ${Array.from(
80
+ supportedModes,
81
+ ).join(', ')}`,
82
+ )
83
+ }
84
+ if (supportedModes.size < 2) {
85
+ defaultMode = Array.from(supportedModes)[0]
86
+ }
87
+
87
88
  program.name(name).description(`CLI to create a new ${appName} application`)
88
89
 
89
90
  program
@@ -110,9 +111,10 @@ export function cli({
110
111
  mode: 'add',
111
112
  addOns: parsedAddOns,
112
113
  projectPath: resolve(process.cwd()),
113
- forcedRouterMode: forcedMode,
114
+ forcedRouterMode: defaultMode,
114
115
  forcedAddOns,
115
116
  environmentFactory: () => createUIEnvironment(appName, false),
117
+ webBase,
116
118
  })
117
119
  } else if (parsedAddOns.length < 1) {
118
120
  const addOns = await promptForAddOns()
@@ -158,7 +160,7 @@ export function cli({
158
160
 
159
161
  program.argument('[project-name]', 'name of the project')
160
162
 
161
- if (!forcedMode) {
163
+ if (!defaultMode && craCompatible) {
162
164
  program.option<'typescript' | 'javascript' | 'file-router'>(
163
165
  '--template <type>',
164
166
  'project template (typescript, javascript, file-router)',
@@ -177,8 +179,8 @@ export function cli({
177
179
  )
178
180
  }
179
181
 
180
- program
181
- .option<string>(
182
+ if (!defaultFramework) {
183
+ program.option<string>(
182
184
  '--framework <type>',
183
185
  `project framework (${availableFrameworks.join(', ')})`,
184
186
  (value) => {
@@ -189,8 +191,11 @@ export function cli({
189
191
  }
190
192
  return value
191
193
  },
192
- 'react',
194
+ defaultFramework || 'react',
193
195
  )
196
+ }
197
+
198
+ program
194
199
  .option(
195
200
  '--starter [url]',
196
201
  'initialize this project from a starter URL',
@@ -210,7 +215,9 @@ export function cli({
210
215
  return value as PackageManager
211
216
  },
212
217
  )
213
- .option<string>(
218
+
219
+ if (toolchains.size > 0) {
220
+ program.option<string>(
214
221
  `--toolchain <${Array.from(toolchains).join('|')}>`,
215
222
  `Explicitly tell the CLI to use this toolchain`,
216
223
  (value) => {
@@ -224,6 +231,9 @@ export function cli({
224
231
  return value
225
232
  },
226
233
  )
234
+ }
235
+
236
+ program
227
237
  .option('--interactive', 'interactive mode', false)
228
238
  .option('--tailwind', 'add Tailwind CSS', false)
229
239
  .option<Array<string> | boolean>(
@@ -249,14 +259,17 @@ export function cli({
249
259
 
250
260
  program.action(async (projectName: string, options: CliOptions) => {
251
261
  if (options.listAddOns) {
252
- await listAddOns(options, {
253
- forcedMode,
254
- forcedAddOns,
255
- defaultTemplate,
256
- })
262
+ const addOns = await getAllAddOns(
263
+ getFrameworkById(options.framework || defaultFramework || 'react-cra')!,
264
+ defaultMode ||
265
+ convertTemplateToMode(options.template || defaultTemplate),
266
+ )
267
+ for (const addOn of addOns.filter((a) => !forcedAddOns.includes(a.id))) {
268
+ console.log(`${chalk.bold(addOn.id)}: ${addOn.description}`)
269
+ }
257
270
  } else if (options.mcp || options.mcpSse) {
258
271
  await runMCPServer(!!options.mcpSse, {
259
- forcedMode,
272
+ forcedMode: defaultMode,
260
273
  forcedAddOns,
261
274
  appName,
262
275
  })
@@ -268,11 +281,11 @@ export function cli({
268
281
  } as CliOptions
269
282
 
270
283
  cliOptions.framework = getFrameworkByName(
271
- options.framework || 'react',
284
+ options.framework || defaultFramework || 'react',
272
285
  )!.id
273
286
 
274
- if (forcedMode) {
275
- cliOptions.template = forcedMode as TemplateOptions
287
+ if (defaultMode) {
288
+ cliOptions.template = defaultMode as TemplateOptions
276
289
  }
277
290
 
278
291
  let finalOptions: Options | undefined
@@ -281,7 +294,7 @@ export function cli({
281
294
  } else {
282
295
  finalOptions = await normalizeOptions(
283
296
  cliOptions,
284
- forcedMode,
297
+ defaultMode,
285
298
  forcedAddOns,
286
299
  )
287
300
  }
@@ -289,7 +302,7 @@ export function cli({
289
302
  if (options.ui) {
290
303
  const optionsFromCLI = await normalizeOptions(
291
304
  cliOptions,
292
- forcedMode,
305
+ defaultMode,
293
306
  forcedAddOns,
294
307
  { disableNameCheck: true },
295
308
  )
@@ -300,9 +313,10 @@ export function cli({
300
313
  projectName: 'my-app',
301
314
  targetDir: resolve(process.cwd(), 'my-app'),
302
315
  },
303
- forcedRouterMode: forcedMode,
316
+ forcedRouterMode: defaultMode,
304
317
  forcedAddOns,
305
318
  environmentFactory: () => createUIEnvironment(appName, false),
319
+ webBase,
306
320
  })
307
321
  return
308
322
  }
@@ -312,7 +326,7 @@ export function cli({
312
326
  } else {
313
327
  intro(`Let's configure your ${appName} application`)
314
328
  finalOptions = await promptForCreateOptions(cliOptions, {
315
- forcedMode,
329
+ forcedMode: defaultMode,
316
330
  forcedAddOns,
317
331
  })
318
332
  }
@@ -1,22 +1,20 @@
1
1
  import { resolve } from 'node:path'
2
2
 
3
3
  import {
4
- CODE_ROUTER,
5
4
  DEFAULT_PACKAGE_MANAGER,
6
- FILE_ROUTER,
7
5
  finalizeAddOns,
8
6
  getFrameworkById,
9
7
  getPackageManager,
10
8
  loadStarter,
11
9
  } from '@tanstack/cta-engine'
12
10
 
13
- import type { Mode, Options } from '@tanstack/cta-engine'
11
+ import type { Options } from '@tanstack/cta-engine'
14
12
 
15
13
  import type { CliOptions } from './types.js'
16
14
 
17
15
  export async function normalizeOptions(
18
16
  cliOptions: CliOptions,
19
- forcedMode?: Mode,
17
+ forcedMode?: string,
20
18
  forcedAddOns?: Array<string>,
21
19
  opts?: {
22
20
  disableNameCheck?: boolean
@@ -27,34 +25,42 @@ export async function normalizeOptions(
27
25
  return undefined
28
26
  }
29
27
 
30
- let typescript =
31
- cliOptions.template === 'typescript' ||
32
- cliOptions.template === 'file-router' ||
33
- cliOptions.framework === 'solid'
34
-
35
28
  let tailwind = !!cliOptions.tailwind
36
- if (cliOptions.framework === 'solid') {
37
- tailwind = true
38
- }
39
29
 
40
- let mode: typeof FILE_ROUTER | typeof CODE_ROUTER =
41
- forcedMode || cliOptions.template === 'file-router'
42
- ? FILE_ROUTER
43
- : CODE_ROUTER
30
+ let mode: string =
31
+ forcedMode ||
32
+ (cliOptions.template === 'file-router' ? 'file-router' : 'code-router')
44
33
 
45
34
  const starter = cliOptions.starter
46
35
  ? await loadStarter(cliOptions.starter)
47
36
  : undefined
48
37
 
38
+ // TODO: Make this declarative
39
+ let typescript =
40
+ cliOptions.template === 'typescript' ||
41
+ cliOptions.template === 'file-router' ||
42
+ cliOptions.framework === 'solid'
43
+
49
44
  if (starter) {
50
45
  tailwind = starter.tailwind
51
46
  typescript = starter.typescript
52
47
  cliOptions.framework = starter.framework
53
- mode = starter.mode as Mode
48
+ mode = starter.mode
54
49
  }
55
50
 
56
51
  const framework = getFrameworkById(cliOptions.framework || 'react-cra')!
57
52
 
53
+ if (
54
+ forcedMode &&
55
+ framework.supportedModes?.[forcedMode]?.forceTypescript !== undefined
56
+ ) {
57
+ typescript = true
58
+ }
59
+
60
+ if (cliOptions.framework === 'solid') {
61
+ tailwind = true
62
+ }
63
+
58
64
  async function selectAddOns() {
59
65
  // Edge case for Windows Powershell
60
66
  if (Array.isArray(cliOptions.addOns) && cliOptions.addOns.length === 1) {
package/src/mcp.ts CHANGED
@@ -11,8 +11,6 @@ import {
11
11
  getFrameworkById,
12
12
  } from '@tanstack/cta-engine'
13
13
 
14
- import type { Mode } from '@tanstack/cta-engine'
15
-
16
14
  function createServer({
17
15
  appName,
18
16
  forcedAddOns = [],
@@ -213,7 +211,7 @@ export async function runMCPServer(
213
211
  appName,
214
212
  name,
215
213
  }: {
216
- forcedMode?: Mode
214
+ forcedMode?: string
217
215
  forcedAddOns?: Array<string>
218
216
  appName?: string
219
217
  name?: string
package/src/options.ts CHANGED
@@ -1,6 +1,6 @@
1
+ import { intro } from '@clack/prompts'
2
+
1
3
  import {
2
- CODE_ROUTER,
3
- FILE_ROUTER,
4
4
  finalizeAddOns,
5
5
  getFrameworkById,
6
6
  getPackageManager,
@@ -18,10 +18,9 @@ import {
18
18
  selectTypescript,
19
19
  } from './ui-prompts.js'
20
20
 
21
- import type { Mode, Options } from '@tanstack/cta-engine'
21
+ import type { Options } from '@tanstack/cta-engine'
22
22
 
23
23
  import type { CliOptions } from './types.js'
24
- import { intro } from '@clack/prompts'
25
24
 
26
25
  export async function promptForCreateOptions(
27
26
  cliOptions: CliOptions,
@@ -30,7 +29,7 @@ export async function promptForCreateOptions(
30
29
  forcedMode,
31
30
  }: {
32
31
  forcedAddOns?: Array<string>
33
- forcedMode?: Mode
32
+ forcedMode?: string
34
33
  },
35
34
  ): Promise<Required<Options> | undefined> {
36
35
  const options = {} as Required<Options>
@@ -44,15 +43,22 @@ export async function promptForCreateOptions(
44
43
  options.mode = forcedMode
45
44
  } else if (cliOptions.template) {
46
45
  options.mode =
47
- cliOptions.template === 'file-router' ? FILE_ROUTER : CODE_ROUTER
46
+ cliOptions.template === 'file-router' ? 'file-router' : 'code-router'
48
47
  } else {
49
48
  options.mode = await selectRouterType()
50
49
  }
51
50
 
52
51
  // TypeScript selection (if using Code Router)
52
+ // TODO: Make this declarative
53
53
  options.typescript =
54
- options.mode === FILE_ROUTER || options.framework.id === 'solid'
55
- if (!options.typescript && options.mode === CODE_ROUTER) {
54
+ options.mode === 'file-router' || options.framework.id === 'solid'
55
+ if (
56
+ forcedMode &&
57
+ options.framework.supportedModes[forcedMode].forceTypescript
58
+ ) {
59
+ options.typescript = true
60
+ }
61
+ if (!options.typescript && options.mode === 'code-router') {
56
62
  options.typescript = await selectTypescript()
57
63
  }
58
64
 
package/src/ui-prompts.ts CHANGED
@@ -8,14 +8,12 @@ import {
8
8
  } from '@clack/prompts'
9
9
 
10
10
  import {
11
- CODE_ROUTER,
12
11
  DEFAULT_PACKAGE_MANAGER,
13
- FILE_ROUTER,
14
12
  SUPPORTED_PACKAGE_MANAGERS,
15
13
  getAllAddOns,
16
14
  } from '@tanstack/cta-engine'
17
15
 
18
- import type { AddOn, Mode, PackageManager } from '@tanstack/cta-engine'
16
+ import type { AddOn, PackageManager } from '@tanstack/cta-engine'
19
17
 
20
18
  import type { Framework } from '@tanstack/cta-engine/dist/types/types.js'
21
19
 
@@ -38,20 +36,20 @@ export async function getProjectName(): Promise<string> {
38
36
  return value
39
37
  }
40
38
 
41
- export async function selectRouterType(): Promise<Mode> {
39
+ export async function selectRouterType(): Promise<string> {
42
40
  const routerType = await select({
43
41
  message: 'Select the router type:',
44
42
  options: [
45
43
  {
46
- value: FILE_ROUTER,
44
+ value: 'file-router',
47
45
  label: 'File Router - File-based routing structure',
48
46
  },
49
47
  {
50
- value: CODE_ROUTER,
48
+ value: 'code-router',
51
49
  label: 'Code Router - Traditional code-based routing',
52
50
  },
53
51
  ],
54
- initialValue: FILE_ROUTER,
52
+ initialValue: 'file-router',
55
53
  })
56
54
 
57
55
  if (isCancel(routerType)) {
@@ -59,7 +57,7 @@ export async function selectRouterType(): Promise<Mode> {
59
57
  process.exit(0)
60
58
  }
61
59
 
62
- return routerType as Mode
60
+ return routerType
63
61
  }
64
62
 
65
63
  export async function selectTypescript(): Promise<boolean> {
@@ -106,7 +104,7 @@ export async function selectPackageManager(): Promise<PackageManager> {
106
104
 
107
105
  export async function selectAddOns(
108
106
  framework: Framework,
109
- mode: Mode,
107
+ mode: string,
110
108
  type: string,
111
109
  message: string,
112
110
  forcedAddOns: Array<string> = [],
package/src/utils.ts CHANGED
@@ -1,11 +1,8 @@
1
- import { CODE_ROUTER, FILE_ROUTER } from '@tanstack/cta-engine'
2
- import type { Mode } from '@tanstack/cta-engine'
3
-
4
1
  import type { TemplateOptions } from './types.js'
5
2
 
6
- export function convertTemplateToMode(template: TemplateOptions): Mode {
3
+ export function convertTemplateToMode(template: TemplateOptions): string {
7
4
  if (template === 'typescript' || template === 'javascript') {
8
- return CODE_ROUTER
5
+ return 'code-router'
9
6
  }
10
- return FILE_ROUTER
7
+ return 'file-router'
11
8
  }
@@ -63,6 +63,18 @@ describe('normalizeOptions', () => {
63
63
  id: 'solid',
64
64
  name: 'Solid',
65
65
  getAddOns: () => [],
66
+ supportedModes: {
67
+ 'code-router': {
68
+ displayName: 'Code Router',
69
+ description: 'TanStack Router using code to define the routes',
70
+ forceTypescript: false,
71
+ },
72
+ 'file-router': {
73
+ displayName: 'File Router',
74
+ description: 'TanStack Router using files to define the routes',
75
+ forceTypescript: true,
76
+ },
77
+ },
66
78
  })
67
79
  fetch.mockResponseOnce(
68
80
  JSON.stringify({
@@ -36,6 +36,18 @@ beforeEach(() => {
36
36
  modes: ['file-router', 'code-router'],
37
37
  },
38
38
  ],
39
+ supportedModes: {
40
+ 'code-router': {
41
+ displayName: 'Code Router',
42
+ description: 'TanStack Router using code to define the routes',
43
+ forceTypescript: false,
44
+ },
45
+ 'file-router': {
46
+ displayName: 'File Router',
47
+ description: 'TanStack Router using files to define the routes',
48
+ forceTypescript: true,
49
+ },
50
+ },
39
51
  } as unknown as Framework)
40
52
 
41
53
  __testRegisterFramework({
@@ -11,7 +11,6 @@ import {
11
11
  selectTailwind,
12
12
  selectToolchain,
13
13
  } from '../src/ui-prompts'
14
- import { FILE_ROUTER } from '@tanstack/cta-engine'
15
14
 
16
15
  import type { AddOn, Framework } from '@tanstack/cta-engine'
17
16
 
@@ -44,7 +43,7 @@ describe('selectRouterType', () => {
44
43
  vi.spyOn(clack, 'isCancel').mockImplementation(() => false)
45
44
 
46
45
  const routerType = await selectRouterType()
47
- expect(routerType).toBe(FILE_ROUTER)
46
+ expect(routerType).toBe('file-router')
48
47
  })
49
48
 
50
49
  it('should exit on cancel', async () => {