@tanstack/cta-cli 0.10.0-alpha.19 → 0.10.0-alpha.21
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 +82 -33
- package/dist/command-line.js +75 -0
- package/dist/mcp.js +170 -0
- package/dist/options.js +40 -331
- package/dist/types/cli.d.ts +3 -1
- package/dist/types/command-line.d.ts +3 -0
- package/dist/types/mcp.d.ts +7 -0
- package/dist/types/options.d.ts +2 -3
- package/dist/types/types.d.ts +3 -1
- package/dist/types/ui-environment.d.ts +1 -1
- package/dist/types/ui-prompts.d.ts +10 -0
- package/dist/types/utils.d.ts +3 -0
- package/dist/ui-environment.js +45 -42
- package/dist/ui-prompts.js +140 -0
- package/dist/utils.js +7 -0
- package/package.json +10 -6
- package/src/cli.ts +102 -46
- package/src/command-line.ts +111 -0
- package/src/mcp.ts +247 -0
- package/src/options.ts +69 -395
- package/src/types.ts +4 -1
- package/src/ui-environment.ts +54 -44
- package/src/ui-prompts.ts +187 -0
- package/src/utils.ts +11 -0
- package/tests/command-line.test.ts +205 -0
- package/tests/index.test.ts +9 -0
- package/tests/options.test.ts +287 -0
- package/tests/setupVitest.ts +6 -0
- package/tests/ui-environment.test.ts +97 -0
- package/tests/ui-prompts.test.ts +233 -0
- package/vitest.config.js +7 -0
package/dist/cli.js
CHANGED
|
@@ -1,20 +1,23 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
1
2
|
import { Command, InvalidArgumentError } from 'commander';
|
|
2
3
|
import { intro, log } from '@clack/prompts';
|
|
3
4
|
import chalk from 'chalk';
|
|
4
|
-
import { SUPPORTED_PACKAGE_MANAGERS, addToApp, createApp, getAllAddOns, getFrameworkById, getFrameworkByName, getFrameworks, } from '@tanstack/cta-engine';
|
|
5
|
-
import { initAddOn } from '@tanstack/cta-custom-add-on';
|
|
6
|
-
import { runMCPServer } from '@tanstack/cta-mcp';
|
|
5
|
+
import { SUPPORTED_PACKAGE_MANAGERS, addToApp, compileAddOn, compileStarter, createApp, createSerializedOptions, getAllAddOns, getFrameworkById, getFrameworkByName, getFrameworks, initAddOn, initStarter, } from '@tanstack/cta-engine';
|
|
7
6
|
import { launchUI } from '@tanstack/cta-ui';
|
|
8
|
-
import {
|
|
7
|
+
import { runMCPServer } from './mcp.js';
|
|
8
|
+
import { promptForOptions } from './options.js';
|
|
9
|
+
import { normalizeOptions } from './command-line.js';
|
|
9
10
|
import { createUIEnvironment } from './ui-environment.js';
|
|
10
|
-
|
|
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'));
|
|
12
15
|
for (const addOn of addOns.filter((a) => !forcedAddOns.includes(a.id))) {
|
|
13
16
|
console.log(`${chalk.bold(addOn.id)}: ${addOn.description}`);
|
|
14
17
|
}
|
|
15
18
|
}
|
|
16
|
-
export function cli({ name, appName, forcedMode, forcedAddOns, }) {
|
|
17
|
-
const environment = createUIEnvironment();
|
|
19
|
+
export function cli({ name, appName, forcedMode, forcedAddOns = [], defaultTemplate = 'javascript', }) {
|
|
20
|
+
const environment = createUIEnvironment(appName, false);
|
|
18
21
|
const program = new Command();
|
|
19
22
|
const availableFrameworks = getFrameworks().map((f) => f.name);
|
|
20
23
|
const toolchains = new Set();
|
|
@@ -28,30 +31,56 @@ export function cli({ name, appName, forcedMode, forcedAddOns, }) {
|
|
|
28
31
|
program.name(name).description(`CLI to create a new ${appName} application`);
|
|
29
32
|
program
|
|
30
33
|
.command('add')
|
|
31
|
-
.argument('add-on', 'Name of the add-
|
|
32
|
-
.
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
.argument('[add-on...]', 'Name of the add-ons (or add-ons separated by spaces or commas)')
|
|
35
|
+
.option('--forced', 'Force the add-on to be added', false)
|
|
36
|
+
.option('--ui', 'Add with the UI')
|
|
37
|
+
.action(async (addOns) => {
|
|
38
|
+
const parsedAddOns = [];
|
|
39
|
+
for (const addOn of addOns) {
|
|
40
|
+
if (addOn.includes(',') || addOn.includes(' ')) {
|
|
41
|
+
parsedAddOns.push(...addOn.split(/[\s,]+/).map((addon) => addon.trim()));
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
parsedAddOns.push(addOn.trim());
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (program.opts().ui) {
|
|
48
|
+
launchUI({
|
|
49
|
+
mode: 'add',
|
|
50
|
+
addOns: parsedAddOns,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
await addToApp(environment, parsedAddOns, process.cwd(), {
|
|
55
|
+
forced: program.opts().forced,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
36
58
|
});
|
|
37
59
|
const addOnCommand = program.command('add-on');
|
|
38
60
|
addOnCommand
|
|
39
|
-
.command('
|
|
40
|
-
.description('
|
|
61
|
+
.command('init')
|
|
62
|
+
.description('Initialize an add-on from the current project')
|
|
41
63
|
.action(async () => {
|
|
42
|
-
await initAddOn(
|
|
64
|
+
await initAddOn(environment);
|
|
43
65
|
});
|
|
44
66
|
addOnCommand
|
|
45
|
-
.command('
|
|
46
|
-
.description('
|
|
67
|
+
.command('compile')
|
|
68
|
+
.description('Update add-on from the current project')
|
|
47
69
|
.action(async () => {
|
|
48
|
-
|
|
70
|
+
await compileAddOn(environment);
|
|
49
71
|
});
|
|
50
|
-
program
|
|
51
|
-
|
|
52
|
-
.
|
|
72
|
+
const starterCommand = program.command('starter');
|
|
73
|
+
starterCommand
|
|
74
|
+
.command('init')
|
|
75
|
+
.description('Initialize a project starter from the current project')
|
|
76
|
+
.action(async () => {
|
|
77
|
+
await initStarter(environment);
|
|
78
|
+
});
|
|
79
|
+
starterCommand
|
|
80
|
+
.command('compile')
|
|
81
|
+
.description('Compile the starter JSON file for the current project')
|
|
53
82
|
.action(async () => {
|
|
54
|
-
await
|
|
83
|
+
await compileStarter(environment);
|
|
55
84
|
});
|
|
56
85
|
program.argument('[project-name]', 'name of the project');
|
|
57
86
|
if (!forcedMode) {
|
|
@@ -97,17 +126,19 @@ export function cli({ name, appName, forcedMode, forcedAddOns, }) {
|
|
|
97
126
|
.option('--no-git', 'do not create a git repository')
|
|
98
127
|
.option('--target-dir <path>', 'the target directory for the application root')
|
|
99
128
|
.option('--mcp', 'run the MCP server', false)
|
|
100
|
-
.option('--mcp-sse', 'run the MCP server in SSE mode', false)
|
|
129
|
+
.option('--mcp-sse', 'run the MCP server in SSE mode', false)
|
|
130
|
+
.option('--ui', 'Add with the UI');
|
|
101
131
|
program.action(async (projectName, options) => {
|
|
102
132
|
if (options.listAddOns) {
|
|
103
133
|
await listAddOns(options, {
|
|
104
|
-
forcedMode
|
|
134
|
+
forcedMode,
|
|
105
135
|
forcedAddOns,
|
|
136
|
+
defaultTemplate,
|
|
106
137
|
});
|
|
107
138
|
}
|
|
108
139
|
else if (options.mcp || options.mcpSse) {
|
|
109
140
|
await runMCPServer(!!options.mcpSse, {
|
|
110
|
-
forcedMode
|
|
141
|
+
forcedMode,
|
|
111
142
|
forcedAddOns,
|
|
112
143
|
appName,
|
|
113
144
|
});
|
|
@@ -129,22 +160,40 @@ export function cli({ name, appName, forcedMode, forcedAddOns, }) {
|
|
|
129
160
|
else {
|
|
130
161
|
finalOptions = await normalizeOptions(cliOptions, forcedMode, forcedAddOns);
|
|
131
162
|
}
|
|
163
|
+
if (options.ui) {
|
|
164
|
+
const defaultOptions = {
|
|
165
|
+
framework: getFrameworkById(cliOptions.framework || 'react-cra'),
|
|
166
|
+
mode: 'file-router',
|
|
167
|
+
chosenAddOns: [],
|
|
168
|
+
packageManager: 'pnpm',
|
|
169
|
+
projectName: projectName || 'my-app',
|
|
170
|
+
targetDir: resolve(process.cwd(), projectName || 'my-app'),
|
|
171
|
+
typescript: true,
|
|
172
|
+
tailwind: true,
|
|
173
|
+
git: true,
|
|
174
|
+
};
|
|
175
|
+
launchUI({
|
|
176
|
+
mode: 'setup',
|
|
177
|
+
options: createSerializedOptions(finalOptions || defaultOptions),
|
|
178
|
+
});
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
132
181
|
if (finalOptions) {
|
|
133
182
|
intro(`Creating a new ${appName} app in ${projectName}...`);
|
|
134
183
|
}
|
|
135
184
|
else {
|
|
136
185
|
intro(`Let's configure your ${appName} application`);
|
|
137
186
|
finalOptions = await promptForOptions(cliOptions, {
|
|
138
|
-
forcedMode
|
|
187
|
+
forcedMode,
|
|
139
188
|
forcedAddOns,
|
|
140
189
|
});
|
|
141
190
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
191
|
+
if (!finalOptions) {
|
|
192
|
+
throw new Error('No options were provided');
|
|
193
|
+
}
|
|
194
|
+
finalOptions.targetDir =
|
|
195
|
+
options.targetDir || resolve(process.cwd(), finalOptions.projectName);
|
|
196
|
+
await createApp(environment, finalOptions);
|
|
148
197
|
}
|
|
149
198
|
catch (error) {
|
|
150
199
|
log.error(error instanceof Error ? error.message : 'An unknown error occurred');
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
import { CODE_ROUTER, DEFAULT_PACKAGE_MANAGER, FILE_ROUTER, finalizeAddOns, getFrameworkById, getPackageManager, loadStarter, } from '@tanstack/cta-engine';
|
|
3
|
+
export async function normalizeOptions(cliOptions, forcedMode, forcedAddOns) {
|
|
4
|
+
const projectName = (cliOptions.projectName ?? '').trim();
|
|
5
|
+
if (!projectName) {
|
|
6
|
+
return undefined;
|
|
7
|
+
}
|
|
8
|
+
let typescript = cliOptions.template === 'typescript' ||
|
|
9
|
+
cliOptions.template === 'file-router' ||
|
|
10
|
+
cliOptions.framework === 'solid';
|
|
11
|
+
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;
|
|
18
|
+
const starter = cliOptions.starter
|
|
19
|
+
? await loadStarter(cliOptions.starter)
|
|
20
|
+
: undefined;
|
|
21
|
+
if (starter) {
|
|
22
|
+
tailwind = starter.tailwind;
|
|
23
|
+
typescript = starter.typescript;
|
|
24
|
+
cliOptions.framework = starter.framework;
|
|
25
|
+
mode = starter.mode;
|
|
26
|
+
}
|
|
27
|
+
const framework = getFrameworkById(cliOptions.framework || 'react-cra');
|
|
28
|
+
async function selectAddOns() {
|
|
29
|
+
// Edge case for Windows Powershell
|
|
30
|
+
if (Array.isArray(cliOptions.addOns) && cliOptions.addOns.length === 1) {
|
|
31
|
+
const parseSeparatedArgs = cliOptions.addOns[0].split(' ');
|
|
32
|
+
if (parseSeparatedArgs.length > 1) {
|
|
33
|
+
cliOptions.addOns = parseSeparatedArgs;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (Array.isArray(cliOptions.addOns) ||
|
|
37
|
+
starter?.dependsOn ||
|
|
38
|
+
forcedAddOns ||
|
|
39
|
+
cliOptions.toolchain) {
|
|
40
|
+
const selectedAddOns = new Set([
|
|
41
|
+
...(starter?.dependsOn || []),
|
|
42
|
+
...(forcedAddOns || []),
|
|
43
|
+
]);
|
|
44
|
+
if (cliOptions.addOns && Array.isArray(cliOptions.addOns)) {
|
|
45
|
+
for (const a of cliOptions.addOns) {
|
|
46
|
+
selectedAddOns.add(a);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (cliOptions.toolchain) {
|
|
50
|
+
selectedAddOns.add(cliOptions.toolchain);
|
|
51
|
+
}
|
|
52
|
+
return await finalizeAddOns(framework, mode, Array.from(selectedAddOns));
|
|
53
|
+
}
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
const chosenAddOns = await selectAddOns();
|
|
57
|
+
if (chosenAddOns.length) {
|
|
58
|
+
tailwind = true;
|
|
59
|
+
typescript = true;
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
projectName: projectName,
|
|
63
|
+
targetDir: resolve(process.cwd(), projectName),
|
|
64
|
+
framework,
|
|
65
|
+
mode,
|
|
66
|
+
typescript,
|
|
67
|
+
tailwind,
|
|
68
|
+
packageManager: cliOptions.packageManager ||
|
|
69
|
+
getPackageManager() ||
|
|
70
|
+
DEFAULT_PACKAGE_MANAGER,
|
|
71
|
+
git: !!cliOptions.git,
|
|
72
|
+
chosenAddOns,
|
|
73
|
+
starter: starter,
|
|
74
|
+
};
|
|
75
|
+
}
|
package/dist/mcp.js
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
|
|
3
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
+
import express from 'express';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { createApp, createDefaultEnvironment, finalizeAddOns, getFrameworkById, } from '@tanstack/cta-engine';
|
|
7
|
+
function createServer({ appName, forcedAddOns = [], }) {
|
|
8
|
+
const server = new McpServer({
|
|
9
|
+
name: `${appName} Application Builder`,
|
|
10
|
+
version: '1.0.0',
|
|
11
|
+
});
|
|
12
|
+
server.tool('listTanStackReactAddOns', 'List the available add-ons for creating TanStack React applications', {}, () => {
|
|
13
|
+
const framework = getFrameworkById('react-cra');
|
|
14
|
+
return {
|
|
15
|
+
content: [
|
|
16
|
+
{
|
|
17
|
+
type: 'text',
|
|
18
|
+
text: JSON.stringify(framework
|
|
19
|
+
.getAddOns()
|
|
20
|
+
.filter((addOn) => addOn.modes.includes('file-router'))
|
|
21
|
+
.map((addOn) => ({
|
|
22
|
+
id: addOn.id,
|
|
23
|
+
description: addOn.description,
|
|
24
|
+
}))),
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
};
|
|
28
|
+
});
|
|
29
|
+
server.tool('createTanStackReactApplication', 'Create a new TanStack React application', {
|
|
30
|
+
projectName: z
|
|
31
|
+
.string()
|
|
32
|
+
.describe('The package.json module name of the application (will also be the directory name)'),
|
|
33
|
+
cwd: z.string().describe('The directory to create the application in'),
|
|
34
|
+
addOns: z.array(z.string()).describe('The IDs of the add-ons to install'),
|
|
35
|
+
targetDir: z
|
|
36
|
+
.string()
|
|
37
|
+
.describe('The directory to create the application in. Use the absolute path of the directory you want the application to be created in'),
|
|
38
|
+
}, async ({ projectName, addOns, cwd, targetDir }) => {
|
|
39
|
+
const framework = getFrameworkById('react-cra');
|
|
40
|
+
try {
|
|
41
|
+
process.chdir(cwd);
|
|
42
|
+
try {
|
|
43
|
+
const chosenAddOns = await finalizeAddOns(framework, 'file-router', Array.from(new Set([
|
|
44
|
+
...addOns,
|
|
45
|
+
...forcedAddOns,
|
|
46
|
+
])));
|
|
47
|
+
await createApp(createDefaultEnvironment(), {
|
|
48
|
+
projectName: projectName.replace(/^\//, './'),
|
|
49
|
+
targetDir,
|
|
50
|
+
framework,
|
|
51
|
+
typescript: true,
|
|
52
|
+
tailwind: true,
|
|
53
|
+
packageManager: 'pnpm',
|
|
54
|
+
mode: 'file-router',
|
|
55
|
+
chosenAddOns,
|
|
56
|
+
git: true,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
console.error(error);
|
|
61
|
+
return {
|
|
62
|
+
content: [
|
|
63
|
+
{ type: 'text', text: `Error creating application: ${error}` },
|
|
64
|
+
],
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
content: [{ type: 'text', text: 'Application created successfully' }],
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
return {
|
|
73
|
+
content: [
|
|
74
|
+
{ type: 'text', text: `Error creating application: ${error}` },
|
|
75
|
+
],
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
server.tool('listTanStackSolidAddOns', 'List the available add-ons for creating TanStack Solid applications', {}, () => {
|
|
80
|
+
const framework = getFrameworkById('solid');
|
|
81
|
+
return {
|
|
82
|
+
content: [
|
|
83
|
+
{
|
|
84
|
+
type: 'text',
|
|
85
|
+
text: JSON.stringify(framework
|
|
86
|
+
.getAddOns()
|
|
87
|
+
.filter((addOn) => addOn.modes.includes('file-router'))
|
|
88
|
+
.map((addOn) => ({
|
|
89
|
+
id: addOn.id,
|
|
90
|
+
description: addOn.description,
|
|
91
|
+
}))),
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
};
|
|
95
|
+
});
|
|
96
|
+
server.tool('createTanStackSolidApplication', 'Create a new TanStack Solid application', {
|
|
97
|
+
projectName: z
|
|
98
|
+
.string()
|
|
99
|
+
.describe('The package.json module name of the application (will also be the directory name)'),
|
|
100
|
+
cwd: z.string().describe('The directory to create the application in'),
|
|
101
|
+
addOns: z.array(z.string()).describe('The IDs of the add-ons to install'),
|
|
102
|
+
targetDir: z
|
|
103
|
+
.string()
|
|
104
|
+
.describe('The directory to create the application in. Use the absolute path of the directory you want the application to be created in'),
|
|
105
|
+
}, async ({ projectName, addOns, cwd, targetDir }) => {
|
|
106
|
+
const framework = getFrameworkById('solid');
|
|
107
|
+
try {
|
|
108
|
+
process.chdir(cwd);
|
|
109
|
+
try {
|
|
110
|
+
const chosenAddOns = await finalizeAddOns(framework, 'file-router', Array.from(new Set([
|
|
111
|
+
...addOns,
|
|
112
|
+
...forcedAddOns,
|
|
113
|
+
])));
|
|
114
|
+
await createApp(createDefaultEnvironment(), {
|
|
115
|
+
projectName: projectName.replace(/^\//, './'),
|
|
116
|
+
targetDir,
|
|
117
|
+
framework,
|
|
118
|
+
typescript: true,
|
|
119
|
+
tailwind: true,
|
|
120
|
+
packageManager: 'pnpm',
|
|
121
|
+
mode: 'file-router',
|
|
122
|
+
chosenAddOns,
|
|
123
|
+
git: true,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
return {
|
|
128
|
+
content: [
|
|
129
|
+
{ type: 'text', text: `Error creating application: ${error}` },
|
|
130
|
+
],
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
content: [{ type: 'text', text: 'Application created successfully' }],
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
return {
|
|
139
|
+
content: [
|
|
140
|
+
{ type: 'text', text: `Error creating application: ${error}` },
|
|
141
|
+
],
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
return server;
|
|
146
|
+
}
|
|
147
|
+
export async function runMCPServer(sse, { forcedAddOns, appName, name, }) {
|
|
148
|
+
let transport = null;
|
|
149
|
+
const server = createServer({ appName, forcedAddOns, name });
|
|
150
|
+
if (sse) {
|
|
151
|
+
const app = express();
|
|
152
|
+
app.get('/sse', (req, res) => {
|
|
153
|
+
transport = new SSEServerTransport('/messages', res);
|
|
154
|
+
server.connect(transport);
|
|
155
|
+
});
|
|
156
|
+
app.post('/messages', (req, res) => {
|
|
157
|
+
if (transport) {
|
|
158
|
+
transport.handlePostMessage(req, res);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
const port = process.env.PORT || 8080;
|
|
162
|
+
app.listen(port, () => {
|
|
163
|
+
console.log(`Server is running on port http://localhost:${port}/sse`);
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
const transport = new StdioServerTransport();
|
|
168
|
+
await server.connect(transport);
|
|
169
|
+
}
|
|
170
|
+
}
|