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.
Files changed (47) hide show
  1. package/README.md +8 -0
  2. package/dist/cli.js +7 -0
  3. package/dist/create-app.js +52 -4
  4. package/dist/mcp.js +19 -1
  5. package/dist/options.js +40 -19
  6. package/dist/toolchain.js +2 -0
  7. package/package.json +1 -1
  8. package/src/cli.ts +16 -0
  9. package/src/create-app.ts +65 -7
  10. package/src/mcp.ts +21 -1
  11. package/src/options.ts +40 -19
  12. package/src/toolchain.ts +3 -0
  13. package/src/types.ts +3 -0
  14. package/templates/react/add-on/form/info.json +1 -1
  15. package/templates/react/add-on/start/package.json +1 -1
  16. package/templates/react/base/README.md.ejs +10 -0
  17. package/templates/react/base/_dot_vscode/settings.biome.json +38 -0
  18. package/templates/react/base/package.biome.json +10 -0
  19. package/templates/react/base/package.json +2 -2
  20. package/templates/react/base/toolchain/biome.json +31 -0
  21. package/templates/react/code-router/src/main.tsx.ejs +2 -2
  22. package/templates/react/example/tanchat/assets/src/routes/{example.chat.tsx.ejs → example.chat.tsx} +119 -57
  23. package/templates/react/example/tanchat/assets/src/utils/demo.ai.ts +64 -60
  24. package/templates/react/example/tanchat/package.json +1 -0
  25. package/templates/react/file-router/package.fr.json +1 -1
  26. package/templates/react/file-router/src/main.tsx.ejs +2 -2
  27. package/templates/solid/add-on/form/info.json +1 -1
  28. package/templates/solid/base/_dot_vscode/settings.biome.json +38 -0
  29. package/templates/solid/base/package.biome.json +10 -0
  30. package/templates/solid/base/toolchain/biome.json +31 -0
  31. package/templates/solid/code-router/src/main.tsx.ejs +4 -2
  32. package/templates/solid/example/tanchat/README.md +52 -0
  33. package/templates/solid/example/tanchat/assets/ai-streaming-server/README.md +110 -0
  34. package/templates/solid/example/tanchat/assets/ai-streaming-server/_dot_env.example +1 -0
  35. package/templates/solid/example/tanchat/assets/ai-streaming-server/package.json +26 -0
  36. package/templates/solid/example/tanchat/assets/ai-streaming-server/src/index.ts +102 -0
  37. package/templates/solid/example/tanchat/assets/ai-streaming-server/tsconfig.json +15 -0
  38. package/templates/solid/example/tanchat/assets/src/components/demo.SettingsDialog.tsx +149 -0
  39. package/templates/solid/example/tanchat/assets/src/demo.index.css +227 -0
  40. package/templates/solid/example/tanchat/assets/src/lib/demo-store.ts +13 -0
  41. package/templates/solid/example/tanchat/assets/src/routes/example.chat.tsx +435 -0
  42. package/templates/solid/example/tanchat/assets/src/store/demo.hooks.ts +17 -0
  43. package/templates/solid/example/tanchat/assets/src/store/demo.store.ts +133 -0
  44. package/templates/solid/example/tanchat/info.json +14 -0
  45. package/templates/solid/example/tanchat/package.json +7 -0
  46. package/templates/solid/file-router/src/main.tsx.ejs +4 -2
  47. 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) => {
@@ -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
- const targetFileName = file.replace('.tw', '');
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
- await copyFile(resolve(templateDirBase, '_dot_vscode/settings.json'), resolve(targetDir, '.vscode/settings.json'));
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 tailwind
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(['solid-ui', 'form', 'sentry', 'store', 'tanstack-query']))
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 === undefined && options.framework === 'react') {
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
- const selectedExamples = [];
215
- // const examples = allAddOns.filter((addOn) => addOn.type === 'example')
216
- // if (options.typescript && examples.length > 0) {
217
- // const value = await multiselect({
218
- // message: 'Would you like any examples?',
219
- // options: examples.map((addOn) => ({
220
- // value: addOn.id,
221
- // label: addOn.name,
222
- // hint: addOn.description,
223
- // })),
224
- // required: false,
225
- // })
226
- // if (isCancel(value)) {
227
- // cancel('Operation cancelled.')
228
- // process.exit(0)
229
- // }
230
- // selectedExamples = value
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;
@@ -0,0 +1,2 @@
1
+ export const SUPPORTED_TOOLCHAINS = ['none', 'biome'];
2
+ export const DEFAULT_TOOLCHAIN = 'none';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-start-app",
3
- "version": "0.4.4",
3
+ "version": "0.6.1",
4
4
  "description": "Tanstack Application Builder",
5
5
  "bin": "./dist/index.js",
6
6
  "type": "module",
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(templateDir: string, files: Array<string>) {
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
- const targetFileName = file.replace('.tw', '')
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
- await copyFile(
286
- resolve(templateDirBase, '_dot_vscode/settings.json'),
287
- resolve(targetDir, '.vscode/settings.json'),
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 tailwind
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(z.enum(['solid-ui', 'form', 'sentry', 'store', 'tanstack-query']))
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 === undefined && options.framework === 'react') {
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
- const selectedExamples: Array<string> = []
255
- // const examples = allAddOns.filter((addOn) => addOn.type === 'example')
256
- // if (options.typescript && examples.length > 0) {
257
- // const value = await multiselect({
258
- // message: 'Would you like any examples?',
259
- // options: examples.map((addOn) => ({
260
- // value: addOn.id,
261
- // label: addOn.name,
262
- // hint: addOn.description,
263
- // })),
264
- // required: false,
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
- // if (isCancel(value)) {
268
- // cancel('Operation cancelled.')
269
- // process.exit(0)
270
- // }
271
- // selectedExamples = value
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(
@@ -0,0 +1,3 @@
1
+ export const SUPPORTED_TOOLCHAINS = ['none', 'biome'] as const
2
+ export type ToolChain = (typeof SUPPORTED_TOOLCHAINS)[number]
3
+ export const DEFAULT_TOOLCHAIN: ToolChain = 'none'
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "Form",
3
- "description": "TansStack Form",
3
+ "description": "TanStack Form",
4
4
  "phase": "add-on",
5
5
  "templates": ["file-router", "code-router"],
6
6
  "link": "https://tanstack.com/form/latest",