@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 +50 -36
- package/dist/command-line.js +14 -10
- package/dist/options.js +10 -5
- package/dist/types/cli.d.ts +5 -3
- package/dist/types/command-line.d.ts +2 -2
- package/dist/types/mcp.d.ts +1 -2
- package/dist/types/options.d.ts +2 -2
- package/dist/types/ui-prompts.d.ts +3 -3
- package/dist/types/utils.d.ts +1 -2
- package/dist/ui-prompts.js +4 -4
- package/dist/utils.js +2 -3
- package/package.json +3 -3
- package/src/cli.ts +59 -45
- package/src/command-line.ts +23 -17
- package/src/mcp.ts +1 -3
- package/src/options.ts +14 -8
- package/src/ui-prompts.ts +7 -9
- package/src/utils.ts +3 -6
- package/tests/command-line.test.ts +12 -0
- package/tests/options.test.ts +12 -0
- package/tests/ui-prompts.test.ts +1 -2
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
|
-
|
|
13
|
-
|
|
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:
|
|
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 (!
|
|
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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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 (
|
|
166
|
-
cliOptions.template =
|
|
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,
|
|
186
|
+
finalOptions = await normalizeOptions(cliOptions, defaultMode, forcedAddOns);
|
|
174
187
|
}
|
|
175
188
|
if (options.ui) {
|
|
176
|
-
const optionsFromCLI = await normalizeOptions(cliOptions,
|
|
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:
|
|
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
|
}
|
package/dist/command-line.js
CHANGED
|
@@ -1,23 +1,20 @@
|
|
|
1
1
|
import { resolve } from 'node:path';
|
|
2
|
-
import {
|
|
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
|
-
|
|
13
|
-
|
|
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' ?
|
|
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 ===
|
|
22
|
-
if (
|
|
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
|
package/dist/types/cli.d.ts
CHANGED
|
@@ -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?:
|
|
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 {
|
|
1
|
+
import type { Options } from '@tanstack/cta-engine';
|
|
2
2
|
import type { CliOptions } from './types.js';
|
|
3
|
-
export declare function normalizeOptions(cliOptions: CliOptions, forcedMode?:
|
|
3
|
+
export declare function normalizeOptions(cliOptions: CliOptions, forcedMode?: string, forcedAddOns?: Array<string>, opts?: {
|
|
4
4
|
disableNameCheck?: boolean;
|
|
5
5
|
}): Promise<Options | undefined>;
|
package/dist/types/mcp.d.ts
CHANGED
package/dist/types/options.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type {
|
|
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?:
|
|
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 {
|
|
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<
|
|
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:
|
|
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>;
|
package/dist/types/utils.d.ts
CHANGED
package/dist/ui-prompts.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { cancel, confirm, isCancel, multiselect, select, text, } from '@clack/prompts';
|
|
2
|
-
import {
|
|
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:
|
|
24
|
+
value: 'file-router',
|
|
25
25
|
label: 'File Router - File-based routing structure',
|
|
26
26
|
},
|
|
27
27
|
{
|
|
28
|
-
value:
|
|
28
|
+
value: 'code-router',
|
|
29
29
|
label: 'Code Router - Traditional code-based routing',
|
|
30
30
|
},
|
|
31
31
|
],
|
|
32
|
-
initialValue:
|
|
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
|
|
3
|
+
return 'code-router';
|
|
5
4
|
}
|
|
6
|
-
return
|
|
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
|
+
"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.
|
|
33
|
-
"@tanstack/cta-ui": "0.15.
|
|
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
|
-
|
|
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?:
|
|
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:
|
|
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 (!
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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 (
|
|
275
|
-
cliOptions.template =
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
}
|
package/src/command-line.ts
CHANGED
|
@@ -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 {
|
|
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?:
|
|
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:
|
|
41
|
-
forcedMode ||
|
|
42
|
-
|
|
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
|
|
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?:
|
|
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 {
|
|
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?:
|
|
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' ?
|
|
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 ===
|
|
55
|
-
if (
|
|
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,
|
|
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<
|
|
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:
|
|
44
|
+
value: 'file-router',
|
|
47
45
|
label: 'File Router - File-based routing structure',
|
|
48
46
|
},
|
|
49
47
|
{
|
|
50
|
-
value:
|
|
48
|
+
value: 'code-router',
|
|
51
49
|
label: 'Code Router - Traditional code-based routing',
|
|
52
50
|
},
|
|
53
51
|
],
|
|
54
|
-
initialValue:
|
|
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
|
|
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:
|
|
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):
|
|
3
|
+
export function convertTemplateToMode(template: TemplateOptions): string {
|
|
7
4
|
if (template === 'typescript' || template === 'javascript') {
|
|
8
|
-
return
|
|
5
|
+
return 'code-router'
|
|
9
6
|
}
|
|
10
|
-
return
|
|
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({
|
package/tests/options.test.ts
CHANGED
|
@@ -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({
|
package/tests/ui-prompts.test.ts
CHANGED
|
@@ -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(
|
|
46
|
+
expect(routerType).toBe('file-router')
|
|
48
47
|
})
|
|
49
48
|
|
|
50
49
|
it('should exit on cancel', async () => {
|