@tanstack/cta-cli 0.28.0 → 0.29.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/dist/cli.js +64 -2
- package/dist/command-line.js +16 -1
- package/dist/mcp.js +8 -3
- package/dist/options.js +14 -2
- package/dist/types/types.d.ts +2 -0
- package/dist/types/ui-prompts.d.ts +1 -0
- package/dist/ui-prompts.js +31 -0
- package/package.json +3 -3
- package/src/cli.ts +68 -1
- package/src/command-line.ts +16 -0
- package/src/mcp.ts +8 -1
- package/src/options.ts +13 -0
- package/src/types.ts +2 -0
- package/src/ui-prompts.ts +47 -0
package/dist/cli.js
CHANGED
|
@@ -226,17 +226,79 @@ Remove your node_modules directory and package lock file and re-install.`);
|
|
|
226
226
|
return addOns;
|
|
227
227
|
})
|
|
228
228
|
.option('--list-add-ons', 'list all available add-ons', false)
|
|
229
|
+
.option('--addon-details <addon-id>', 'show detailed information about a specific add-on')
|
|
229
230
|
.option('--no-git', 'do not create a git repository')
|
|
230
231
|
.option('--target-dir <path>', 'the target directory for the application root')
|
|
231
232
|
.option('--mcp', 'run the MCP server', false)
|
|
232
233
|
.option('--mcp-sse', 'run the MCP server in SSE mode', false)
|
|
233
|
-
.option('--ui', 'Add with the UI')
|
|
234
|
+
.option('--ui', 'Add with the UI')
|
|
235
|
+
.option('--add-on-config <config>', 'JSON string with add-on configuration options');
|
|
234
236
|
program.action(async (projectName, options) => {
|
|
235
237
|
if (options.listAddOns) {
|
|
236
238
|
const addOns = await getAllAddOns(getFrameworkByName(options.framework || defaultFramework || 'React'), defaultMode ||
|
|
237
239
|
convertTemplateToMode(options.template || defaultTemplate));
|
|
240
|
+
let hasConfigurableAddOns = false;
|
|
238
241
|
for (const addOn of addOns.filter((a) => !forcedAddOns.includes(a.id))) {
|
|
239
|
-
|
|
242
|
+
const hasOptions = addOn.options && Object.keys(addOn.options).length > 0;
|
|
243
|
+
const optionMarker = hasOptions ? '*' : ' ';
|
|
244
|
+
if (hasOptions)
|
|
245
|
+
hasConfigurableAddOns = true;
|
|
246
|
+
console.log(`${optionMarker} ${chalk.bold(addOn.id)}: ${addOn.description}`);
|
|
247
|
+
}
|
|
248
|
+
if (hasConfigurableAddOns) {
|
|
249
|
+
console.log('\n* = has configuration options');
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
else if (options.addonDetails) {
|
|
253
|
+
const addOns = await getAllAddOns(getFrameworkByName(options.framework || defaultFramework || 'React'), defaultMode ||
|
|
254
|
+
convertTemplateToMode(options.template || defaultTemplate));
|
|
255
|
+
const addOn = addOns.find((a) => a.id === options.addonDetails);
|
|
256
|
+
if (!addOn) {
|
|
257
|
+
console.error(`Add-on '${options.addonDetails}' not found`);
|
|
258
|
+
process.exit(1);
|
|
259
|
+
}
|
|
260
|
+
console.log(`${chalk.bold.cyan('Add-on Details:')} ${chalk.bold(addOn.name)}`);
|
|
261
|
+
console.log(`${chalk.bold('ID:')} ${addOn.id}`);
|
|
262
|
+
console.log(`${chalk.bold('Description:')} ${addOn.description}`);
|
|
263
|
+
console.log(`${chalk.bold('Type:')} ${addOn.type}`);
|
|
264
|
+
console.log(`${chalk.bold('Phase:')} ${addOn.phase}`);
|
|
265
|
+
console.log(`${chalk.bold('Supported Modes:')} ${addOn.modes.join(', ')}`);
|
|
266
|
+
if (addOn.link) {
|
|
267
|
+
console.log(`${chalk.bold('Link:')} ${chalk.blue(addOn.link)}`);
|
|
268
|
+
}
|
|
269
|
+
if (addOn.dependsOn && addOn.dependsOn.length > 0) {
|
|
270
|
+
console.log(`${chalk.bold('Dependencies:')} ${addOn.dependsOn.join(', ')}`);
|
|
271
|
+
}
|
|
272
|
+
if (addOn.options && Object.keys(addOn.options).length > 0) {
|
|
273
|
+
console.log(`\n${chalk.bold.yellow('Configuration Options:')}`);
|
|
274
|
+
for (const [optionName, option] of Object.entries(addOn.options)) {
|
|
275
|
+
if (option && typeof option === 'object' && 'type' in option) {
|
|
276
|
+
const opt = option;
|
|
277
|
+
console.log(` ${chalk.bold(optionName)}:`);
|
|
278
|
+
console.log(` Label: ${opt.label}`);
|
|
279
|
+
if (opt.description) {
|
|
280
|
+
console.log(` Description: ${opt.description}`);
|
|
281
|
+
}
|
|
282
|
+
console.log(` Type: ${opt.type}`);
|
|
283
|
+
console.log(` Default: ${opt.default}`);
|
|
284
|
+
if (opt.type === 'select' && opt.options) {
|
|
285
|
+
console.log(` Available values:`);
|
|
286
|
+
for (const choice of opt.options) {
|
|
287
|
+
console.log(` - ${choice.value}: ${choice.label}`);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
console.log(`\n${chalk.gray('No configuration options available')}`);
|
|
295
|
+
}
|
|
296
|
+
if (addOn.routes && addOn.routes.length > 0) {
|
|
297
|
+
console.log(`\n${chalk.bold.green('Routes:')}`);
|
|
298
|
+
for (const route of addOn.routes) {
|
|
299
|
+
console.log(` ${chalk.bold(route.url)} (${route.name})`);
|
|
300
|
+
console.log(` File: ${route.path}`);
|
|
301
|
+
}
|
|
240
302
|
}
|
|
241
303
|
}
|
|
242
304
|
else if (options.mcp || options.mcpSse) {
|
package/dist/command-line.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { resolve } from 'node:path';
|
|
2
|
-
import { DEFAULT_PACKAGE_MANAGER, finalizeAddOns, getFrameworkById, getPackageManager, loadStarter, } from '@tanstack/cta-engine';
|
|
2
|
+
import { DEFAULT_PACKAGE_MANAGER, finalizeAddOns, getFrameworkById, getPackageManager, loadStarter, populateAddOnOptionsDefaults, } 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) {
|
|
@@ -69,6 +69,17 @@ export async function normalizeOptions(cliOptions, forcedMode, forcedAddOns, opt
|
|
|
69
69
|
tailwind = true;
|
|
70
70
|
typescript = true;
|
|
71
71
|
}
|
|
72
|
+
// Handle add-on configuration option
|
|
73
|
+
let addOnOptionsFromCLI = {};
|
|
74
|
+
if (cliOptions.addOnConfig) {
|
|
75
|
+
try {
|
|
76
|
+
addOnOptionsFromCLI = JSON.parse(cliOptions.addOnConfig);
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
console.error('Error parsing add-on config:', error);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
72
83
|
return {
|
|
73
84
|
projectName: projectName,
|
|
74
85
|
targetDir: resolve(process.cwd(), projectName),
|
|
@@ -81,6 +92,10 @@ export async function normalizeOptions(cliOptions, forcedMode, forcedAddOns, opt
|
|
|
81
92
|
DEFAULT_PACKAGE_MANAGER,
|
|
82
93
|
git: !!cliOptions.git,
|
|
83
94
|
chosenAddOns,
|
|
95
|
+
addOnOptions: {
|
|
96
|
+
...populateAddOnOptionsDefaults(chosenAddOns),
|
|
97
|
+
...addOnOptionsFromCLI
|
|
98
|
+
},
|
|
84
99
|
starter: starter,
|
|
85
100
|
};
|
|
86
101
|
}
|
package/dist/mcp.js
CHANGED
|
@@ -5,7 +5,7 @@ import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
|
|
|
5
5
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
6
6
|
import express from 'express';
|
|
7
7
|
import { z } from 'zod';
|
|
8
|
-
import { createApp, createDefaultEnvironment, finalizeAddOns, getFrameworkByName, getFrameworks, } from '@tanstack/cta-engine';
|
|
8
|
+
import { createApp, createDefaultEnvironment, finalizeAddOns, getFrameworkByName, getFrameworks, populateAddOnOptionsDefaults, } from '@tanstack/cta-engine';
|
|
9
9
|
function createServer({ appName, forcedAddOns = [], }) {
|
|
10
10
|
const server = new McpServer({
|
|
11
11
|
name: `${appName} Application Builder`,
|
|
@@ -28,7 +28,10 @@ function createServer({ appName, forcedAddOns = [], }) {
|
|
|
28
28
|
.filter((addOn) => addOn.modes.includes('file-router'))
|
|
29
29
|
.map((addOn) => ({
|
|
30
30
|
id: addOn.id,
|
|
31
|
+
name: addOn.name,
|
|
31
32
|
description: addOn.description,
|
|
33
|
+
options: addOn.options,
|
|
34
|
+
dependsOn: addOn.dependsOn,
|
|
32
35
|
}))),
|
|
33
36
|
},
|
|
34
37
|
],
|
|
@@ -42,11 +45,12 @@ function createServer({ appName, forcedAddOns = [], }) {
|
|
|
42
45
|
.string()
|
|
43
46
|
.describe('The package.json module name of the application (will also be the directory name)'),
|
|
44
47
|
cwd: z.string().describe('The directory to create the application in'),
|
|
45
|
-
addOns: z.array(z.string()).describe('
|
|
48
|
+
addOns: z.array(z.string()).describe('Array of add-on IDs to install. Use listTanStackAddOns tool to see available add-ons and their configuration options. Example: ["prisma", "shadcn", "tanstack-query"]'),
|
|
49
|
+
addOnOptions: z.record(z.record(z.any())).optional().describe('Configuration options for add-ons. Format: {"addOnId": {"optionName": "value"}}. Use listTanStackAddOns to see available options for each add-on.'),
|
|
46
50
|
targetDir: z
|
|
47
51
|
.string()
|
|
48
52
|
.describe('The directory to create the application in. Use the absolute path of the directory you want the application to be created in'),
|
|
49
|
-
}, async ({ framework: frameworkName, projectName, addOns, cwd, targetDir, }) => {
|
|
53
|
+
}, async ({ framework: frameworkName, projectName, addOns, addOnOptions, cwd, targetDir, }) => {
|
|
50
54
|
const framework = getFrameworkByName(frameworkName);
|
|
51
55
|
try {
|
|
52
56
|
process.chdir(cwd);
|
|
@@ -64,6 +68,7 @@ function createServer({ appName, forcedAddOns = [], }) {
|
|
|
64
68
|
packageManager: 'pnpm',
|
|
65
69
|
mode: 'file-router',
|
|
66
70
|
chosenAddOns,
|
|
71
|
+
addOnOptions: addOnOptions || populateAddOnOptionsDefaults(chosenAddOns),
|
|
67
72
|
git: true,
|
|
68
73
|
});
|
|
69
74
|
}
|
package/dist/options.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
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, selectHost, selectTypescript, } from './ui-prompts.js';
|
|
2
|
+
import { finalizeAddOns, getFrameworkById, getPackageManager, populateAddOnOptionsDefaults, readConfigFile, } from '@tanstack/cta-engine';
|
|
3
|
+
import { getProjectName, promptForAddOnOptions, selectAddOns, selectGit, selectPackageManager, selectRouterType, selectTailwind, selectToolchain, selectHost, 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');
|
|
@@ -76,6 +76,18 @@ export async function promptForCreateOptions(cliOptions, { forcedAddOns = [], fo
|
|
|
76
76
|
options.tailwind = true;
|
|
77
77
|
options.typescript = true;
|
|
78
78
|
}
|
|
79
|
+
// Prompt for add-on options in interactive mode
|
|
80
|
+
if (Array.isArray(cliOptions.addOns)) {
|
|
81
|
+
// Non-interactive mode: use defaults
|
|
82
|
+
options.addOnOptions = populateAddOnOptionsDefaults(options.chosenAddOns);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
// Interactive mode: prompt for options
|
|
86
|
+
const userOptions = await promptForAddOnOptions(options.chosenAddOns.map(a => a.id), options.framework);
|
|
87
|
+
const defaultOptions = populateAddOnOptionsDefaults(options.chosenAddOns);
|
|
88
|
+
// Merge user options with defaults
|
|
89
|
+
options.addOnOptions = { ...defaultOptions, ...userOptions };
|
|
90
|
+
}
|
|
79
91
|
options.git = cliOptions.git || (await selectGit());
|
|
80
92
|
return options;
|
|
81
93
|
}
|
package/dist/types/types.d.ts
CHANGED
|
@@ -11,10 +11,12 @@ export interface CliOptions {
|
|
|
11
11
|
git?: boolean;
|
|
12
12
|
addOns?: Array<string> | boolean;
|
|
13
13
|
listAddOns?: boolean;
|
|
14
|
+
addonDetails?: string;
|
|
14
15
|
mcp?: boolean;
|
|
15
16
|
mcpSse?: boolean;
|
|
16
17
|
starter?: string;
|
|
17
18
|
targetDir?: string;
|
|
18
19
|
interactive?: boolean;
|
|
19
20
|
ui?: boolean;
|
|
21
|
+
addOnConfig?: string;
|
|
20
22
|
}
|
|
@@ -8,4 +8,5 @@ export declare function selectPackageManager(): Promise<PackageManager>;
|
|
|
8
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>;
|
|
11
|
+
export declare function promptForAddOnOptions(addOnIds: Array<string>, framework: Framework): Promise<Record<string, Record<string, any>>>;
|
|
11
12
|
export declare function selectHost(framework: Framework, host?: string): Promise<string | undefined>;
|
package/dist/ui-prompts.js
CHANGED
|
@@ -138,6 +138,37 @@ export async function selectToolchain(framework, toolchain) {
|
|
|
138
138
|
}
|
|
139
139
|
return tc;
|
|
140
140
|
}
|
|
141
|
+
export async function promptForAddOnOptions(addOnIds, framework) {
|
|
142
|
+
const addOnOptions = {};
|
|
143
|
+
for (const addOnId of addOnIds) {
|
|
144
|
+
const addOn = framework.getAddOns().find((a) => a.id === addOnId);
|
|
145
|
+
if (!addOn || !addOn.options)
|
|
146
|
+
continue;
|
|
147
|
+
addOnOptions[addOnId] = {};
|
|
148
|
+
for (const [optionName, option] of Object.entries(addOn.options)) {
|
|
149
|
+
if (option && typeof option === 'object' && 'type' in option) {
|
|
150
|
+
if (option.type === 'select') {
|
|
151
|
+
const selectOption = option;
|
|
152
|
+
const value = await select({
|
|
153
|
+
message: `${addOn.name}: ${selectOption.label}`,
|
|
154
|
+
options: selectOption.options.map((opt) => ({
|
|
155
|
+
value: opt.value,
|
|
156
|
+
label: opt.label,
|
|
157
|
+
})),
|
|
158
|
+
initialValue: selectOption.default,
|
|
159
|
+
});
|
|
160
|
+
if (isCancel(value)) {
|
|
161
|
+
cancel('Operation cancelled.');
|
|
162
|
+
process.exit(0);
|
|
163
|
+
}
|
|
164
|
+
addOnOptions[addOnId][optionName] = value;
|
|
165
|
+
}
|
|
166
|
+
// Future option types can be added here
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return addOnOptions;
|
|
171
|
+
}
|
|
141
172
|
export async function selectHost(framework, host) {
|
|
142
173
|
const hosts = new Set();
|
|
143
174
|
let initialValue = undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/cta-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.29.0",
|
|
4
4
|
"description": "Tanstack Application Builder CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
"express": "^4.21.2",
|
|
31
31
|
"semver": "^7.7.2",
|
|
32
32
|
"zod": "^3.24.2",
|
|
33
|
-
"@tanstack/cta-
|
|
34
|
-
"@tanstack/cta-
|
|
33
|
+
"@tanstack/cta-engine": "0.29.0",
|
|
34
|
+
"@tanstack/cta-ui": "0.29.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@tanstack/config": "^0.16.2",
|
package/src/cli.ts
CHANGED
|
@@ -353,6 +353,7 @@ Remove your node_modules directory and package lock file and re-install.`,
|
|
|
353
353
|
},
|
|
354
354
|
)
|
|
355
355
|
.option('--list-add-ons', 'list all available add-ons', false)
|
|
356
|
+
.option('--addon-details <addon-id>', 'show detailed information about a specific add-on')
|
|
356
357
|
.option('--no-git', 'do not create a git repository')
|
|
357
358
|
.option(
|
|
358
359
|
'--target-dir <path>',
|
|
@@ -361,6 +362,7 @@ Remove your node_modules directory and package lock file and re-install.`,
|
|
|
361
362
|
.option('--mcp', 'run the MCP server', false)
|
|
362
363
|
.option('--mcp-sse', 'run the MCP server in SSE mode', false)
|
|
363
364
|
.option('--ui', 'Add with the UI')
|
|
365
|
+
.option('--add-on-config <config>', 'JSON string with add-on configuration options')
|
|
364
366
|
|
|
365
367
|
program.action(async (projectName: string, options: CliOptions) => {
|
|
366
368
|
if (options.listAddOns) {
|
|
@@ -369,8 +371,73 @@ Remove your node_modules directory and package lock file and re-install.`,
|
|
|
369
371
|
defaultMode ||
|
|
370
372
|
convertTemplateToMode(options.template || defaultTemplate),
|
|
371
373
|
)
|
|
374
|
+
let hasConfigurableAddOns = false
|
|
372
375
|
for (const addOn of addOns.filter((a) => !forcedAddOns.includes(a.id))) {
|
|
373
|
-
|
|
376
|
+
const hasOptions = addOn.options && Object.keys(addOn.options).length > 0
|
|
377
|
+
const optionMarker = hasOptions ? '*' : ' '
|
|
378
|
+
if (hasOptions) hasConfigurableAddOns = true
|
|
379
|
+
console.log(`${optionMarker} ${chalk.bold(addOn.id)}: ${addOn.description}`)
|
|
380
|
+
}
|
|
381
|
+
if (hasConfigurableAddOns) {
|
|
382
|
+
console.log('\n* = has configuration options')
|
|
383
|
+
}
|
|
384
|
+
} else if (options.addonDetails) {
|
|
385
|
+
const addOns = await getAllAddOns(
|
|
386
|
+
getFrameworkByName(options.framework || defaultFramework || 'React')!,
|
|
387
|
+
defaultMode ||
|
|
388
|
+
convertTemplateToMode(options.template || defaultTemplate),
|
|
389
|
+
)
|
|
390
|
+
const addOn = addOns.find((a) => a.id === options.addonDetails)
|
|
391
|
+
if (!addOn) {
|
|
392
|
+
console.error(`Add-on '${options.addonDetails}' not found`)
|
|
393
|
+
process.exit(1)
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
console.log(`${chalk.bold.cyan('Add-on Details:')} ${chalk.bold(addOn.name)}`)
|
|
397
|
+
console.log(`${chalk.bold('ID:')} ${addOn.id}`)
|
|
398
|
+
console.log(`${chalk.bold('Description:')} ${addOn.description}`)
|
|
399
|
+
console.log(`${chalk.bold('Type:')} ${addOn.type}`)
|
|
400
|
+
console.log(`${chalk.bold('Phase:')} ${addOn.phase}`)
|
|
401
|
+
console.log(`${chalk.bold('Supported Modes:')} ${addOn.modes.join(', ')}`)
|
|
402
|
+
|
|
403
|
+
if (addOn.link) {
|
|
404
|
+
console.log(`${chalk.bold('Link:')} ${chalk.blue(addOn.link)}`)
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (addOn.dependsOn && addOn.dependsOn.length > 0) {
|
|
408
|
+
console.log(`${chalk.bold('Dependencies:')} ${addOn.dependsOn.join(', ')}`)
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (addOn.options && Object.keys(addOn.options).length > 0) {
|
|
412
|
+
console.log(`\n${chalk.bold.yellow('Configuration Options:')}`)
|
|
413
|
+
for (const [optionName, option] of Object.entries(addOn.options)) {
|
|
414
|
+
if (option && typeof option === 'object' && 'type' in option) {
|
|
415
|
+
const opt = option as any
|
|
416
|
+
console.log(` ${chalk.bold(optionName)}:`)
|
|
417
|
+
console.log(` Label: ${opt.label}`)
|
|
418
|
+
if (opt.description) {
|
|
419
|
+
console.log(` Description: ${opt.description}`)
|
|
420
|
+
}
|
|
421
|
+
console.log(` Type: ${opt.type}`)
|
|
422
|
+
console.log(` Default: ${opt.default}`)
|
|
423
|
+
if (opt.type === 'select' && opt.options) {
|
|
424
|
+
console.log(` Available values:`)
|
|
425
|
+
for (const choice of opt.options) {
|
|
426
|
+
console.log(` - ${choice.value}: ${choice.label}`)
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
} else {
|
|
432
|
+
console.log(`\n${chalk.gray('No configuration options available')}`)
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if (addOn.routes && addOn.routes.length > 0) {
|
|
436
|
+
console.log(`\n${chalk.bold.green('Routes:')}`)
|
|
437
|
+
for (const route of addOn.routes) {
|
|
438
|
+
console.log(` ${chalk.bold(route.url)} (${route.name})`)
|
|
439
|
+
console.log(` File: ${route.path}`)
|
|
440
|
+
}
|
|
374
441
|
}
|
|
375
442
|
} else if (options.mcp || options.mcpSse) {
|
|
376
443
|
await runMCPServer(!!options.mcpSse, {
|
package/src/command-line.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
getFrameworkById,
|
|
7
7
|
getPackageManager,
|
|
8
8
|
loadStarter,
|
|
9
|
+
populateAddOnOptionsDefaults,
|
|
9
10
|
} from '@tanstack/cta-engine'
|
|
10
11
|
|
|
11
12
|
import type { Options } from '@tanstack/cta-engine'
|
|
@@ -110,6 +111,17 @@ export async function normalizeOptions(
|
|
|
110
111
|
typescript = true
|
|
111
112
|
}
|
|
112
113
|
|
|
114
|
+
// Handle add-on configuration option
|
|
115
|
+
let addOnOptionsFromCLI = {}
|
|
116
|
+
if (cliOptions.addOnConfig) {
|
|
117
|
+
try {
|
|
118
|
+
addOnOptionsFromCLI = JSON.parse(cliOptions.addOnConfig)
|
|
119
|
+
} catch (error) {
|
|
120
|
+
console.error('Error parsing add-on config:', error)
|
|
121
|
+
process.exit(1)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
113
125
|
return {
|
|
114
126
|
projectName: projectName,
|
|
115
127
|
targetDir: resolve(process.cwd(), projectName),
|
|
@@ -123,6 +135,10 @@ export async function normalizeOptions(
|
|
|
123
135
|
DEFAULT_PACKAGE_MANAGER,
|
|
124
136
|
git: !!cliOptions.git,
|
|
125
137
|
chosenAddOns,
|
|
138
|
+
addOnOptions: {
|
|
139
|
+
...populateAddOnOptionsDefaults(chosenAddOns),
|
|
140
|
+
...addOnOptionsFromCLI
|
|
141
|
+
},
|
|
126
142
|
starter: starter,
|
|
127
143
|
}
|
|
128
144
|
}
|
package/src/mcp.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
finalizeAddOns,
|
|
14
14
|
getFrameworkByName,
|
|
15
15
|
getFrameworks,
|
|
16
|
+
populateAddOnOptionsDefaults,
|
|
16
17
|
} from '@tanstack/cta-engine'
|
|
17
18
|
|
|
18
19
|
function createServer({
|
|
@@ -53,7 +54,10 @@ function createServer({
|
|
|
53
54
|
.filter((addOn) => addOn.modes.includes('file-router'))
|
|
54
55
|
.map((addOn) => ({
|
|
55
56
|
id: addOn.id,
|
|
57
|
+
name: addOn.name,
|
|
56
58
|
description: addOn.description,
|
|
59
|
+
options: addOn.options,
|
|
60
|
+
dependsOn: addOn.dependsOn,
|
|
57
61
|
})),
|
|
58
62
|
),
|
|
59
63
|
},
|
|
@@ -77,7 +81,8 @@ function createServer({
|
|
|
77
81
|
'The package.json module name of the application (will also be the directory name)',
|
|
78
82
|
),
|
|
79
83
|
cwd: z.string().describe('The directory to create the application in'),
|
|
80
|
-
addOns: z.array(z.string()).describe('
|
|
84
|
+
addOns: z.array(z.string()).describe('Array of add-on IDs to install. Use listTanStackAddOns tool to see available add-ons and their configuration options. Example: ["prisma", "shadcn", "tanstack-query"]'),
|
|
85
|
+
addOnOptions: z.record(z.record(z.any())).optional().describe('Configuration options for add-ons. Format: {"addOnId": {"optionName": "value"}}. Use listTanStackAddOns to see available options for each add-on.'),
|
|
81
86
|
targetDir: z
|
|
82
87
|
.string()
|
|
83
88
|
.describe(
|
|
@@ -88,6 +93,7 @@ function createServer({
|
|
|
88
93
|
framework: frameworkName,
|
|
89
94
|
projectName,
|
|
90
95
|
addOns,
|
|
96
|
+
addOnOptions,
|
|
91
97
|
cwd,
|
|
92
98
|
targetDir,
|
|
93
99
|
}) => {
|
|
@@ -114,6 +120,7 @@ function createServer({
|
|
|
114
120
|
packageManager: 'pnpm',
|
|
115
121
|
mode: 'file-router',
|
|
116
122
|
chosenAddOns,
|
|
123
|
+
addOnOptions: addOnOptions || populateAddOnOptionsDefaults(chosenAddOns),
|
|
117
124
|
git: true,
|
|
118
125
|
})
|
|
119
126
|
} catch (error) {
|
package/src/options.ts
CHANGED
|
@@ -4,11 +4,13 @@ import {
|
|
|
4
4
|
finalizeAddOns,
|
|
5
5
|
getFrameworkById,
|
|
6
6
|
getPackageManager,
|
|
7
|
+
populateAddOnOptionsDefaults,
|
|
7
8
|
readConfigFile,
|
|
8
9
|
} from '@tanstack/cta-engine'
|
|
9
10
|
|
|
10
11
|
import {
|
|
11
12
|
getProjectName,
|
|
13
|
+
promptForAddOnOptions,
|
|
12
14
|
selectAddOns,
|
|
13
15
|
selectGit,
|
|
14
16
|
selectPackageManager,
|
|
@@ -137,6 +139,17 @@ export async function promptForCreateOptions(
|
|
|
137
139
|
options.typescript = true
|
|
138
140
|
}
|
|
139
141
|
|
|
142
|
+
// Prompt for add-on options in interactive mode
|
|
143
|
+
if (Array.isArray(cliOptions.addOns)) {
|
|
144
|
+
// Non-interactive mode: use defaults
|
|
145
|
+
options.addOnOptions = populateAddOnOptionsDefaults(options.chosenAddOns)
|
|
146
|
+
} else {
|
|
147
|
+
// Interactive mode: prompt for options
|
|
148
|
+
const userOptions = await promptForAddOnOptions(options.chosenAddOns.map(a => a.id), options.framework)
|
|
149
|
+
const defaultOptions = populateAddOnOptionsDefaults(options.chosenAddOns)
|
|
150
|
+
// Merge user options with defaults
|
|
151
|
+
options.addOnOptions = { ...defaultOptions, ...userOptions }
|
|
152
|
+
}
|
|
140
153
|
options.git = cliOptions.git || (await selectGit())
|
|
141
154
|
|
|
142
155
|
return options
|
package/src/types.ts
CHANGED
|
@@ -13,10 +13,12 @@ export interface CliOptions {
|
|
|
13
13
|
git?: boolean
|
|
14
14
|
addOns?: Array<string> | boolean
|
|
15
15
|
listAddOns?: boolean
|
|
16
|
+
addonDetails?: string
|
|
16
17
|
mcp?: boolean
|
|
17
18
|
mcpSse?: boolean
|
|
18
19
|
starter?: string
|
|
19
20
|
targetDir?: string
|
|
20
21
|
interactive?: boolean
|
|
21
22
|
ui?: boolean
|
|
23
|
+
addOnConfig?: string
|
|
22
24
|
}
|
package/src/ui-prompts.ts
CHANGED
|
@@ -185,6 +185,53 @@ export async function selectToolchain(
|
|
|
185
185
|
return tc
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
+
export async function promptForAddOnOptions(
|
|
189
|
+
addOnIds: Array<string>,
|
|
190
|
+
framework: Framework,
|
|
191
|
+
): Promise<Record<string, Record<string, any>>> {
|
|
192
|
+
const addOnOptions: Record<string, Record<string, any>> = {}
|
|
193
|
+
|
|
194
|
+
for (const addOnId of addOnIds) {
|
|
195
|
+
const addOn = framework.getAddOns().find((a) => a.id === addOnId)
|
|
196
|
+
if (!addOn || !addOn.options) continue
|
|
197
|
+
|
|
198
|
+
addOnOptions[addOnId] = {}
|
|
199
|
+
|
|
200
|
+
for (const [optionName, option] of Object.entries(addOn.options)) {
|
|
201
|
+
if (option && typeof option === 'object' && 'type' in option) {
|
|
202
|
+
if (option.type === 'select') {
|
|
203
|
+
const selectOption = option as {
|
|
204
|
+
type: 'select'
|
|
205
|
+
label: string
|
|
206
|
+
description?: string
|
|
207
|
+
default: string
|
|
208
|
+
options: Array<{ value: string; label: string }>
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const value = await select({
|
|
212
|
+
message: `${addOn.name}: ${selectOption.label}`,
|
|
213
|
+
options: selectOption.options.map((opt) => ({
|
|
214
|
+
value: opt.value,
|
|
215
|
+
label: opt.label,
|
|
216
|
+
})),
|
|
217
|
+
initialValue: selectOption.default,
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
if (isCancel(value)) {
|
|
221
|
+
cancel('Operation cancelled.')
|
|
222
|
+
process.exit(0)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
addOnOptions[addOnId][optionName] = value
|
|
226
|
+
}
|
|
227
|
+
// Future option types can be added here
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return addOnOptions
|
|
233
|
+
}
|
|
234
|
+
|
|
188
235
|
export async function selectHost(
|
|
189
236
|
framework: Framework,
|
|
190
237
|
host?: string,
|