@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 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 { normalizeOptions, promptForOptions } from './options.js';
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
- async function listAddOns(options, { forcedMode, forcedAddOns = [], }) {
11
- const addOns = await getAllAddOns(getFrameworkById(options.framework || 'react-cra'), forcedMode || options.template || 'typescript');
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-on (or add-ons separated by commas)')
32
- .action(async (addOn) => {
33
- await addToApp(addOn.split(',').map((addon) => addon.trim()), {
34
- silent: false,
35
- }, environment);
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('update')
40
- .description('Create or update an add-on from the current project')
61
+ .command('init')
62
+ .description('Initialize an add-on from the current project')
41
63
  .action(async () => {
42
- await initAddOn('add-on', environment);
64
+ await initAddOn(environment);
43
65
  });
44
66
  addOnCommand
45
- .command('ui')
46
- .description('Show the add-on developer UI')
67
+ .command('compile')
68
+ .description('Update add-on from the current project')
47
69
  .action(async () => {
48
- launchUI();
70
+ await compileAddOn(environment);
49
71
  });
50
- program
51
- .command('update-starter')
52
- .description('Create or update a project starter from the current project')
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 initAddOn('starter', environment);
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: 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: 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: forcedMode,
187
+ forcedMode,
139
188
  forcedAddOns,
140
189
  });
141
190
  }
142
- await createApp(finalOptions, {
143
- environment: createUIEnvironment(),
144
- cwd: options.targetDir || undefined,
145
- name,
146
- appName,
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
+ }