create-start-app 0.4.4 → 0.6.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/README.md +8 -0
- package/dist/cli.js +7 -0
- package/dist/create-app.js +52 -4
- package/dist/mcp.js +19 -1
- package/dist/options.js +40 -19
- package/dist/toolchain.js +2 -0
- package/package.json +1 -1
- package/src/cli.ts +16 -0
- package/src/create-app.ts +65 -7
- package/src/mcp.ts +21 -1
- package/src/options.ts +40 -19
- package/src/toolchain.ts +3 -0
- package/src/types.ts +3 -0
- package/templates/react/add-on/form/info.json +1 -1
- package/templates/react/add-on/start/package.json +1 -1
- package/templates/react/base/README.md.ejs +10 -0
- package/templates/react/base/_dot_vscode/settings.biome.json +38 -0
- package/templates/react/base/package.biome.json +10 -0
- package/templates/react/base/package.json +2 -2
- package/templates/react/base/toolchain/biome.json +31 -0
- package/templates/react/code-router/src/main.tsx.ejs +2 -2
- package/templates/react/example/tanchat/assets/src/routes/{example.chat.tsx.ejs → example.chat.tsx} +119 -57
- package/templates/react/example/tanchat/assets/src/utils/demo.ai.ts +64 -60
- package/templates/react/example/tanchat/package.json +1 -0
- package/templates/react/file-router/package.fr.json +1 -1
- package/templates/react/file-router/src/main.tsx.ejs +2 -2
- package/templates/solid/add-on/form/info.json +1 -1
- package/templates/solid/base/_dot_vscode/settings.biome.json +38 -0
- package/templates/solid/base/package.biome.json +10 -0
- package/templates/solid/base/toolchain/biome.json +31 -0
- package/templates/solid/code-router/src/main.tsx.ejs +4 -2
- package/templates/solid/example/tanchat/README.md +52 -0
- package/templates/solid/example/tanchat/assets/ai-streaming-server/README.md +110 -0
- package/templates/solid/example/tanchat/assets/ai-streaming-server/_dot_env.example +1 -0
- package/templates/solid/example/tanchat/assets/ai-streaming-server/package.json +26 -0
- package/templates/solid/example/tanchat/assets/ai-streaming-server/src/index.ts +102 -0
- package/templates/solid/example/tanchat/assets/ai-streaming-server/tsconfig.json +15 -0
- package/templates/solid/example/tanchat/assets/src/components/demo.SettingsDialog.tsx +149 -0
- package/templates/solid/example/tanchat/assets/src/demo.index.css +227 -0
- package/templates/solid/example/tanchat/assets/src/lib/demo-store.ts +13 -0
- package/templates/solid/example/tanchat/assets/src/routes/example.chat.tsx +435 -0
- package/templates/solid/example/tanchat/assets/src/store/demo.hooks.ts +17 -0
- package/templates/solid/example/tanchat/assets/src/store/demo.store.ts +133 -0
- package/templates/solid/example/tanchat/info.json +14 -0
- package/templates/solid/example/tanchat/package.json +7 -0
- package/templates/solid/file-router/src/main.tsx.ejs +4 -2
- package/templates/solid/file-router/src/routes/__root.tsx.ejs +1 -1
package/README.md
CHANGED
|
@@ -30,6 +30,7 @@ This will start an interactive CLI that guides you through the setup process, al
|
|
|
30
30
|
- TypeScript support
|
|
31
31
|
- Tailwind CSS integration
|
|
32
32
|
- Package manager
|
|
33
|
+
- Toolchain
|
|
33
34
|
- Git initialization
|
|
34
35
|
|
|
35
36
|
## Command Line Options
|
|
@@ -45,6 +46,7 @@ Available options:
|
|
|
45
46
|
- `--template <type>`: Choose between `file-router`, `typescript`, or `javascript`
|
|
46
47
|
- `--tailwind`: Enable Tailwind CSS
|
|
47
48
|
- `--package-manager`: Specify your preferred package manager (`npm`, `yarn`, `pnpm`, `bun`, or `deno`)
|
|
49
|
+
- `--toolchain`: Specify your toolchain solution for formatting/linting (`biome`)
|
|
48
50
|
- `--no-git`: Do not initialize a git repository
|
|
49
51
|
- `--add-ons`: Enable add-on selection or specify add-ons to install
|
|
50
52
|
|
|
@@ -94,6 +96,12 @@ Choose your preferred package manager (`npm`, `bun`, `yarn`, `pnpm`, or `deno`)
|
|
|
94
96
|
|
|
95
97
|
Extensive documentation on using the TanStack Router, migrating to a File Base Routing approach, as well as integrating [@tanstack/react-query](https://tanstack.com/query/latest) and [@tanstack/store](https://tanstack.com/store/latest) can be found in the generated `README.md` for your project.
|
|
96
98
|
|
|
99
|
+
### Toolchain
|
|
100
|
+
|
|
101
|
+
Choose your preferred solution for formatting and linting either through the interactive CLI or using the `--toolchain` flag.
|
|
102
|
+
|
|
103
|
+
Setting this flag to `biome` will configure it as your toolchain of choice, adding a `biome.json` to the root of the project. Consult the [biome documentation](https://biomejs.dev/guides/getting-started/) for further customization.
|
|
104
|
+
|
|
97
105
|
## Add-ons (experimental)
|
|
98
106
|
|
|
99
107
|
You can enable add-on selection:
|
package/dist/cli.js
CHANGED
|
@@ -3,6 +3,7 @@ import { intro, log } from '@clack/prompts';
|
|
|
3
3
|
import { createApp } from './create-app.js';
|
|
4
4
|
import { normalizeOptions, promptForOptions } from './options.js';
|
|
5
5
|
import { SUPPORTED_PACKAGE_MANAGERS } from './package-manager.js';
|
|
6
|
+
import { SUPPORTED_TOOLCHAINS } from './toolchain.js';
|
|
6
7
|
import runServer from './mcp.js';
|
|
7
8
|
import { listAddOns } from './add-ons.js';
|
|
8
9
|
import { DEFAULT_FRAMEWORK, SUPPORTED_FRAMEWORKS } from './constants.js';
|
|
@@ -32,6 +33,12 @@ export function cli() {
|
|
|
32
33
|
throw new InvalidArgumentError(`Invalid package manager: ${value}. The following are allowed: ${SUPPORTED_PACKAGE_MANAGERS.join(', ')}`);
|
|
33
34
|
}
|
|
34
35
|
return value;
|
|
36
|
+
})
|
|
37
|
+
.option(`--toolchain <${SUPPORTED_TOOLCHAINS.join('|')}>`, `Explicitly tell the CLI to use this toolchain`, (value) => {
|
|
38
|
+
if (!SUPPORTED_TOOLCHAINS.includes(value)) {
|
|
39
|
+
throw new InvalidArgumentError(`Invalid toolchain: ${value}. The following are allowed: ${SUPPORTED_TOOLCHAINS.join(', ')}`);
|
|
40
|
+
}
|
|
41
|
+
return value;
|
|
35
42
|
})
|
|
36
43
|
.option('--tailwind', 'add Tailwind CSS', false)
|
|
37
44
|
.option('--add-ons [...add-ons]', 'pick from a list of available add-ons (comma separated list)', (value) => {
|
package/dist/create-app.js
CHANGED
|
@@ -18,9 +18,15 @@ function sortObject(obj) {
|
|
|
18
18
|
}, {});
|
|
19
19
|
}
|
|
20
20
|
function createCopyFiles(targetDir) {
|
|
21
|
-
return async function copyFiles(templateDir, files
|
|
21
|
+
return async function copyFiles(templateDir, files,
|
|
22
|
+
// optionally copy files from a folder to the root
|
|
23
|
+
toRoot) {
|
|
22
24
|
for (const file of files) {
|
|
23
|
-
|
|
25
|
+
let targetFileName = file.replace('.tw', '');
|
|
26
|
+
if (toRoot) {
|
|
27
|
+
const fileNoPath = targetFileName.split('/').pop();
|
|
28
|
+
targetFileName = fileNoPath ? `./${fileNoPath}` : targetFileName;
|
|
29
|
+
}
|
|
24
30
|
await copyFile(resolve(templateDir, file), resolve(targetDir, targetFileName));
|
|
25
31
|
}
|
|
26
32
|
};
|
|
@@ -38,6 +44,7 @@ function createTemplateFile(projectName, options, targetDir) {
|
|
|
38
44
|
projectName: projectName,
|
|
39
45
|
typescript: options.typescript,
|
|
40
46
|
tailwind: options.tailwind,
|
|
47
|
+
toolchain: options.toolchain,
|
|
41
48
|
js: options.typescript ? 'ts' : 'js',
|
|
42
49
|
jsx: options.typescript ? 'tsx' : 'jsx',
|
|
43
50
|
fileRouter: options.mode === FILE_ROUTER,
|
|
@@ -97,6 +104,20 @@ async function createPackageJSON(projectName, options, templateDir, routerDir, t
|
|
|
97
104
|
},
|
|
98
105
|
};
|
|
99
106
|
}
|
|
107
|
+
if (options.toolchain === 'biome') {
|
|
108
|
+
const biomePackageJSON = JSON.parse(await readFile(resolve(templateDir, 'package.biome.json'), 'utf8'));
|
|
109
|
+
packageJSON = {
|
|
110
|
+
...packageJSON,
|
|
111
|
+
scripts: {
|
|
112
|
+
...packageJSON.scripts,
|
|
113
|
+
...biomePackageJSON.scripts,
|
|
114
|
+
},
|
|
115
|
+
devDependencies: {
|
|
116
|
+
...packageJSON.devDependencies,
|
|
117
|
+
...biomePackageJSON.devDependencies,
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
}
|
|
100
121
|
if (options.mode === FILE_ROUTER) {
|
|
101
122
|
const frPackageJSON = JSON.parse(await readFile(resolve(routerDir, 'package.fr.json'), 'utf8'));
|
|
102
123
|
packageJSON = {
|
|
@@ -184,7 +205,14 @@ export async function createApp(options, { silent = false, } = {}) {
|
|
|
184
205
|
await mkdir(targetDir, { recursive: true });
|
|
185
206
|
// Setup the .vscode directory
|
|
186
207
|
await mkdir(resolve(targetDir, '.vscode'), { recursive: true });
|
|
187
|
-
|
|
208
|
+
switch (options.toolchain) {
|
|
209
|
+
case 'biome':
|
|
210
|
+
await copyFile(resolve(templateDirBase, '_dot_vscode/settings.biome.json'), resolve(targetDir, '.vscode/settings.json'));
|
|
211
|
+
break;
|
|
212
|
+
case 'none':
|
|
213
|
+
default:
|
|
214
|
+
await copyFile(resolve(templateDirBase, '_dot_vscode/settings.json'), resolve(targetDir, '.vscode/settings.json'));
|
|
215
|
+
}
|
|
188
216
|
// Fill the public directory
|
|
189
217
|
await mkdir(resolve(targetDir, 'public'), { recursive: true });
|
|
190
218
|
copyFiles(templateDirBase, [
|
|
@@ -211,6 +239,9 @@ export async function createApp(options, { silent = false, } = {}) {
|
|
|
211
239
|
await templateFile(templateDirBase, './vite.config.js.ejs');
|
|
212
240
|
await templateFile(templateDirBase, './src/styles.css.ejs');
|
|
213
241
|
copyFiles(templateDirBase, ['./src/logo.svg']);
|
|
242
|
+
if (options.toolchain === 'biome') {
|
|
243
|
+
copyFiles(templateDirBase, ['./toolchain/biome.json'], true);
|
|
244
|
+
}
|
|
214
245
|
// Setup the main, reportWebVitals and index.html files
|
|
215
246
|
if (!isAddOnEnabled('start') && options.framework === 'react') {
|
|
216
247
|
if (options.typescript) {
|
|
@@ -227,7 +258,7 @@ export async function createApp(options, { silent = false, } = {}) {
|
|
|
227
258
|
if (options.typescript) {
|
|
228
259
|
await templateFile(templateDirBase, './tsconfig.json.ejs', './tsconfig.json');
|
|
229
260
|
}
|
|
230
|
-
// Setup the package.json file, optionally with typescript and
|
|
261
|
+
// Setup the package.json file, optionally with typescript, tailwind and biome
|
|
231
262
|
await createPackageJSON(options.projectName, options, templateDirBase, templateDirRouter, targetDir, options.chosenAddOns.map((addOn) => addOn.packageAdditions));
|
|
232
263
|
// Copy all the asset files from the addons
|
|
233
264
|
const s = silent ? null : spinner();
|
|
@@ -355,6 +386,23 @@ export async function createApp(options, { silent = false, } = {}) {
|
|
|
355
386
|
log.warn(chalk.red(warnings.join('\n')));
|
|
356
387
|
}
|
|
357
388
|
}
|
|
389
|
+
if (options.toolchain === 'biome') {
|
|
390
|
+
s?.start(`Applying toolchain ${options.toolchain}...`);
|
|
391
|
+
switch (options.packageManager) {
|
|
392
|
+
case 'pnpm':
|
|
393
|
+
// pnpm automatically forwards extra arguments
|
|
394
|
+
await execa(options.packageManager, ['run', 'check', '--fix'], {
|
|
395
|
+
cwd: targetDir,
|
|
396
|
+
});
|
|
397
|
+
break;
|
|
398
|
+
default:
|
|
399
|
+
await execa(options.packageManager, ['run', 'check', '--', '--fix'], {
|
|
400
|
+
cwd: targetDir,
|
|
401
|
+
});
|
|
402
|
+
break;
|
|
403
|
+
}
|
|
404
|
+
s?.stop(`Applied toolchain ${options.toolchain}...`);
|
|
405
|
+
}
|
|
358
406
|
if (options.git) {
|
|
359
407
|
s?.start(`Initializing git repository...`);
|
|
360
408
|
await execa('git', ['init'], { cwd: targetDir });
|
package/dist/mcp.js
CHANGED
|
@@ -46,6 +46,10 @@ const tanStackReactAddOns = [
|
|
|
46
46
|
id: 'store',
|
|
47
47
|
description: 'Enable the TanStack Store state management library',
|
|
48
48
|
},
|
|
49
|
+
{
|
|
50
|
+
id: 'tanchat',
|
|
51
|
+
description: 'Add an AI chatbot example to the application',
|
|
52
|
+
},
|
|
49
53
|
];
|
|
50
54
|
server.tool('listTanStackReactAddOns', {}, () => {
|
|
51
55
|
return {
|
|
@@ -68,6 +72,7 @@ server.tool('createTanStackReactApplication', {
|
|
|
68
72
|
'start',
|
|
69
73
|
'store',
|
|
70
74
|
'tanstack-query',
|
|
75
|
+
'tanchat',
|
|
71
76
|
]))
|
|
72
77
|
.describe('The IDs of the add-ons to install'),
|
|
73
78
|
}, async ({ projectName, addOns, cwd }) => {
|
|
@@ -80,6 +85,7 @@ server.tool('createTanStackReactApplication', {
|
|
|
80
85
|
typescript: true,
|
|
81
86
|
tailwind: true,
|
|
82
87
|
packageManager: 'pnpm',
|
|
88
|
+
toolchain: 'none',
|
|
83
89
|
mode: 'file-router',
|
|
84
90
|
addOns: true,
|
|
85
91
|
chosenAddOns,
|
|
@@ -121,6 +127,10 @@ const tanStackSolidAddOns = [
|
|
|
121
127
|
id: 'tanstack-query',
|
|
122
128
|
description: 'Enable TanStack Query for data fetching',
|
|
123
129
|
},
|
|
130
|
+
{
|
|
131
|
+
id: 'tanchat',
|
|
132
|
+
description: 'Add an AI chatbot example to the application',
|
|
133
|
+
},
|
|
124
134
|
];
|
|
125
135
|
server.tool('listTanStackSolidAddOns', {}, () => {
|
|
126
136
|
return {
|
|
@@ -133,7 +143,14 @@ server.tool('createTanStackSolidApplication', {
|
|
|
133
143
|
.describe('The package.json module name of the application (will also be the directory name)'),
|
|
134
144
|
cwd: z.string().describe('The directory to create the application in'),
|
|
135
145
|
addOns: z
|
|
136
|
-
.array(z.enum([
|
|
146
|
+
.array(z.enum([
|
|
147
|
+
'solid-ui',
|
|
148
|
+
'form',
|
|
149
|
+
'sentry',
|
|
150
|
+
'store',
|
|
151
|
+
'tanstack-query',
|
|
152
|
+
'tanchat',
|
|
153
|
+
]))
|
|
137
154
|
.describe('The IDs of the add-ons to install'),
|
|
138
155
|
}, async ({ projectName, addOns, cwd }) => {
|
|
139
156
|
try {
|
|
@@ -145,6 +162,7 @@ server.tool('createTanStackSolidApplication', {
|
|
|
145
162
|
typescript: true,
|
|
146
163
|
tailwind: true,
|
|
147
164
|
packageManager: 'pnpm',
|
|
165
|
+
toolchain: 'none',
|
|
148
166
|
mode: 'file-router',
|
|
149
167
|
addOns: true,
|
|
150
168
|
chosenAddOns,
|
package/dist/options.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { cancel, confirm, isCancel, multiselect, select, text, } from '@clack/prompts';
|
|
2
2
|
import { DEFAULT_PACKAGE_MANAGER, SUPPORTED_PACKAGE_MANAGERS, getPackageManager, } from './package-manager.js';
|
|
3
|
+
import { DEFAULT_TOOLCHAIN, SUPPORTED_TOOLCHAINS } from './toolchain.js';
|
|
3
4
|
import { CODE_ROUTER, DEFAULT_FRAMEWORK, FILE_ROUTER } from './constants.js';
|
|
4
5
|
import { finalizeAddOns, getAllAddOns } from './add-ons.js';
|
|
5
6
|
// If all CLI options are provided, use them directly
|
|
@@ -26,6 +27,7 @@ export async function normalizeOptions(cliOptions) {
|
|
|
26
27
|
typescript,
|
|
27
28
|
tailwind,
|
|
28
29
|
packageManager: cliOptions.packageManager || DEFAULT_PACKAGE_MANAGER,
|
|
30
|
+
toolchain: cliOptions.toolchain || DEFAULT_TOOLCHAIN,
|
|
29
31
|
mode: cliOptions.template === 'file-router' ? FILE_ROUTER : CODE_ROUTER,
|
|
30
32
|
git: !!cliOptions.git,
|
|
31
33
|
addOns,
|
|
@@ -145,7 +147,7 @@ export async function promptForOptions(cliOptions) {
|
|
|
145
147
|
}
|
|
146
148
|
}
|
|
147
149
|
// Tailwind selection
|
|
148
|
-
if (cliOptions.tailwind
|
|
150
|
+
if (!cliOptions.tailwind && options.framework === 'react') {
|
|
149
151
|
const tailwind = await confirm({
|
|
150
152
|
message: 'Would you like to use Tailwind CSS?',
|
|
151
153
|
initialValue: true,
|
|
@@ -184,6 +186,25 @@ export async function promptForOptions(cliOptions) {
|
|
|
184
186
|
else {
|
|
185
187
|
options.packageManager = cliOptions.packageManager;
|
|
186
188
|
}
|
|
189
|
+
// Toolchain selection
|
|
190
|
+
if (cliOptions.toolchain === undefined) {
|
|
191
|
+
const tc = await select({
|
|
192
|
+
message: 'Select toolchain',
|
|
193
|
+
options: SUPPORTED_TOOLCHAINS.map((tc) => ({
|
|
194
|
+
value: tc,
|
|
195
|
+
label: tc,
|
|
196
|
+
})),
|
|
197
|
+
initialValue: DEFAULT_TOOLCHAIN,
|
|
198
|
+
});
|
|
199
|
+
if (isCancel(tc)) {
|
|
200
|
+
cancel('Operation cancelled.');
|
|
201
|
+
process.exit(0);
|
|
202
|
+
}
|
|
203
|
+
options.toolchain = tc;
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
options.toolchain = cliOptions.toolchain;
|
|
207
|
+
}
|
|
187
208
|
options.chosenAddOns = [];
|
|
188
209
|
if (Array.isArray(cliOptions.addOns)) {
|
|
189
210
|
options.chosenAddOns = await finalizeAddOns(options.framework, options.mode, cliOptions.addOns);
|
|
@@ -211,24 +232,24 @@ export async function promptForOptions(cliOptions) {
|
|
|
211
232
|
selectedAddOns = value;
|
|
212
233
|
}
|
|
213
234
|
// Select any examples
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
235
|
+
let selectedExamples = [];
|
|
236
|
+
const examples = allAddOns.filter((addOn) => addOn.type === 'example');
|
|
237
|
+
if (options.typescript && examples.length > 0) {
|
|
238
|
+
const value = await multiselect({
|
|
239
|
+
message: 'Would you like any examples?',
|
|
240
|
+
options: examples.map((addOn) => ({
|
|
241
|
+
value: addOn.id,
|
|
242
|
+
label: addOn.name,
|
|
243
|
+
hint: addOn.description,
|
|
244
|
+
})),
|
|
245
|
+
required: false,
|
|
246
|
+
});
|
|
247
|
+
if (isCancel(value)) {
|
|
248
|
+
cancel('Operation cancelled.');
|
|
249
|
+
process.exit(0);
|
|
250
|
+
}
|
|
251
|
+
selectedExamples = value;
|
|
252
|
+
}
|
|
232
253
|
if (selectedAddOns.length > 0 || selectedExamples.length > 0) {
|
|
233
254
|
options.chosenAddOns = await finalizeAddOns(options.framework, options.mode, [...selectedAddOns, ...selectedExamples]);
|
|
234
255
|
options.tailwind = true;
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -4,12 +4,14 @@ import { intro, log } from '@clack/prompts'
|
|
|
4
4
|
import { createApp } from './create-app.js'
|
|
5
5
|
import { normalizeOptions, promptForOptions } from './options.js'
|
|
6
6
|
import { SUPPORTED_PACKAGE_MANAGERS } from './package-manager.js'
|
|
7
|
+
import { SUPPORTED_TOOLCHAINS } from './toolchain.js'
|
|
7
8
|
|
|
8
9
|
import runServer from './mcp.js'
|
|
9
10
|
import { listAddOns } from './add-ons.js'
|
|
10
11
|
import { DEFAULT_FRAMEWORK, SUPPORTED_FRAMEWORKS } from './constants.js'
|
|
11
12
|
|
|
12
13
|
import type { PackageManager } from './package-manager.js'
|
|
14
|
+
import type { ToolChain } from './toolchain.js'
|
|
13
15
|
import type { CliOptions, Framework } from './types.js'
|
|
14
16
|
|
|
15
17
|
export function cli() {
|
|
@@ -65,6 +67,20 @@ export function cli() {
|
|
|
65
67
|
return value as PackageManager
|
|
66
68
|
},
|
|
67
69
|
)
|
|
70
|
+
.option<ToolChain>(
|
|
71
|
+
`--toolchain <${SUPPORTED_TOOLCHAINS.join('|')}>`,
|
|
72
|
+
`Explicitly tell the CLI to use this toolchain`,
|
|
73
|
+
(value) => {
|
|
74
|
+
if (!SUPPORTED_TOOLCHAINS.includes(value as ToolChain)) {
|
|
75
|
+
throw new InvalidArgumentError(
|
|
76
|
+
`Invalid toolchain: ${value}. The following are allowed: ${SUPPORTED_TOOLCHAINS.join(
|
|
77
|
+
', ',
|
|
78
|
+
)}`,
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
return value as ToolChain
|
|
82
|
+
},
|
|
83
|
+
)
|
|
68
84
|
.option('--tailwind', 'add Tailwind CSS', false)
|
|
69
85
|
.option<Array<string> | boolean>(
|
|
70
86
|
'--add-ons [...add-ons]',
|
package/src/create-app.ts
CHANGED
|
@@ -30,9 +30,18 @@ function sortObject(obj: Record<string, string>): Record<string, string> {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
function createCopyFiles(targetDir: string) {
|
|
33
|
-
return async function copyFiles(
|
|
33
|
+
return async function copyFiles(
|
|
34
|
+
templateDir: string,
|
|
35
|
+
files: Array<string>,
|
|
36
|
+
// optionally copy files from a folder to the root
|
|
37
|
+
toRoot?: boolean,
|
|
38
|
+
) {
|
|
34
39
|
for (const file of files) {
|
|
35
|
-
|
|
40
|
+
let targetFileName = file.replace('.tw', '')
|
|
41
|
+
if (toRoot) {
|
|
42
|
+
const fileNoPath = targetFileName.split('/').pop()
|
|
43
|
+
targetFileName = fileNoPath ? `./${fileNoPath}` : targetFileName
|
|
44
|
+
}
|
|
36
45
|
await copyFile(
|
|
37
46
|
resolve(templateDir, file),
|
|
38
47
|
resolve(targetDir, targetFileName),
|
|
@@ -64,6 +73,7 @@ function createTemplateFile(
|
|
|
64
73
|
projectName: projectName,
|
|
65
74
|
typescript: options.typescript,
|
|
66
75
|
tailwind: options.tailwind,
|
|
76
|
+
toolchain: options.toolchain,
|
|
67
77
|
js: options.typescript ? 'ts' : 'js',
|
|
68
78
|
jsx: options.typescript ? 'tsx' : 'jsx',
|
|
69
79
|
fileRouter: options.mode === FILE_ROUTER,
|
|
@@ -147,6 +157,22 @@ async function createPackageJSON(
|
|
|
147
157
|
},
|
|
148
158
|
}
|
|
149
159
|
}
|
|
160
|
+
if (options.toolchain === 'biome') {
|
|
161
|
+
const biomePackageJSON = JSON.parse(
|
|
162
|
+
await readFile(resolve(templateDir, 'package.biome.json'), 'utf8'),
|
|
163
|
+
)
|
|
164
|
+
packageJSON = {
|
|
165
|
+
...packageJSON,
|
|
166
|
+
scripts: {
|
|
167
|
+
...packageJSON.scripts,
|
|
168
|
+
...biomePackageJSON.scripts,
|
|
169
|
+
},
|
|
170
|
+
devDependencies: {
|
|
171
|
+
...packageJSON.devDependencies,
|
|
172
|
+
...biomePackageJSON.devDependencies,
|
|
173
|
+
},
|
|
174
|
+
}
|
|
175
|
+
}
|
|
150
176
|
if (options.mode === FILE_ROUTER) {
|
|
151
177
|
const frPackageJSON = JSON.parse(
|
|
152
178
|
await readFile(resolve(routerDir, 'package.fr.json'), 'utf8'),
|
|
@@ -282,10 +308,20 @@ export async function createApp(
|
|
|
282
308
|
|
|
283
309
|
// Setup the .vscode directory
|
|
284
310
|
await mkdir(resolve(targetDir, '.vscode'), { recursive: true })
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
311
|
+
switch (options.toolchain) {
|
|
312
|
+
case 'biome':
|
|
313
|
+
await copyFile(
|
|
314
|
+
resolve(templateDirBase, '_dot_vscode/settings.biome.json'),
|
|
315
|
+
resolve(targetDir, '.vscode/settings.json'),
|
|
316
|
+
)
|
|
317
|
+
break
|
|
318
|
+
case 'none':
|
|
319
|
+
default:
|
|
320
|
+
await copyFile(
|
|
321
|
+
resolve(templateDirBase, '_dot_vscode/settings.json'),
|
|
322
|
+
resolve(targetDir, '.vscode/settings.json'),
|
|
323
|
+
)
|
|
324
|
+
}
|
|
289
325
|
|
|
290
326
|
// Fill the public directory
|
|
291
327
|
await mkdir(resolve(targetDir, 'public'), { recursive: true })
|
|
@@ -321,6 +357,10 @@ export async function createApp(
|
|
|
321
357
|
|
|
322
358
|
copyFiles(templateDirBase, ['./src/logo.svg'])
|
|
323
359
|
|
|
360
|
+
if (options.toolchain === 'biome') {
|
|
361
|
+
copyFiles(templateDirBase, ['./toolchain/biome.json'], true)
|
|
362
|
+
}
|
|
363
|
+
|
|
324
364
|
// Setup the main, reportWebVitals and index.html files
|
|
325
365
|
if (!isAddOnEnabled('start') && options.framework === 'react') {
|
|
326
366
|
if (options.typescript) {
|
|
@@ -346,7 +386,7 @@ export async function createApp(
|
|
|
346
386
|
)
|
|
347
387
|
}
|
|
348
388
|
|
|
349
|
-
// Setup the package.json file, optionally with typescript and
|
|
389
|
+
// Setup the package.json file, optionally with typescript, tailwind and biome
|
|
350
390
|
await createPackageJSON(
|
|
351
391
|
options.projectName,
|
|
352
392
|
options,
|
|
@@ -567,6 +607,24 @@ export async function createApp(
|
|
|
567
607
|
}
|
|
568
608
|
}
|
|
569
609
|
|
|
610
|
+
if (options.toolchain === 'biome') {
|
|
611
|
+
s?.start(`Applying toolchain ${options.toolchain}...`)
|
|
612
|
+
switch (options.packageManager) {
|
|
613
|
+
case 'pnpm':
|
|
614
|
+
// pnpm automatically forwards extra arguments
|
|
615
|
+
await execa(options.packageManager, ['run', 'check', '--fix'], {
|
|
616
|
+
cwd: targetDir,
|
|
617
|
+
})
|
|
618
|
+
break
|
|
619
|
+
default:
|
|
620
|
+
await execa(options.packageManager, ['run', 'check', '--', '--fix'], {
|
|
621
|
+
cwd: targetDir,
|
|
622
|
+
})
|
|
623
|
+
break
|
|
624
|
+
}
|
|
625
|
+
s?.stop(`Applied toolchain ${options.toolchain}...`)
|
|
626
|
+
}
|
|
627
|
+
|
|
570
628
|
if (options.git) {
|
|
571
629
|
s?.start(`Initializing git repository...`)
|
|
572
630
|
await execa('git', ['init'], { cwd: targetDir })
|
package/src/mcp.ts
CHANGED
|
@@ -50,6 +50,10 @@ const tanStackReactAddOns = [
|
|
|
50
50
|
id: 'store',
|
|
51
51
|
description: 'Enable the TanStack Store state management library',
|
|
52
52
|
},
|
|
53
|
+
{
|
|
54
|
+
id: 'tanchat',
|
|
55
|
+
description: 'Add an AI chatbot example to the application',
|
|
56
|
+
},
|
|
53
57
|
]
|
|
54
58
|
|
|
55
59
|
server.tool('listTanStackReactAddOns', {}, () => {
|
|
@@ -79,6 +83,7 @@ server.tool(
|
|
|
79
83
|
'start',
|
|
80
84
|
'store',
|
|
81
85
|
'tanstack-query',
|
|
86
|
+
'tanchat',
|
|
82
87
|
]),
|
|
83
88
|
)
|
|
84
89
|
.describe('The IDs of the add-ons to install'),
|
|
@@ -98,6 +103,7 @@ server.tool(
|
|
|
98
103
|
typescript: true,
|
|
99
104
|
tailwind: true,
|
|
100
105
|
packageManager: 'pnpm',
|
|
106
|
+
toolchain: 'none',
|
|
101
107
|
mode: 'file-router',
|
|
102
108
|
addOns: true,
|
|
103
109
|
chosenAddOns,
|
|
@@ -142,6 +148,10 @@ const tanStackSolidAddOns = [
|
|
|
142
148
|
id: 'tanstack-query',
|
|
143
149
|
description: 'Enable TanStack Query for data fetching',
|
|
144
150
|
},
|
|
151
|
+
{
|
|
152
|
+
id: 'tanchat',
|
|
153
|
+
description: 'Add an AI chatbot example to the application',
|
|
154
|
+
},
|
|
145
155
|
]
|
|
146
156
|
|
|
147
157
|
server.tool('listTanStackSolidAddOns', {}, () => {
|
|
@@ -160,7 +170,16 @@ server.tool(
|
|
|
160
170
|
),
|
|
161
171
|
cwd: z.string().describe('The directory to create the application in'),
|
|
162
172
|
addOns: z
|
|
163
|
-
.array(
|
|
173
|
+
.array(
|
|
174
|
+
z.enum([
|
|
175
|
+
'solid-ui',
|
|
176
|
+
'form',
|
|
177
|
+
'sentry',
|
|
178
|
+
'store',
|
|
179
|
+
'tanstack-query',
|
|
180
|
+
'tanchat',
|
|
181
|
+
]),
|
|
182
|
+
)
|
|
164
183
|
.describe('The IDs of the add-ons to install'),
|
|
165
184
|
},
|
|
166
185
|
async ({ projectName, addOns, cwd }) => {
|
|
@@ -178,6 +197,7 @@ server.tool(
|
|
|
178
197
|
typescript: true,
|
|
179
198
|
tailwind: true,
|
|
180
199
|
packageManager: 'pnpm',
|
|
200
|
+
toolchain: 'none',
|
|
181
201
|
mode: 'file-router',
|
|
182
202
|
addOns: true,
|
|
183
203
|
chosenAddOns,
|
package/src/options.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
SUPPORTED_PACKAGE_MANAGERS,
|
|
13
13
|
getPackageManager,
|
|
14
14
|
} from './package-manager.js'
|
|
15
|
+
import { DEFAULT_TOOLCHAIN, SUPPORTED_TOOLCHAINS } from './toolchain.js'
|
|
15
16
|
import { CODE_ROUTER, DEFAULT_FRAMEWORK, FILE_ROUTER } from './constants.js'
|
|
16
17
|
import { finalizeAddOns, getAllAddOns } from './add-ons.js'
|
|
17
18
|
import type { AddOn, Variable } from './add-ons.js'
|
|
@@ -52,6 +53,7 @@ export async function normalizeOptions(
|
|
|
52
53
|
typescript,
|
|
53
54
|
tailwind,
|
|
54
55
|
packageManager: cliOptions.packageManager || DEFAULT_PACKAGE_MANAGER,
|
|
56
|
+
toolchain: cliOptions.toolchain || DEFAULT_TOOLCHAIN,
|
|
55
57
|
mode: cliOptions.template === 'file-router' ? FILE_ROUTER : CODE_ROUTER,
|
|
56
58
|
git: !!cliOptions.git,
|
|
57
59
|
addOns,
|
|
@@ -181,7 +183,7 @@ export async function promptForOptions(
|
|
|
181
183
|
}
|
|
182
184
|
|
|
183
185
|
// Tailwind selection
|
|
184
|
-
if (cliOptions.tailwind
|
|
186
|
+
if (!cliOptions.tailwind && options.framework === 'react') {
|
|
185
187
|
const tailwind = await confirm({
|
|
186
188
|
message: 'Would you like to use Tailwind CSS?',
|
|
187
189
|
initialValue: true,
|
|
@@ -219,6 +221,25 @@ export async function promptForOptions(
|
|
|
219
221
|
options.packageManager = cliOptions.packageManager
|
|
220
222
|
}
|
|
221
223
|
|
|
224
|
+
// Toolchain selection
|
|
225
|
+
if (cliOptions.toolchain === undefined) {
|
|
226
|
+
const tc = await select({
|
|
227
|
+
message: 'Select toolchain',
|
|
228
|
+
options: SUPPORTED_TOOLCHAINS.map((tc) => ({
|
|
229
|
+
value: tc,
|
|
230
|
+
label: tc,
|
|
231
|
+
})),
|
|
232
|
+
initialValue: DEFAULT_TOOLCHAIN,
|
|
233
|
+
})
|
|
234
|
+
if (isCancel(tc)) {
|
|
235
|
+
cancel('Operation cancelled.')
|
|
236
|
+
process.exit(0)
|
|
237
|
+
}
|
|
238
|
+
options.toolchain = tc
|
|
239
|
+
} else {
|
|
240
|
+
options.toolchain = cliOptions.toolchain
|
|
241
|
+
}
|
|
242
|
+
|
|
222
243
|
options.chosenAddOns = []
|
|
223
244
|
if (Array.isArray(cliOptions.addOns)) {
|
|
224
245
|
options.chosenAddOns = await finalizeAddOns(
|
|
@@ -251,25 +272,25 @@ export async function promptForOptions(
|
|
|
251
272
|
}
|
|
252
273
|
|
|
253
274
|
// Select any examples
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
275
|
+
let selectedExamples: Array<string> = []
|
|
276
|
+
const examples = allAddOns.filter((addOn) => addOn.type === 'example')
|
|
277
|
+
if (options.typescript && examples.length > 0) {
|
|
278
|
+
const value = await multiselect({
|
|
279
|
+
message: 'Would you like any examples?',
|
|
280
|
+
options: examples.map((addOn) => ({
|
|
281
|
+
value: addOn.id,
|
|
282
|
+
label: addOn.name,
|
|
283
|
+
hint: addOn.description,
|
|
284
|
+
})),
|
|
285
|
+
required: false,
|
|
286
|
+
})
|
|
266
287
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
288
|
+
if (isCancel(value)) {
|
|
289
|
+
cancel('Operation cancelled.')
|
|
290
|
+
process.exit(0)
|
|
291
|
+
}
|
|
292
|
+
selectedExamples = value
|
|
293
|
+
}
|
|
273
294
|
|
|
274
295
|
if (selectedAddOns.length > 0 || selectedExamples.length > 0) {
|
|
275
296
|
options.chosenAddOns = await finalizeAddOns(
|
package/src/toolchain.ts
ADDED
package/src/types.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { AddOn } from './add-ons.js'
|
|
2
2
|
import type { CODE_ROUTER, FILE_ROUTER } from './constants.js'
|
|
3
3
|
import type { PackageManager } from './package-manager.js'
|
|
4
|
+
import type { ToolChain } from './toolchain.js'
|
|
4
5
|
|
|
5
6
|
export type Framework = 'solid' | 'react'
|
|
6
7
|
|
|
@@ -10,6 +11,7 @@ export interface Options {
|
|
|
10
11
|
typescript: boolean
|
|
11
12
|
tailwind: boolean
|
|
12
13
|
packageManager: PackageManager
|
|
14
|
+
toolchain: ToolChain
|
|
13
15
|
mode: typeof CODE_ROUTER | typeof FILE_ROUTER
|
|
14
16
|
addOns: boolean
|
|
15
17
|
chosenAddOns: Array<AddOn>
|
|
@@ -22,6 +24,7 @@ export interface CliOptions {
|
|
|
22
24
|
framework?: Framework
|
|
23
25
|
tailwind?: boolean
|
|
24
26
|
packageManager?: PackageManager
|
|
27
|
+
toolchain?: ToolChain
|
|
25
28
|
projectName?: string
|
|
26
29
|
git?: boolean
|
|
27
30
|
addOns?: Array<string> | boolean
|