@tanstack/cta-engine 0.10.0-alpha.9 → 0.11.1
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/add-ons.js +6 -6
- package/dist/cli.js +28 -22
- package/dist/create-app.js +9 -3
- package/dist/custom-add-on.js +2 -1
- package/dist/file-helper.js +18 -0
- package/dist/mcp.js +128 -112
- package/dist/options.js +9 -6
- package/dist/types/add-ons.d.ts +5 -2
- package/dist/types/cli.d.ts +2 -1
- package/dist/types/create-app.d.ts +3 -2
- package/dist/types/file-helper.d.ts +2 -0
- package/dist/types/mcp.d.ts +7 -1
- package/dist/types/types.d.ts +2 -1
- package/package.json +1 -1
- package/src/add-ons.ts +18 -9
- package/src/cli.ts +41 -30
- package/src/create-app.ts +15 -4
- package/src/custom-add-on.ts +2 -3
- package/src/file-helper.ts +20 -0
- package/src/mcp.ts +192 -144
- package/src/options.ts +16 -6
- package/src/types.ts +3 -1
- package/templates/react/add-on/tanstack-query/assets/src/integrations/tanstack-query/root-provider.tsx.ejs +2 -2
package/dist/add-ons.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
|
-
import { existsSync,
|
|
2
|
+
import { existsSync, readdirSync, statSync } from 'node:fs';
|
|
3
3
|
import { resolve } from 'node:path';
|
|
4
4
|
import chalk from 'chalk';
|
|
5
5
|
import { getTemplatesRoot } from './templates.js';
|
|
6
6
|
import { DEFAULT_FRAMEWORK } from './constants.js';
|
|
7
|
+
import { readFileHelper } from './file-helper.js';
|
|
7
8
|
function isDirectory(path) {
|
|
8
9
|
return statSync(path).isDirectory();
|
|
9
10
|
}
|
|
@@ -15,7 +16,7 @@ function findFilesRecursively(path, files) {
|
|
|
15
16
|
findFilesRecursively(filePath, files);
|
|
16
17
|
}
|
|
17
18
|
else {
|
|
18
|
-
files[filePath] =
|
|
19
|
+
files[filePath] = readFileHelper(filePath);
|
|
19
20
|
}
|
|
20
21
|
}
|
|
21
22
|
}
|
|
@@ -91,10 +92,9 @@ export async function finalizeAddOns(framework, template, chosenAddOnIDs) {
|
|
|
91
92
|
const finalAddOns = [...finalAddOnIDs].map((id) => addOns.find((a) => a.id === id));
|
|
92
93
|
return finalAddOns;
|
|
93
94
|
}
|
|
94
|
-
export async function listAddOns(options) {
|
|
95
|
-
const
|
|
96
|
-
const addOns
|
|
97
|
-
for (const addOn of addOns) {
|
|
95
|
+
export async function listAddOns(options, { forcedMode, forcedAddOns = [], }) {
|
|
96
|
+
const addOns = await getAllAddOns(options.framework || DEFAULT_FRAMEWORK, forcedMode || options.template || 'typescript');
|
|
97
|
+
for (const addOn of addOns.filter((a) => !forcedAddOns.includes(a.id))) {
|
|
98
98
|
console.log(`${chalk.bold(addOn.id)}: ${addOn.description}`);
|
|
99
99
|
}
|
|
100
100
|
}
|
package/dist/cli.js
CHANGED
|
@@ -9,11 +9,9 @@ import { listAddOns } from './add-ons.js';
|
|
|
9
9
|
import { DEFAULT_FRAMEWORK, SUPPORTED_FRAMEWORKS } from './constants.js';
|
|
10
10
|
// import { initAddOn } from './custom-add-on.js'
|
|
11
11
|
import { createDefaultEnvironment } from './environment.js';
|
|
12
|
-
export function cli({ name, forcedMode, forcedAddOns, }) {
|
|
12
|
+
export function cli({ name, appName, forcedMode, forcedAddOns, }) {
|
|
13
13
|
const program = new Command();
|
|
14
|
-
program
|
|
15
|
-
.name('create-tsrouter-app')
|
|
16
|
-
.description('CLI to create a new TanStack application');
|
|
14
|
+
program.name(name).description(`CLI to create a new ${appName} application`);
|
|
17
15
|
// program
|
|
18
16
|
// .command('add')
|
|
19
17
|
// .argument('add-on', 'Name of the add-on (or add-ons separated by commas)')
|
|
@@ -32,16 +30,25 @@ export function cli({ name, forcedMode, forcedAddOns, }) {
|
|
|
32
30
|
// .action(async () => {
|
|
33
31
|
// await initAddOn('overlay')
|
|
34
32
|
// })
|
|
33
|
+
program.argument('[project-name]', 'name of the project');
|
|
34
|
+
if (!forcedMode) {
|
|
35
|
+
program.option('--template <type>', 'project template (typescript, javascript, file-router)', (value) => {
|
|
36
|
+
if (value !== 'typescript' &&
|
|
37
|
+
value !== 'javascript' &&
|
|
38
|
+
value !== 'file-router') {
|
|
39
|
+
throw new InvalidArgumentError(`Invalid template: ${value}. Only the following are allowed: typescript, javascript, file-router`);
|
|
40
|
+
}
|
|
41
|
+
return value;
|
|
42
|
+
});
|
|
43
|
+
}
|
|
35
44
|
program
|
|
36
|
-
.argument('[project-name]', 'name of the project')
|
|
37
|
-
.option('--no-git', 'do not create a git repository')
|
|
38
|
-
.option('--target-dir <path>', 'the directory to create the project in')
|
|
39
45
|
.option('--framework <type>', 'project framework (solid, react)', (value) => {
|
|
40
46
|
if (!SUPPORTED_FRAMEWORKS.includes(value)) {
|
|
41
47
|
throw new InvalidArgumentError(`Invalid framework: ${value}. Only the following are allowed: ${SUPPORTED_FRAMEWORKS.join(', ')}`);
|
|
42
48
|
}
|
|
43
49
|
return value;
|
|
44
50
|
}, DEFAULT_FRAMEWORK)
|
|
51
|
+
// .option('--overlay [url]', 'add an overlay from a URL', false)
|
|
45
52
|
.option(`--package-manager <${SUPPORTED_PACKAGE_MANAGERS.join('|')}>`, `Explicitly tell the CLI to use this package manager`, (value) => {
|
|
46
53
|
if (!SUPPORTED_PACKAGE_MANAGERS.includes(value)) {
|
|
47
54
|
throw new InvalidArgumentError(`Invalid package manager: ${value}. The following are allowed: ${SUPPORTED_PACKAGE_MANAGERS.join(', ')}`);
|
|
@@ -63,25 +70,23 @@ export function cli({ name, forcedMode, forcedAddOns, }) {
|
|
|
63
70
|
return addOns;
|
|
64
71
|
})
|
|
65
72
|
.option('--list-add-ons', 'list all available add-ons', false)
|
|
66
|
-
|
|
73
|
+
.option('--no-git', 'do not create a git repository')
|
|
74
|
+
.option('--target-dir <path>', 'the target directory for the application root')
|
|
67
75
|
.option('--mcp', 'run the MCP server', false)
|
|
68
76
|
.option('--mcp-sse', 'run the MCP server in SSE mode', false);
|
|
69
|
-
if (!forcedMode) {
|
|
70
|
-
program.option('--template <type>', 'project template (typescript, javascript, file-router)', (value) => {
|
|
71
|
-
if (value !== 'typescript' &&
|
|
72
|
-
value !== 'javascript' &&
|
|
73
|
-
value !== 'file-router') {
|
|
74
|
-
throw new InvalidArgumentError(`Invalid template: ${value}. Only the following are allowed: typescript, javascript, file-router`);
|
|
75
|
-
}
|
|
76
|
-
return value;
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
77
|
program.action(async (projectName, options) => {
|
|
80
78
|
if (options.listAddOns) {
|
|
81
|
-
await listAddOns(options
|
|
79
|
+
await listAddOns(options, {
|
|
80
|
+
forcedMode,
|
|
81
|
+
forcedAddOns,
|
|
82
|
+
});
|
|
82
83
|
}
|
|
83
84
|
else if (options.mcp || options.mcpSse) {
|
|
84
|
-
await runServer(!!options.mcpSse
|
|
85
|
+
await runServer(!!options.mcpSse, {
|
|
86
|
+
forcedMode,
|
|
87
|
+
forcedAddOns,
|
|
88
|
+
appName,
|
|
89
|
+
});
|
|
85
90
|
}
|
|
86
91
|
else {
|
|
87
92
|
try {
|
|
@@ -94,10 +99,10 @@ export function cli({ name, forcedMode, forcedAddOns, }) {
|
|
|
94
99
|
}
|
|
95
100
|
let finalOptions = await normalizeOptions(cliOptions, forcedAddOns);
|
|
96
101
|
if (finalOptions) {
|
|
97
|
-
intro(`Creating a new
|
|
102
|
+
intro(`Creating a new ${appName} app in ${projectName}...`);
|
|
98
103
|
}
|
|
99
104
|
else {
|
|
100
|
-
intro(
|
|
105
|
+
intro(`Let's configure your ${appName} application`);
|
|
101
106
|
finalOptions = await promptForOptions(cliOptions, {
|
|
102
107
|
forcedMode,
|
|
103
108
|
forcedAddOns,
|
|
@@ -107,6 +112,7 @@ export function cli({ name, forcedMode, forcedAddOns, }) {
|
|
|
107
112
|
environment: createDefaultEnvironment(),
|
|
108
113
|
cwd: options.targetDir || undefined,
|
|
109
114
|
name,
|
|
115
|
+
appName,
|
|
110
116
|
});
|
|
111
117
|
}
|
|
112
118
|
catch (error) {
|
package/dist/create-app.js
CHANGED
|
@@ -8,6 +8,7 @@ import { CODE_ROUTER, FILE_ROUTER } from './constants.js';
|
|
|
8
8
|
import { sortObject } from './utils.js';
|
|
9
9
|
import { writeConfigFile } from './config-file.js';
|
|
10
10
|
import { packageManagerExecute } from './package-manager.js';
|
|
11
|
+
import { getBinaryFile } from './file-helper.js';
|
|
11
12
|
function createCopyFiles(environment, targetDir) {
|
|
12
13
|
return async function copyFiles(templateDir, files,
|
|
13
14
|
// optionally copy files from a folder to the root
|
|
@@ -197,6 +198,11 @@ async function copyAddOnFile(environment, content, target, targetPath, templateF
|
|
|
197
198
|
isAppend = true;
|
|
198
199
|
}
|
|
199
200
|
const finalTargetPath = resolve(dirname(targetPath), targetFile);
|
|
201
|
+
const binaryContent = getBinaryFile(content);
|
|
202
|
+
if (binaryContent) {
|
|
203
|
+
await environment.writeFile(finalTargetPath, binaryContent);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
200
206
|
if (isTemplate) {
|
|
201
207
|
await templateFile(content, finalTargetPath);
|
|
202
208
|
}
|
|
@@ -209,7 +215,7 @@ async function copyAddOnFile(environment, content, target, targetPath, templateF
|
|
|
209
215
|
}
|
|
210
216
|
}
|
|
211
217
|
}
|
|
212
|
-
export async function createApp(options, { silent = false, environment, cwd,
|
|
218
|
+
export async function createApp(options, { silent = false, environment, cwd, appName = 'TanStack', }) {
|
|
213
219
|
environment.startRun();
|
|
214
220
|
const templateDirBase = resolve(getTemplatesRoot(), options.framework, 'base');
|
|
215
221
|
const templateDirRouter = resolve(getTemplatesRoot(), options.framework, options.mode);
|
|
@@ -309,7 +315,7 @@ export async function createApp(options, { silent = false, environment, cwd, nam
|
|
|
309
315
|
// Copy all the asset files from the addons
|
|
310
316
|
const s = silent ? null : spinner();
|
|
311
317
|
for (const type of ['add-on', 'example']) {
|
|
312
|
-
for (const phase of ['setup', 'add-on']) {
|
|
318
|
+
for (const phase of ['setup', 'add-on', 'example']) {
|
|
313
319
|
for (const addOn of options.chosenAddOns.filter((addOn) => addOn.phase === phase && addOn.type === type)) {
|
|
314
320
|
s?.start(`Setting up ${addOn.name}...`);
|
|
315
321
|
await runAddOn(addOn);
|
|
@@ -480,7 +486,7 @@ ${environment.getErrors().join('\n')}`;
|
|
|
480
486
|
if (options.packageManager === 'deno') {
|
|
481
487
|
startCommand = `deno ${isAddOnEnabled('start') ? 'task dev' : 'start'}`;
|
|
482
488
|
}
|
|
483
|
-
outro(`Your
|
|
489
|
+
outro(`Your ${appName} app is ready in '${basename(targetDir)}'.
|
|
484
490
|
|
|
485
491
|
Use the following commands to start your app:
|
|
486
492
|
% cd ${options.projectName}
|
package/dist/custom-add-on.js
CHANGED
|
@@ -6,6 +6,7 @@ import { createMemoryEnvironment } from './environment.js';
|
|
|
6
6
|
import { createApp } from './create-app.js';
|
|
7
7
|
import { readConfigFile } from './config-file.js';
|
|
8
8
|
import { finalizeAddOns } from './add-ons.js';
|
|
9
|
+
import { readFileHelper } from './file-helper.js';
|
|
9
10
|
const INFO_FILE = {
|
|
10
11
|
'add-on': '.add-on/info.json',
|
|
11
12
|
overlay: 'overlay-info.json',
|
|
@@ -98,7 +99,7 @@ async function recursivelyGatherFiles(path, files) {
|
|
|
98
99
|
await recursivelyGatherFiles(resolve(path, file.name), files);
|
|
99
100
|
}
|
|
100
101
|
else {
|
|
101
|
-
files[resolve(path, file.name)] = (
|
|
102
|
+
files[resolve(path, file.name)] = readFileHelper(resolve(path, file.name));
|
|
102
103
|
}
|
|
103
104
|
}
|
|
104
105
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { extname } from 'node:path';
|
|
3
|
+
const BINARY_EXTENSIONS = ['.png', '.jpg', '.jpeg', '.gif', '.svg', '.ico'];
|
|
4
|
+
export function readFileHelper(path) {
|
|
5
|
+
if (BINARY_EXTENSIONS.includes(extname(path))) {
|
|
6
|
+
return `base64::${readFileSync(path).toString('base64')}`;
|
|
7
|
+
}
|
|
8
|
+
else {
|
|
9
|
+
return readFileSync(path, 'utf-8').toString();
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export function getBinaryFile(content) {
|
|
13
|
+
if (content.startsWith('base64::')) {
|
|
14
|
+
const binaryContent = Buffer.from(content.replace('base64::', ''), 'base64');
|
|
15
|
+
return binaryContent;
|
|
16
|
+
}
|
|
17
|
+
return null;
|
|
18
|
+
}
|
package/dist/mcp.js
CHANGED
|
@@ -6,10 +6,6 @@ import { z } from 'zod';
|
|
|
6
6
|
import { createApp } from './create-app.js';
|
|
7
7
|
import { finalizeAddOns } from './add-ons.js';
|
|
8
8
|
import { createDefaultEnvironment } from './environment.js';
|
|
9
|
-
const server = new McpServer({
|
|
10
|
-
name: 'Demo',
|
|
11
|
-
version: '1.0.0',
|
|
12
|
-
});
|
|
13
9
|
const tanStackReactAddOns = [
|
|
14
10
|
{
|
|
15
11
|
id: 'clerk',
|
|
@@ -52,63 +48,6 @@ const tanStackReactAddOns = [
|
|
|
52
48
|
description: 'Add an AI chatbot example to the application',
|
|
53
49
|
},
|
|
54
50
|
];
|
|
55
|
-
server.tool('listTanStackReactAddOns', {}, () => {
|
|
56
|
-
return {
|
|
57
|
-
content: [{ type: 'text', text: JSON.stringify(tanStackReactAddOns) }],
|
|
58
|
-
};
|
|
59
|
-
});
|
|
60
|
-
server.tool('createTanStackReactApplication', {
|
|
61
|
-
projectName: z
|
|
62
|
-
.string()
|
|
63
|
-
.describe('The package.json module name of the application (will also be the directory name)'),
|
|
64
|
-
cwd: z.string().describe('The directory to create the application in'),
|
|
65
|
-
addOns: z
|
|
66
|
-
.array(z.enum([
|
|
67
|
-
'clerk',
|
|
68
|
-
'convex',
|
|
69
|
-
'form',
|
|
70
|
-
'netlify',
|
|
71
|
-
'sentry',
|
|
72
|
-
'shadcn',
|
|
73
|
-
'start',
|
|
74
|
-
'store',
|
|
75
|
-
'tanstack-query',
|
|
76
|
-
'tanchat',
|
|
77
|
-
]))
|
|
78
|
-
.describe('The IDs of the add-ons to install'),
|
|
79
|
-
}, async ({ projectName, addOns, cwd }) => {
|
|
80
|
-
try {
|
|
81
|
-
process.chdir(cwd);
|
|
82
|
-
const chosenAddOns = await finalizeAddOns('react', 'file-router', addOns);
|
|
83
|
-
await createApp({
|
|
84
|
-
projectName: projectName.replace(/^\//, './'),
|
|
85
|
-
framework: 'react',
|
|
86
|
-
typescript: true,
|
|
87
|
-
tailwind: true,
|
|
88
|
-
packageManager: 'pnpm',
|
|
89
|
-
toolchain: 'none',
|
|
90
|
-
mode: 'file-router',
|
|
91
|
-
addOns: true,
|
|
92
|
-
chosenAddOns,
|
|
93
|
-
git: true,
|
|
94
|
-
variableValues: {},
|
|
95
|
-
}, {
|
|
96
|
-
silent: true,
|
|
97
|
-
environment: createDefaultEnvironment(),
|
|
98
|
-
name: 'create-tsrouter-app',
|
|
99
|
-
});
|
|
100
|
-
return {
|
|
101
|
-
content: [{ type: 'text', text: 'Application created successfully' }],
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
catch (error) {
|
|
105
|
-
return {
|
|
106
|
-
content: [
|
|
107
|
-
{ type: 'text', text: `Error creating application: ${error}` },
|
|
108
|
-
],
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
51
|
const tanStackSolidAddOns = [
|
|
113
52
|
{
|
|
114
53
|
id: 'solid-ui',
|
|
@@ -126,6 +65,10 @@ const tanStackSolidAddOns = [
|
|
|
126
65
|
id: 'store',
|
|
127
66
|
description: 'Enable the TanStack Store state management library',
|
|
128
67
|
},
|
|
68
|
+
{
|
|
69
|
+
id: 'start',
|
|
70
|
+
description: 'Set this if you want a TanStack Start application that supports server functions or APIs',
|
|
71
|
+
},
|
|
129
72
|
{
|
|
130
73
|
id: 'tanstack-query',
|
|
131
74
|
description: 'Enable TanStack Query for data fetching',
|
|
@@ -135,63 +78,136 @@ const tanStackSolidAddOns = [
|
|
|
135
78
|
description: 'Add an AI chatbot example to the application',
|
|
136
79
|
},
|
|
137
80
|
];
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
});
|
|
143
|
-
server.tool('
|
|
144
|
-
projectName: z
|
|
145
|
-
.string()
|
|
146
|
-
.describe('The package.json module name of the application (will also be the directory name)'),
|
|
147
|
-
cwd: z.string().describe('The directory to create the application in'),
|
|
148
|
-
addOns: z
|
|
149
|
-
.array(z.enum([
|
|
150
|
-
'solid-ui',
|
|
151
|
-
'form',
|
|
152
|
-
'sentry',
|
|
153
|
-
'store',
|
|
154
|
-
'tanstack-query',
|
|
155
|
-
'tanchat',
|
|
156
|
-
]))
|
|
157
|
-
.describe('The IDs of the add-ons to install'),
|
|
158
|
-
}, async ({ projectName, addOns, cwd }) => {
|
|
159
|
-
try {
|
|
160
|
-
process.chdir(cwd);
|
|
161
|
-
const chosenAddOns = await finalizeAddOns('solid', 'file-router', addOns);
|
|
162
|
-
await createApp({
|
|
163
|
-
projectName: projectName.replace(/^\//, './'),
|
|
164
|
-
framework: 'solid',
|
|
165
|
-
typescript: true,
|
|
166
|
-
tailwind: true,
|
|
167
|
-
packageManager: 'pnpm',
|
|
168
|
-
toolchain: 'none',
|
|
169
|
-
mode: 'file-router',
|
|
170
|
-
addOns: true,
|
|
171
|
-
chosenAddOns,
|
|
172
|
-
git: true,
|
|
173
|
-
variableValues: {},
|
|
174
|
-
}, {
|
|
175
|
-
silent: true,
|
|
176
|
-
environment: createDefaultEnvironment(),
|
|
177
|
-
name: 'create-tsrouter-app',
|
|
178
|
-
});
|
|
81
|
+
function createServer({ appName, forcedAddOns = [], name, }) {
|
|
82
|
+
const server = new McpServer({
|
|
83
|
+
name: `${appName} Application Builder`,
|
|
84
|
+
version: '1.0.0',
|
|
85
|
+
});
|
|
86
|
+
server.tool('listTanStackReactAddOns', {}, () => {
|
|
179
87
|
return {
|
|
180
|
-
content: [{ type: 'text', text:
|
|
88
|
+
content: [{ type: 'text', text: JSON.stringify(tanStackReactAddOns) }],
|
|
181
89
|
};
|
|
182
|
-
}
|
|
183
|
-
|
|
90
|
+
});
|
|
91
|
+
server.tool('createTanStackReactApplication', {
|
|
92
|
+
projectName: z
|
|
93
|
+
.string()
|
|
94
|
+
.describe('The package.json module name of the application (will also be the directory name)'),
|
|
95
|
+
cwd: z.string().describe('The directory to create the application in'),
|
|
96
|
+
addOns: z
|
|
97
|
+
.array(z.enum([
|
|
98
|
+
'clerk',
|
|
99
|
+
'convex',
|
|
100
|
+
'form',
|
|
101
|
+
'netlify',
|
|
102
|
+
'sentry',
|
|
103
|
+
'shadcn',
|
|
104
|
+
'start',
|
|
105
|
+
'store',
|
|
106
|
+
'tanstack-query',
|
|
107
|
+
'tanchat',
|
|
108
|
+
]))
|
|
109
|
+
.describe('The IDs of the add-ons to install'),
|
|
110
|
+
targetDir: z
|
|
111
|
+
.string()
|
|
112
|
+
.describe('The directory to create the application in. Use the absolute path of the directory you want the application to be created in'),
|
|
113
|
+
}, async ({ projectName, addOns, cwd, targetDir }) => {
|
|
114
|
+
try {
|
|
115
|
+
process.chdir(cwd);
|
|
116
|
+
const chosenAddOns = await finalizeAddOns('react', 'file-router', Array.from(new Set([...addOns, ...forcedAddOns])));
|
|
117
|
+
await createApp({
|
|
118
|
+
projectName: projectName.replace(/^\//, './'),
|
|
119
|
+
framework: 'react',
|
|
120
|
+
typescript: true,
|
|
121
|
+
tailwind: true,
|
|
122
|
+
packageManager: 'pnpm',
|
|
123
|
+
toolchain: 'none',
|
|
124
|
+
mode: 'file-router',
|
|
125
|
+
addOns: true,
|
|
126
|
+
chosenAddOns,
|
|
127
|
+
git: true,
|
|
128
|
+
variableValues: {},
|
|
129
|
+
}, {
|
|
130
|
+
silent: true,
|
|
131
|
+
environment: createDefaultEnvironment(),
|
|
132
|
+
name,
|
|
133
|
+
cwd: targetDir,
|
|
134
|
+
});
|
|
135
|
+
return {
|
|
136
|
+
content: [{ type: 'text', text: 'Application created successfully' }],
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
return {
|
|
141
|
+
content: [
|
|
142
|
+
{ type: 'text', text: `Error creating application: ${error}` },
|
|
143
|
+
],
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
server.tool('listTanStackSolidAddOns', {}, () => {
|
|
184
148
|
return {
|
|
185
|
-
content: [
|
|
186
|
-
{ type: 'text', text: `Error creating application: ${error}` },
|
|
187
|
-
],
|
|
149
|
+
content: [{ type: 'text', text: JSON.stringify(tanStackSolidAddOns) }],
|
|
188
150
|
};
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
|
|
151
|
+
});
|
|
152
|
+
server.tool('createTanStackSolidApplication', {
|
|
153
|
+
projectName: z
|
|
154
|
+
.string()
|
|
155
|
+
.describe('The package.json module name of the application (will also be the directory name)'),
|
|
156
|
+
cwd: z.string().describe('The directory to create the application in'),
|
|
157
|
+
addOns: z
|
|
158
|
+
.array(z.enum([
|
|
159
|
+
'solid-ui',
|
|
160
|
+
'form',
|
|
161
|
+
'sentry',
|
|
162
|
+
'store',
|
|
163
|
+
'tanstack-query',
|
|
164
|
+
'tanchat',
|
|
165
|
+
]))
|
|
166
|
+
.describe('The IDs of the add-ons to install'),
|
|
167
|
+
targetDir: z
|
|
168
|
+
.string()
|
|
169
|
+
.describe('The directory to create the application in. Use the absolute path of the directory you want the application to be created in'),
|
|
170
|
+
}, async ({ projectName, addOns, cwd, targetDir }) => {
|
|
171
|
+
try {
|
|
172
|
+
process.chdir(cwd);
|
|
173
|
+
const chosenAddOns = await finalizeAddOns('solid', 'file-router', Array.from(new Set([...addOns, ...forcedAddOns])));
|
|
174
|
+
await createApp({
|
|
175
|
+
projectName: projectName.replace(/^\//, './'),
|
|
176
|
+
framework: 'solid',
|
|
177
|
+
typescript: true,
|
|
178
|
+
tailwind: true,
|
|
179
|
+
packageManager: 'pnpm',
|
|
180
|
+
toolchain: 'none',
|
|
181
|
+
mode: 'file-router',
|
|
182
|
+
addOns: true,
|
|
183
|
+
chosenAddOns,
|
|
184
|
+
git: true,
|
|
185
|
+
variableValues: {},
|
|
186
|
+
}, {
|
|
187
|
+
silent: true,
|
|
188
|
+
environment: createDefaultEnvironment(),
|
|
189
|
+
name,
|
|
190
|
+
cwd: targetDir,
|
|
191
|
+
});
|
|
192
|
+
return {
|
|
193
|
+
content: [{ type: 'text', text: 'Application created successfully' }],
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
return {
|
|
198
|
+
content: [
|
|
199
|
+
{ type: 'text', text: `Error creating application: ${error}` },
|
|
200
|
+
],
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
return server;
|
|
205
|
+
}
|
|
206
|
+
export default async function runServer(sse, { forcedAddOns, appName, name, }) {
|
|
207
|
+
let transport = null;
|
|
208
|
+
const server = createServer({ appName, forcedAddOns, name });
|
|
192
209
|
if (sse) {
|
|
193
210
|
const app = express();
|
|
194
|
-
let transport = null;
|
|
195
211
|
app.get('/sse', (req, res) => {
|
|
196
212
|
transport = new SSEServerTransport('/messages', res);
|
|
197
213
|
server.connect(transport);
|
package/dist/options.js
CHANGED
|
@@ -13,9 +13,6 @@ export async function normalizeOptions(cliOptions, forcedAddOns) {
|
|
|
13
13
|
if (parseSeparatedArgs.length > 1) {
|
|
14
14
|
cliOptions.addOns = parseSeparatedArgs;
|
|
15
15
|
}
|
|
16
|
-
if (forcedAddOns) {
|
|
17
|
-
cliOptions.addOns = [...cliOptions.addOns, ...forcedAddOns];
|
|
18
|
-
}
|
|
19
16
|
}
|
|
20
17
|
if (cliOptions.projectName) {
|
|
21
18
|
let typescript = cliOptions.template === 'typescript' ||
|
|
@@ -37,11 +34,17 @@ export async function normalizeOptions(cliOptions, forcedAddOns) {
|
|
|
37
34
|
}
|
|
38
35
|
let addOns = false;
|
|
39
36
|
let chosenAddOns = [];
|
|
40
|
-
if (Array.isArray(cliOptions.addOns) ||
|
|
37
|
+
if (Array.isArray(cliOptions.addOns) ||
|
|
38
|
+
overlay?.dependsOn ||
|
|
39
|
+
forcedAddOns) {
|
|
41
40
|
addOns = true;
|
|
42
|
-
let finalAddOns = [...(overlay?.dependsOn || [])];
|
|
41
|
+
let finalAddOns = Array.from(new Set([...(overlay?.dependsOn || []), ...(forcedAddOns || [])]));
|
|
43
42
|
if (cliOptions.addOns && Array.isArray(cliOptions.addOns)) {
|
|
44
|
-
finalAddOns = [
|
|
43
|
+
finalAddOns = Array.from(new Set([
|
|
44
|
+
...(forcedAddOns || []),
|
|
45
|
+
...finalAddOns,
|
|
46
|
+
...cliOptions.addOns,
|
|
47
|
+
]));
|
|
45
48
|
}
|
|
46
49
|
chosenAddOns = await finalizeAddOns(cliOptions.framework || DEFAULT_FRAMEWORK, cliOptions.template === 'file-router' ? FILE_ROUTER : CODE_ROUTER, finalAddOns);
|
|
47
50
|
tailwind = true;
|
package/dist/types/add-ons.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import type { AddOn, CliOptions, Framework } from './types.js';
|
|
1
|
+
import type { AddOn, CliOptions, Framework, TemplateOptions } from './types.js';
|
|
2
2
|
export declare function getAllAddOns(framework: Framework, template: string): Promise<Array<AddOn>>;
|
|
3
3
|
export declare function finalizeAddOns(framework: Framework, template: string, chosenAddOnIDs: Array<string>): Promise<Array<AddOn>>;
|
|
4
|
-
export declare function listAddOns(options: CliOptions
|
|
4
|
+
export declare function listAddOns(options: CliOptions, { forcedMode, forcedAddOns, }: {
|
|
5
|
+
forcedMode?: TemplateOptions;
|
|
6
|
+
forcedAddOns?: Array<string>;
|
|
7
|
+
}): Promise<void>;
|
|
5
8
|
export declare function loadRemoteAddOn(url: string): Promise<AddOn>;
|
package/dist/types/cli.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
export declare function cli({ name, forcedMode, forcedAddOns, }: {
|
|
1
|
+
export declare function cli({ name, appName, forcedMode, forcedAddOns, }: {
|
|
2
2
|
name: string;
|
|
3
|
+
appName: string;
|
|
3
4
|
forcedMode?: 'typescript' | 'javascript' | 'file-router';
|
|
4
5
|
forcedAddOns?: Array<string>;
|
|
5
6
|
}): void;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { Environment, Options } from './types.js';
|
|
2
|
-
export declare function createApp(options: Options, { silent, environment, cwd,
|
|
2
|
+
export declare function createApp(options: Options, { silent, environment, cwd, appName, }: {
|
|
3
3
|
silent?: boolean;
|
|
4
4
|
environment: Environment;
|
|
5
5
|
cwd?: string;
|
|
6
|
-
name
|
|
6
|
+
name?: string;
|
|
7
|
+
appName?: string;
|
|
7
8
|
}): Promise<void>;
|
package/dist/types/mcp.d.ts
CHANGED
|
@@ -1 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
import type { TemplateOptions } from './types.js';
|
|
2
|
+
export default function runServer(sse: boolean, { forcedAddOns, appName, name, }: {
|
|
3
|
+
forcedMode?: TemplateOptions;
|
|
4
|
+
forcedAddOns?: Array<string>;
|
|
5
|
+
appName?: string;
|
|
6
|
+
name?: string;
|
|
7
|
+
}): Promise<void>;
|
package/dist/types/types.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { CODE_ROUTER, FILE_ROUTER } from './constants.js';
|
|
|
2
2
|
import type { PackageManager } from './package-manager.js';
|
|
3
3
|
import type { ToolChain } from './toolchain.js';
|
|
4
4
|
export type Framework = 'solid' | 'react';
|
|
5
|
+
export type TemplateOptions = 'typescript' | 'javascript' | 'file-router';
|
|
5
6
|
export interface Options {
|
|
6
7
|
framework: Framework;
|
|
7
8
|
projectName: string;
|
|
@@ -17,7 +18,7 @@ export interface Options {
|
|
|
17
18
|
overlay?: AddOn | undefined;
|
|
18
19
|
}
|
|
19
20
|
export interface CliOptions {
|
|
20
|
-
template?:
|
|
21
|
+
template?: TemplateOptions;
|
|
21
22
|
framework?: Framework;
|
|
22
23
|
tailwind?: boolean;
|
|
23
24
|
packageManager?: PackageManager;
|
package/package.json
CHANGED
package/src/add-ons.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises'
|
|
2
|
-
import { existsSync,
|
|
2
|
+
import { existsSync, readdirSync, statSync } from 'node:fs'
|
|
3
3
|
import { resolve } from 'node:path'
|
|
4
4
|
import chalk from 'chalk'
|
|
5
|
-
import { getTemplatesRoot } from './templates.js'
|
|
6
5
|
|
|
6
|
+
import { getTemplatesRoot } from './templates.js'
|
|
7
7
|
import { DEFAULT_FRAMEWORK } from './constants.js'
|
|
8
|
-
import
|
|
8
|
+
import { readFileHelper } from './file-helper.js'
|
|
9
|
+
|
|
10
|
+
import type { AddOn, CliOptions, Framework, TemplateOptions } from './types.js'
|
|
9
11
|
|
|
10
12
|
function isDirectory(path: string): boolean {
|
|
11
13
|
return statSync(path).isDirectory()
|
|
@@ -18,7 +20,7 @@ function findFilesRecursively(path: string, files: Record<string, string>) {
|
|
|
18
20
|
if (isDirectory(filePath)) {
|
|
19
21
|
findFilesRecursively(filePath, files)
|
|
20
22
|
} else {
|
|
21
|
-
files[filePath] =
|
|
23
|
+
files[filePath] = readFileHelper(filePath)
|
|
22
24
|
}
|
|
23
25
|
}
|
|
24
26
|
}
|
|
@@ -121,14 +123,21 @@ export async function finalizeAddOns(
|
|
|
121
123
|
return finalAddOns
|
|
122
124
|
}
|
|
123
125
|
|
|
124
|
-
export async function listAddOns(
|
|
125
|
-
|
|
126
|
-
|
|
126
|
+
export async function listAddOns(
|
|
127
|
+
options: CliOptions,
|
|
128
|
+
{
|
|
129
|
+
forcedMode,
|
|
130
|
+
forcedAddOns = [],
|
|
131
|
+
}: {
|
|
132
|
+
forcedMode?: TemplateOptions
|
|
133
|
+
forcedAddOns?: Array<string>
|
|
134
|
+
},
|
|
135
|
+
) {
|
|
127
136
|
const addOns = await getAllAddOns(
|
|
128
137
|
options.framework || DEFAULT_FRAMEWORK,
|
|
129
|
-
|
|
138
|
+
forcedMode || options.template || 'typescript',
|
|
130
139
|
)
|
|
131
|
-
for (const addOn of addOns) {
|
|
140
|
+
for (const addOn of addOns.filter((a) => !forcedAddOns.includes(a.id))) {
|
|
132
141
|
console.log(`${chalk.bold(addOn.id)}: ${addOn.description}`)
|
|
133
142
|
}
|
|
134
143
|
}
|
package/src/cli.ts
CHANGED
|
@@ -20,18 +20,18 @@ import type { CliOptions, Framework } from './types.js'
|
|
|
20
20
|
|
|
21
21
|
export function cli({
|
|
22
22
|
name,
|
|
23
|
+
appName,
|
|
23
24
|
forcedMode,
|
|
24
25
|
forcedAddOns,
|
|
25
26
|
}: {
|
|
26
27
|
name: string
|
|
28
|
+
appName: string
|
|
27
29
|
forcedMode?: 'typescript' | 'javascript' | 'file-router'
|
|
28
30
|
forcedAddOns?: Array<string>
|
|
29
31
|
}) {
|
|
30
32
|
const program = new Command()
|
|
31
33
|
|
|
32
|
-
program
|
|
33
|
-
.name('create-tsrouter-app')
|
|
34
|
-
.description('CLI to create a new TanStack application')
|
|
34
|
+
program.name(name).description(`CLI to create a new ${appName} application`)
|
|
35
35
|
|
|
36
36
|
// program
|
|
37
37
|
// .command('add')
|
|
@@ -54,10 +54,27 @@ export function cli({
|
|
|
54
54
|
// await initAddOn('overlay')
|
|
55
55
|
// })
|
|
56
56
|
|
|
57
|
+
program.argument('[project-name]', 'name of the project')
|
|
58
|
+
|
|
59
|
+
if (!forcedMode) {
|
|
60
|
+
program.option<'typescript' | 'javascript' | 'file-router'>(
|
|
61
|
+
'--template <type>',
|
|
62
|
+
'project template (typescript, javascript, file-router)',
|
|
63
|
+
(value) => {
|
|
64
|
+
if (
|
|
65
|
+
value !== 'typescript' &&
|
|
66
|
+
value !== 'javascript' &&
|
|
67
|
+
value !== 'file-router'
|
|
68
|
+
) {
|
|
69
|
+
throw new InvalidArgumentError(
|
|
70
|
+
`Invalid template: ${value}. Only the following are allowed: typescript, javascript, file-router`,
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
return value
|
|
74
|
+
},
|
|
75
|
+
)
|
|
76
|
+
}
|
|
57
77
|
program
|
|
58
|
-
.argument('[project-name]', 'name of the project')
|
|
59
|
-
.option('--no-git', 'do not create a git repository')
|
|
60
|
-
.option('--target-dir <path>', 'the directory to create the project in')
|
|
61
78
|
.option<Framework>(
|
|
62
79
|
'--framework <type>',
|
|
63
80
|
'project framework (solid, react)',
|
|
@@ -73,6 +90,7 @@ export function cli({
|
|
|
73
90
|
},
|
|
74
91
|
DEFAULT_FRAMEWORK,
|
|
75
92
|
)
|
|
93
|
+
// .option('--overlay [url]', 'add an overlay from a URL', false)
|
|
76
94
|
.option<PackageManager>(
|
|
77
95
|
`--package-manager <${SUPPORTED_PACKAGE_MANAGERS.join('|')}>`,
|
|
78
96
|
`Explicitly tell the CLI to use this package manager`,
|
|
@@ -114,34 +132,26 @@ export function cli({
|
|
|
114
132
|
},
|
|
115
133
|
)
|
|
116
134
|
.option('--list-add-ons', 'list all available add-ons', false)
|
|
117
|
-
|
|
135
|
+
.option('--no-git', 'do not create a git repository')
|
|
136
|
+
.option(
|
|
137
|
+
'--target-dir <path>',
|
|
138
|
+
'the target directory for the application root',
|
|
139
|
+
)
|
|
118
140
|
.option('--mcp', 'run the MCP server', false)
|
|
119
141
|
.option('--mcp-sse', 'run the MCP server in SSE mode', false)
|
|
120
142
|
|
|
121
|
-
if (!forcedMode) {
|
|
122
|
-
program.option<'typescript' | 'javascript' | 'file-router'>(
|
|
123
|
-
'--template <type>',
|
|
124
|
-
'project template (typescript, javascript, file-router)',
|
|
125
|
-
(value) => {
|
|
126
|
-
if (
|
|
127
|
-
value !== 'typescript' &&
|
|
128
|
-
value !== 'javascript' &&
|
|
129
|
-
value !== 'file-router'
|
|
130
|
-
) {
|
|
131
|
-
throw new InvalidArgumentError(
|
|
132
|
-
`Invalid template: ${value}. Only the following are allowed: typescript, javascript, file-router`,
|
|
133
|
-
)
|
|
134
|
-
}
|
|
135
|
-
return value
|
|
136
|
-
},
|
|
137
|
-
)
|
|
138
|
-
}
|
|
139
|
-
|
|
140
143
|
program.action(async (projectName: string, options: CliOptions) => {
|
|
141
144
|
if (options.listAddOns) {
|
|
142
|
-
await listAddOns(options
|
|
145
|
+
await listAddOns(options, {
|
|
146
|
+
forcedMode,
|
|
147
|
+
forcedAddOns,
|
|
148
|
+
})
|
|
143
149
|
} else if (options.mcp || options.mcpSse) {
|
|
144
|
-
await runServer(!!options.mcpSse
|
|
150
|
+
await runServer(!!options.mcpSse, {
|
|
151
|
+
forcedMode,
|
|
152
|
+
forcedAddOns,
|
|
153
|
+
appName,
|
|
154
|
+
})
|
|
145
155
|
} else {
|
|
146
156
|
try {
|
|
147
157
|
const cliOptions = {
|
|
@@ -155,9 +165,9 @@ export function cli({
|
|
|
155
165
|
|
|
156
166
|
let finalOptions = await normalizeOptions(cliOptions, forcedAddOns)
|
|
157
167
|
if (finalOptions) {
|
|
158
|
-
intro(`Creating a new
|
|
168
|
+
intro(`Creating a new ${appName} app in ${projectName}...`)
|
|
159
169
|
} else {
|
|
160
|
-
intro(
|
|
170
|
+
intro(`Let's configure your ${appName} application`)
|
|
161
171
|
finalOptions = await promptForOptions(cliOptions, {
|
|
162
172
|
forcedMode,
|
|
163
173
|
forcedAddOns,
|
|
@@ -167,6 +177,7 @@ export function cli({
|
|
|
167
177
|
environment: createDefaultEnvironment(),
|
|
168
178
|
cwd: options.targetDir || undefined,
|
|
169
179
|
name,
|
|
180
|
+
appName,
|
|
170
181
|
})
|
|
171
182
|
} catch (error) {
|
|
172
183
|
log.error(
|
package/src/create-app.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { CODE_ROUTER, FILE_ROUTER } from './constants.js'
|
|
|
9
9
|
import { sortObject } from './utils.js'
|
|
10
10
|
import { writeConfigFile } from './config-file.js'
|
|
11
11
|
import { packageManagerExecute } from './package-manager.js'
|
|
12
|
+
import { getBinaryFile } from './file-helper.js'
|
|
12
13
|
|
|
13
14
|
import type { AddOn, Environment, Options } from './types.js'
|
|
14
15
|
|
|
@@ -287,6 +288,15 @@ async function copyAddOnFile(
|
|
|
287
288
|
|
|
288
289
|
const finalTargetPath = resolve(dirname(targetPath), targetFile)
|
|
289
290
|
|
|
291
|
+
const binaryContent = getBinaryFile(content)
|
|
292
|
+
if (binaryContent) {
|
|
293
|
+
await environment.writeFile(
|
|
294
|
+
finalTargetPath,
|
|
295
|
+
binaryContent as unknown as string,
|
|
296
|
+
)
|
|
297
|
+
return
|
|
298
|
+
}
|
|
299
|
+
|
|
290
300
|
if (isTemplate) {
|
|
291
301
|
await templateFile(content, finalTargetPath)
|
|
292
302
|
} else {
|
|
@@ -304,12 +314,13 @@ export async function createApp(
|
|
|
304
314
|
silent = false,
|
|
305
315
|
environment,
|
|
306
316
|
cwd,
|
|
307
|
-
|
|
317
|
+
appName = 'TanStack',
|
|
308
318
|
}: {
|
|
309
319
|
silent?: boolean
|
|
310
320
|
environment: Environment
|
|
311
321
|
cwd?: string
|
|
312
|
-
name
|
|
322
|
+
name?: string
|
|
323
|
+
appName?: string
|
|
313
324
|
},
|
|
314
325
|
) {
|
|
315
326
|
environment.startRun()
|
|
@@ -498,7 +509,7 @@ export async function createApp(
|
|
|
498
509
|
// Copy all the asset files from the addons
|
|
499
510
|
const s = silent ? null : spinner()
|
|
500
511
|
for (const type of ['add-on', 'example']) {
|
|
501
|
-
for (const phase of ['setup', 'add-on']) {
|
|
512
|
+
for (const phase of ['setup', 'add-on', 'example']) {
|
|
502
513
|
for (const addOn of options.chosenAddOns.filter(
|
|
503
514
|
(addOn) => addOn.phase === phase && addOn.type === type,
|
|
504
515
|
)) {
|
|
@@ -782,7 +793,7 @@ ${environment.getErrors().join('\n')}`
|
|
|
782
793
|
startCommand = `deno ${isAddOnEnabled('start') ? 'task dev' : 'start'}`
|
|
783
794
|
}
|
|
784
795
|
|
|
785
|
-
outro(`Your
|
|
796
|
+
outro(`Your ${appName} app is ready in '${basename(targetDir)}'.
|
|
786
797
|
|
|
787
798
|
Use the following commands to start your app:
|
|
788
799
|
% cd ${options.projectName}
|
package/src/custom-add-on.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { createMemoryEnvironment } from './environment.js'
|
|
|
7
7
|
import { createApp } from './create-app.js'
|
|
8
8
|
import { readConfigFile } from './config-file.js'
|
|
9
9
|
import { finalizeAddOns } from './add-ons.js'
|
|
10
|
+
import { readFileHelper } from './file-helper.js'
|
|
10
11
|
|
|
11
12
|
import type { Framework, Options } from './types.js'
|
|
12
13
|
import type { PersistedOptions } from './config-file.js'
|
|
@@ -134,9 +135,7 @@ async function recursivelyGatherFiles(
|
|
|
134
135
|
if (file.isDirectory()) {
|
|
135
136
|
await recursivelyGatherFiles(resolve(path, file.name), files)
|
|
136
137
|
} else {
|
|
137
|
-
files[resolve(path, file.name)] = (
|
|
138
|
-
await readFile(resolve(path, file.name))
|
|
139
|
-
).toString()
|
|
138
|
+
files[resolve(path, file.name)] = readFileHelper(resolve(path, file.name))
|
|
140
139
|
}
|
|
141
140
|
}
|
|
142
141
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs'
|
|
2
|
+
import { extname } from 'node:path'
|
|
3
|
+
|
|
4
|
+
const BINARY_EXTENSIONS = ['.png', '.jpg', '.jpeg', '.gif', '.svg', '.ico']
|
|
5
|
+
|
|
6
|
+
export function readFileHelper(path: string): string {
|
|
7
|
+
if (BINARY_EXTENSIONS.includes(extname(path))) {
|
|
8
|
+
return `base64::${readFileSync(path).toString('base64')}`
|
|
9
|
+
} else {
|
|
10
|
+
return readFileSync(path, 'utf-8').toString()
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function getBinaryFile(content: string): string | null {
|
|
15
|
+
if (content.startsWith('base64::')) {
|
|
16
|
+
const binaryContent = Buffer.from(content.replace('base64::', ''), 'base64')
|
|
17
|
+
return binaryContent as unknown as string
|
|
18
|
+
}
|
|
19
|
+
return null
|
|
20
|
+
}
|
package/src/mcp.ts
CHANGED
|
@@ -8,10 +8,7 @@ import { createApp } from './create-app.js'
|
|
|
8
8
|
import { finalizeAddOns } from './add-ons.js'
|
|
9
9
|
import { createDefaultEnvironment } from './environment.js'
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
name: 'Demo',
|
|
13
|
-
version: '1.0.0',
|
|
14
|
-
})
|
|
11
|
+
import type { TemplateOptions } from './types.js'
|
|
15
12
|
|
|
16
13
|
const tanStackReactAddOns = [
|
|
17
14
|
{
|
|
@@ -57,79 +54,6 @@ const tanStackReactAddOns = [
|
|
|
57
54
|
},
|
|
58
55
|
]
|
|
59
56
|
|
|
60
|
-
server.tool('listTanStackReactAddOns', {}, () => {
|
|
61
|
-
return {
|
|
62
|
-
content: [{ type: 'text', text: JSON.stringify(tanStackReactAddOns) }],
|
|
63
|
-
}
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
server.tool(
|
|
67
|
-
'createTanStackReactApplication',
|
|
68
|
-
{
|
|
69
|
-
projectName: z
|
|
70
|
-
.string()
|
|
71
|
-
.describe(
|
|
72
|
-
'The package.json module name of the application (will also be the directory name)',
|
|
73
|
-
),
|
|
74
|
-
cwd: z.string().describe('The directory to create the application in'),
|
|
75
|
-
addOns: z
|
|
76
|
-
.array(
|
|
77
|
-
z.enum([
|
|
78
|
-
'clerk',
|
|
79
|
-
'convex',
|
|
80
|
-
'form',
|
|
81
|
-
'netlify',
|
|
82
|
-
'sentry',
|
|
83
|
-
'shadcn',
|
|
84
|
-
'start',
|
|
85
|
-
'store',
|
|
86
|
-
'tanstack-query',
|
|
87
|
-
'tanchat',
|
|
88
|
-
]),
|
|
89
|
-
)
|
|
90
|
-
.describe('The IDs of the add-ons to install'),
|
|
91
|
-
},
|
|
92
|
-
async ({ projectName, addOns, cwd }) => {
|
|
93
|
-
try {
|
|
94
|
-
process.chdir(cwd)
|
|
95
|
-
const chosenAddOns = await finalizeAddOns(
|
|
96
|
-
'react',
|
|
97
|
-
'file-router',
|
|
98
|
-
addOns as unknown as Array<string>,
|
|
99
|
-
)
|
|
100
|
-
await createApp(
|
|
101
|
-
{
|
|
102
|
-
projectName: projectName.replace(/^\//, './'),
|
|
103
|
-
framework: 'react',
|
|
104
|
-
typescript: true,
|
|
105
|
-
tailwind: true,
|
|
106
|
-
packageManager: 'pnpm',
|
|
107
|
-
toolchain: 'none',
|
|
108
|
-
mode: 'file-router',
|
|
109
|
-
addOns: true,
|
|
110
|
-
chosenAddOns,
|
|
111
|
-
git: true,
|
|
112
|
-
variableValues: {},
|
|
113
|
-
},
|
|
114
|
-
{
|
|
115
|
-
silent: true,
|
|
116
|
-
environment: createDefaultEnvironment(),
|
|
117
|
-
name: 'create-tsrouter-app',
|
|
118
|
-
},
|
|
119
|
-
)
|
|
120
|
-
return {
|
|
121
|
-
content: [{ type: 'text', text: 'Application created successfully' }],
|
|
122
|
-
}
|
|
123
|
-
} catch (error) {
|
|
124
|
-
return {
|
|
125
|
-
content: [
|
|
126
|
-
{ type: 'text', text: `Error creating application: ${error}` },
|
|
127
|
-
],
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
},
|
|
131
|
-
)
|
|
132
|
-
|
|
133
57
|
const tanStackSolidAddOns = [
|
|
134
58
|
{
|
|
135
59
|
id: 'solid-ui',
|
|
@@ -147,6 +71,11 @@ const tanStackSolidAddOns = [
|
|
|
147
71
|
id: 'store',
|
|
148
72
|
description: 'Enable the TanStack Store state management library',
|
|
149
73
|
},
|
|
74
|
+
{
|
|
75
|
+
id: 'start',
|
|
76
|
+
description:
|
|
77
|
+
'Set this if you want a TanStack Start application that supports server functions or APIs',
|
|
78
|
+
},
|
|
150
79
|
{
|
|
151
80
|
id: 'tanstack-query',
|
|
152
81
|
description: 'Enable TanStack Query for data fetching',
|
|
@@ -157,81 +86,200 @@ const tanStackSolidAddOns = [
|
|
|
157
86
|
},
|
|
158
87
|
]
|
|
159
88
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
89
|
+
function createServer({
|
|
90
|
+
appName,
|
|
91
|
+
forcedAddOns = [],
|
|
92
|
+
name,
|
|
93
|
+
}: {
|
|
94
|
+
appName?: string
|
|
95
|
+
forcedAddOns?: Array<string>
|
|
96
|
+
name?: string
|
|
97
|
+
}) {
|
|
98
|
+
const server = new McpServer({
|
|
99
|
+
name: `${appName} Application Builder`,
|
|
100
|
+
version: '1.0.0',
|
|
101
|
+
})
|
|
165
102
|
|
|
166
|
-
server.tool(
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
'
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
103
|
+
server.tool('listTanStackReactAddOns', {}, () => {
|
|
104
|
+
return {
|
|
105
|
+
content: [{ type: 'text', text: JSON.stringify(tanStackReactAddOns) }],
|
|
106
|
+
}
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
server.tool(
|
|
110
|
+
'createTanStackReactApplication',
|
|
111
|
+
{
|
|
112
|
+
projectName: z
|
|
113
|
+
.string()
|
|
114
|
+
.describe(
|
|
115
|
+
'The package.json module name of the application (will also be the directory name)',
|
|
116
|
+
),
|
|
117
|
+
cwd: z.string().describe('The directory to create the application in'),
|
|
118
|
+
addOns: z
|
|
119
|
+
.array(
|
|
120
|
+
z.enum([
|
|
121
|
+
'clerk',
|
|
122
|
+
'convex',
|
|
123
|
+
'form',
|
|
124
|
+
'netlify',
|
|
125
|
+
'sentry',
|
|
126
|
+
'shadcn',
|
|
127
|
+
'start',
|
|
128
|
+
'store',
|
|
129
|
+
'tanstack-query',
|
|
130
|
+
'tanchat',
|
|
131
|
+
]),
|
|
132
|
+
)
|
|
133
|
+
.describe('The IDs of the add-ons to install'),
|
|
134
|
+
targetDir: z
|
|
135
|
+
.string()
|
|
136
|
+
.describe(
|
|
137
|
+
'The directory to create the application in. Use the absolute path of the directory you want the application to be created in',
|
|
138
|
+
),
|
|
139
|
+
},
|
|
140
|
+
async ({ projectName, addOns, cwd, targetDir }) => {
|
|
141
|
+
try {
|
|
142
|
+
process.chdir(cwd)
|
|
143
|
+
const chosenAddOns = await finalizeAddOns(
|
|
144
|
+
'react',
|
|
145
|
+
'file-router',
|
|
146
|
+
Array.from(
|
|
147
|
+
new Set([...(addOns as unknown as Array<string>), ...forcedAddOns]),
|
|
148
|
+
),
|
|
149
|
+
)
|
|
150
|
+
await createApp(
|
|
151
|
+
{
|
|
152
|
+
projectName: projectName.replace(/^\//, './'),
|
|
153
|
+
framework: 'react',
|
|
154
|
+
typescript: true,
|
|
155
|
+
tailwind: true,
|
|
156
|
+
packageManager: 'pnpm',
|
|
157
|
+
toolchain: 'none',
|
|
158
|
+
mode: 'file-router',
|
|
159
|
+
addOns: true,
|
|
160
|
+
chosenAddOns,
|
|
161
|
+
git: true,
|
|
162
|
+
variableValues: {},
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
silent: true,
|
|
166
|
+
environment: createDefaultEnvironment(),
|
|
167
|
+
name,
|
|
168
|
+
cwd: targetDir,
|
|
169
|
+
},
|
|
170
|
+
)
|
|
171
|
+
return {
|
|
172
|
+
content: [{ type: 'text', text: 'Application created successfully' }],
|
|
173
|
+
}
|
|
174
|
+
} catch (error) {
|
|
175
|
+
return {
|
|
176
|
+
content: [
|
|
177
|
+
{ type: 'text', text: `Error creating application: ${error}` },
|
|
178
|
+
],
|
|
179
|
+
}
|
|
224
180
|
}
|
|
181
|
+
},
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
server.tool('listTanStackSolidAddOns', {}, () => {
|
|
185
|
+
return {
|
|
186
|
+
content: [{ type: 'text', text: JSON.stringify(tanStackSolidAddOns) }],
|
|
225
187
|
}
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
server.tool(
|
|
191
|
+
'createTanStackSolidApplication',
|
|
192
|
+
{
|
|
193
|
+
projectName: z
|
|
194
|
+
.string()
|
|
195
|
+
.describe(
|
|
196
|
+
'The package.json module name of the application (will also be the directory name)',
|
|
197
|
+
),
|
|
198
|
+
cwd: z.string().describe('The directory to create the application in'),
|
|
199
|
+
addOns: z
|
|
200
|
+
.array(
|
|
201
|
+
z.enum([
|
|
202
|
+
'solid-ui',
|
|
203
|
+
'form',
|
|
204
|
+
'sentry',
|
|
205
|
+
'store',
|
|
206
|
+
'tanstack-query',
|
|
207
|
+
'tanchat',
|
|
208
|
+
]),
|
|
209
|
+
)
|
|
210
|
+
.describe('The IDs of the add-ons to install'),
|
|
211
|
+
targetDir: z
|
|
212
|
+
.string()
|
|
213
|
+
.describe(
|
|
214
|
+
'The directory to create the application in. Use the absolute path of the directory you want the application to be created in',
|
|
215
|
+
),
|
|
216
|
+
},
|
|
217
|
+
async ({ projectName, addOns, cwd, targetDir }) => {
|
|
218
|
+
try {
|
|
219
|
+
process.chdir(cwd)
|
|
220
|
+
const chosenAddOns = await finalizeAddOns(
|
|
221
|
+
'solid',
|
|
222
|
+
'file-router',
|
|
223
|
+
Array.from(
|
|
224
|
+
new Set([...(addOns as unknown as Array<string>), ...forcedAddOns]),
|
|
225
|
+
),
|
|
226
|
+
)
|
|
227
|
+
await createApp(
|
|
228
|
+
{
|
|
229
|
+
projectName: projectName.replace(/^\//, './'),
|
|
230
|
+
framework: 'solid',
|
|
231
|
+
typescript: true,
|
|
232
|
+
tailwind: true,
|
|
233
|
+
packageManager: 'pnpm',
|
|
234
|
+
toolchain: 'none',
|
|
235
|
+
mode: 'file-router',
|
|
236
|
+
addOns: true,
|
|
237
|
+
chosenAddOns,
|
|
238
|
+
git: true,
|
|
239
|
+
variableValues: {},
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
silent: true,
|
|
243
|
+
environment: createDefaultEnvironment(),
|
|
244
|
+
name,
|
|
245
|
+
cwd: targetDir,
|
|
246
|
+
},
|
|
247
|
+
)
|
|
248
|
+
return {
|
|
249
|
+
content: [{ type: 'text', text: 'Application created successfully' }],
|
|
250
|
+
}
|
|
251
|
+
} catch (error) {
|
|
252
|
+
return {
|
|
253
|
+
content: [
|
|
254
|
+
{ type: 'text', text: `Error creating application: ${error}` },
|
|
255
|
+
],
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
return server
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export default async function runServer(
|
|
265
|
+
sse: boolean,
|
|
266
|
+
{
|
|
267
|
+
forcedAddOns,
|
|
268
|
+
appName,
|
|
269
|
+
name,
|
|
270
|
+
}: {
|
|
271
|
+
forcedMode?: TemplateOptions
|
|
272
|
+
forcedAddOns?: Array<string>
|
|
273
|
+
appName?: string
|
|
274
|
+
name?: string
|
|
226
275
|
},
|
|
227
|
-
)
|
|
276
|
+
) {
|
|
277
|
+
let transport: SSEServerTransport | null = null
|
|
228
278
|
|
|
229
|
-
|
|
279
|
+
const server = createServer({ appName, forcedAddOns, name })
|
|
230
280
|
if (sse) {
|
|
231
281
|
const app = express()
|
|
232
282
|
|
|
233
|
-
let transport: SSEServerTransport | null = null
|
|
234
|
-
|
|
235
283
|
app.get('/sse', (req, res) => {
|
|
236
284
|
transport = new SSEServerTransport('/messages', res)
|
|
237
285
|
server.connect(transport)
|
package/src/options.ts
CHANGED
|
@@ -31,10 +31,8 @@ export async function normalizeOptions(
|
|
|
31
31
|
if (parseSeparatedArgs.length > 1) {
|
|
32
32
|
cliOptions.addOns = parseSeparatedArgs
|
|
33
33
|
}
|
|
34
|
-
if (forcedAddOns) {
|
|
35
|
-
cliOptions.addOns = [...cliOptions.addOns, ...forcedAddOns]
|
|
36
|
-
}
|
|
37
34
|
}
|
|
35
|
+
|
|
38
36
|
if (cliOptions.projectName) {
|
|
39
37
|
let typescript =
|
|
40
38
|
cliOptions.template === 'typescript' ||
|
|
@@ -62,11 +60,23 @@ export async function normalizeOptions(
|
|
|
62
60
|
|
|
63
61
|
let addOns = false
|
|
64
62
|
let chosenAddOns: Array<AddOn> = []
|
|
65
|
-
if (
|
|
63
|
+
if (
|
|
64
|
+
Array.isArray(cliOptions.addOns) ||
|
|
65
|
+
overlay?.dependsOn ||
|
|
66
|
+
forcedAddOns
|
|
67
|
+
) {
|
|
66
68
|
addOns = true
|
|
67
|
-
let finalAddOns =
|
|
69
|
+
let finalAddOns = Array.from(
|
|
70
|
+
new Set([...(overlay?.dependsOn || []), ...(forcedAddOns || [])]),
|
|
71
|
+
)
|
|
68
72
|
if (cliOptions.addOns && Array.isArray(cliOptions.addOns)) {
|
|
69
|
-
finalAddOns =
|
|
73
|
+
finalAddOns = Array.from(
|
|
74
|
+
new Set([
|
|
75
|
+
...(forcedAddOns || []),
|
|
76
|
+
...finalAddOns,
|
|
77
|
+
...cliOptions.addOns,
|
|
78
|
+
]),
|
|
79
|
+
)
|
|
70
80
|
}
|
|
71
81
|
chosenAddOns = await finalizeAddOns(
|
|
72
82
|
cliOptions.framework || DEFAULT_FRAMEWORK,
|
package/src/types.ts
CHANGED
|
@@ -4,6 +4,8 @@ import type { ToolChain } from './toolchain.js'
|
|
|
4
4
|
|
|
5
5
|
export type Framework = 'solid' | 'react'
|
|
6
6
|
|
|
7
|
+
export type TemplateOptions = 'typescript' | 'javascript' | 'file-router'
|
|
8
|
+
|
|
7
9
|
export interface Options {
|
|
8
10
|
framework: Framework
|
|
9
11
|
projectName: string
|
|
@@ -20,7 +22,7 @@ export interface Options {
|
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
export interface CliOptions {
|
|
23
|
-
template?:
|
|
25
|
+
template?: TemplateOptions
|
|
24
26
|
framework?: Framework
|
|
25
27
|
tailwind?: boolean
|
|
26
28
|
packageManager?: PackageManager
|
|
@@ -13,7 +13,7 @@ function getUrl() {
|
|
|
13
13
|
if (typeof window !== "undefined") return "";
|
|
14
14
|
return `http://localhost:${process.env.PORT ?? 3000}`;
|
|
15
15
|
})();
|
|
16
|
-
return base
|
|
16
|
+
return `${base}/api/trpc`;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export const trpcClient = createTRPCClient<TRPCRouter>({
|
|
@@ -67,4 +67,4 @@ export function Provider({ children }: { children: React.ReactNode }) {
|
|
|
67
67
|
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
|
68
68
|
)
|
|
69
69
|
}
|
|
70
|
-
<% } %>
|
|
70
|
+
<% } %>
|