create-start-app 0.5.0 → 0.6.2

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 (44) hide show
  1. package/README.md +8 -0
  2. package/dist/cli.js +11 -1
  3. package/dist/create-app.js +86 -67
  4. package/dist/environment.js +32 -0
  5. package/dist/mcp.js +22 -1
  6. package/dist/options.js +22 -1
  7. package/dist/toolchain.js +2 -0
  8. package/package.json +4 -2
  9. package/src/cli.ts +21 -1
  10. package/src/create-app.ts +134 -77
  11. package/src/environment.ts +53 -0
  12. package/src/mcp.ts +24 -1
  13. package/src/options.ts +22 -1
  14. package/src/toolchain.ts +3 -0
  15. package/src/types.ts +3 -0
  16. package/templates/react/add-on/form/assets/src/components/demo.FormComponents.tsx +120 -0
  17. package/templates/react/add-on/form/assets/src/hooks/demo.form-context.ts +4 -0
  18. package/templates/react/add-on/form/assets/src/hooks/demo.form.ts +22 -0
  19. package/templates/react/add-on/form/assets/src/routes/demo.form.address.tsx.ejs +203 -0
  20. package/templates/react/add-on/form/assets/src/routes/demo.form.simple.tsx.ejs +79 -0
  21. package/templates/react/add-on/form/info.json +6 -2
  22. package/templates/react/add-on/form/package.json +2 -1
  23. package/templates/react/base/README.md.ejs +11 -1
  24. package/templates/react/base/_dot_vscode/settings.biome.json +38 -0
  25. package/templates/react/base/package.biome.json +10 -0
  26. package/templates/react/base/toolchain/biome.json +31 -0
  27. package/templates/react/code-router/src/main.tsx.ejs +2 -2
  28. package/templates/react/example/tanchat/info.json +1 -1
  29. package/templates/react/file-router/src/main.tsx.ejs +2 -2
  30. package/templates/solid/add-on/form/assets/src/routes/demo.form.tsx.ejs +310 -106
  31. package/templates/solid/add-on/form/package.json +1 -1
  32. package/templates/solid/base/_dot_vscode/settings.biome.json +38 -0
  33. package/templates/solid/base/package.biome.json +10 -0
  34. package/templates/solid/base/toolchain/biome.json +31 -0
  35. package/templates/solid/code-router/src/main.tsx.ejs +4 -2
  36. package/templates/solid/file-router/src/main.tsx.ejs +4 -2
  37. package/templates/solid/file-router/src/routes/__root.tsx.ejs +1 -1
  38. package/tests/cra.test.ts +112 -0
  39. package/tests/snapshots/cra/cr-js-npm.json +34 -0
  40. package/tests/snapshots/cra/cr-ts-npm.json +35 -0
  41. package/tests/snapshots/cra/fr-ts-npm.json +35 -0
  42. package/tests/snapshots/cra/fr-ts-tw-npm.json +34 -0
  43. package/tests/test-utilities.ts +69 -0
  44. package/templates/react/add-on/form/assets/src/routes/demo.form.tsx.ejs +0 -62
package/src/create-app.ts CHANGED
@@ -1,23 +1,13 @@
1
- #!/usr/bin/env node
2
-
3
- import {
4
- appendFile,
5
- copyFile,
6
- mkdir,
7
- readFile,
8
- writeFile,
9
- } from 'node:fs/promises'
10
- import { existsSync, readdirSync, statSync } from 'node:fs'
11
1
  import { basename, dirname, resolve } from 'node:path'
12
2
  import { fileURLToPath } from 'node:url'
13
3
  import { log, outro, spinner } from '@clack/prompts'
14
- import { execa } from 'execa'
15
4
  import { render } from 'ejs'
16
5
  import { format } from 'prettier'
17
6
  import chalk from 'chalk'
18
7
 
19
8
  import { CODE_ROUTER, FILE_ROUTER } from './constants.js'
20
9
 
10
+ import type { Environment } from './environment.js'
21
11
  import type { Options } from './types.js'
22
12
 
23
13
  function sortObject(obj: Record<string, string>): Record<string, string> {
@@ -29,11 +19,20 @@ function sortObject(obj: Record<string, string>): Record<string, string> {
29
19
  }, {})
30
20
  }
31
21
 
32
- function createCopyFiles(targetDir: string) {
33
- return async function copyFiles(templateDir: string, files: Array<string>) {
22
+ function createCopyFiles(environment: Environment, targetDir: string) {
23
+ return async function copyFiles(
24
+ templateDir: string,
25
+ files: Array<string>,
26
+ // optionally copy files from a folder to the root
27
+ toRoot?: boolean,
28
+ ) {
34
29
  for (const file of files) {
35
- const targetFileName = file.replace('.tw', '')
36
- await copyFile(
30
+ let targetFileName = file.replace('.tw', '')
31
+ if (toRoot) {
32
+ const fileNoPath = targetFileName.split('/').pop()
33
+ targetFileName = fileNoPath ? `./${fileNoPath}` : targetFileName
34
+ }
35
+ await environment.copyFile(
37
36
  resolve(templateDir, file),
38
37
  resolve(targetDir, targetFileName),
39
38
  )
@@ -49,6 +48,7 @@ function jsSafeName(name: string) {
49
48
  }
50
49
 
51
50
  function createTemplateFile(
51
+ environment: Environment,
52
52
  projectName: string,
53
53
  options: Required<Options>,
54
54
  targetDir: string,
@@ -64,6 +64,7 @@ function createTemplateFile(
64
64
  projectName: projectName,
65
65
  typescript: options.typescript,
66
66
  tailwind: options.tailwind,
67
+ toolchain: options.toolchain,
67
68
  js: options.typescript ? 'ts' : 'js',
68
69
  jsx: options.typescript ? 'tsx' : 'jsx',
69
70
  fileRouter: options.mode === FILE_ROUTER,
@@ -79,7 +80,10 @@ function createTemplateFile(
79
80
  ...extraTemplateValues,
80
81
  }
81
82
 
82
- const template = await readFile(resolve(templateDir, file), 'utf-8')
83
+ const template = await environment.readFile(
84
+ resolve(templateDir, file),
85
+ 'utf-8',
86
+ )
83
87
  let content = ''
84
88
  try {
85
89
  content = render(template, templateValues)
@@ -99,15 +103,12 @@ function createTemplateFile(
99
103
  })
100
104
  }
101
105
 
102
- await mkdir(dirname(resolve(targetDir, target)), {
103
- recursive: true,
104
- })
105
-
106
- await writeFile(resolve(targetDir, target), content)
106
+ await environment.writeFile(resolve(targetDir, target), content)
107
107
  }
108
108
  }
109
109
 
110
110
  async function createPackageJSON(
111
+ environment: Environment,
111
112
  projectName: string,
112
113
  options: Required<Options>,
113
114
  templateDir: string,
@@ -120,12 +121,15 @@ async function createPackageJSON(
120
121
  }>,
121
122
  ) {
122
123
  let packageJSON = JSON.parse(
123
- await readFile(resolve(templateDir, 'package.json'), 'utf8'),
124
+ await environment.readFile(resolve(templateDir, 'package.json'), 'utf8'),
124
125
  )
125
126
  packageJSON.name = projectName
126
127
  if (options.typescript) {
127
128
  const tsPackageJSON = JSON.parse(
128
- await readFile(resolve(templateDir, 'package.ts.json'), 'utf8'),
129
+ await environment.readFile(
130
+ resolve(templateDir, 'package.ts.json'),
131
+ 'utf8',
132
+ ),
129
133
  )
130
134
  packageJSON = {
131
135
  ...packageJSON,
@@ -137,7 +141,10 @@ async function createPackageJSON(
137
141
  }
138
142
  if (options.tailwind) {
139
143
  const twPackageJSON = JSON.parse(
140
- await readFile(resolve(templateDir, 'package.tw.json'), 'utf8'),
144
+ await environment.readFile(
145
+ resolve(templateDir, 'package.tw.json'),
146
+ 'utf8',
147
+ ),
141
148
  )
142
149
  packageJSON = {
143
150
  ...packageJSON,
@@ -147,9 +154,28 @@ async function createPackageJSON(
147
154
  },
148
155
  }
149
156
  }
157
+ if (options.toolchain === 'biome') {
158
+ const biomePackageJSON = JSON.parse(
159
+ await environment.readFile(
160
+ resolve(templateDir, 'package.biome.json'),
161
+ 'utf8',
162
+ ),
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
- await readFile(resolve(routerDir, 'package.fr.json'), 'utf8'),
178
+ await environment.readFile(resolve(routerDir, 'package.fr.json'), 'utf8'),
153
179
  )
154
180
  packageJSON = {
155
181
  ...packageJSON,
@@ -185,28 +211,27 @@ async function createPackageJSON(
185
211
  packageJSON.devDependencies as Record<string, string>,
186
212
  )
187
213
 
188
- await writeFile(
214
+ await environment.writeFile(
189
215
  resolve(targetDir, 'package.json'),
190
216
  JSON.stringify(packageJSON, null, 2),
191
217
  )
192
218
  }
193
219
 
194
220
  async function copyFilesRecursively(
221
+ environment: Environment,
195
222
  source: string,
196
223
  target: string,
197
- copyFile: (source: string, target: string) => Promise<void>,
198
224
  templateFile: (file: string, targetFileName?: string) => Promise<void>,
199
225
  ) {
200
- const sourceStat = statSync(source)
201
- if (sourceStat.isDirectory()) {
202
- const files = readdirSync(source)
226
+ if (environment.isDirectory(source)) {
227
+ const files = environment.readdir(source)
203
228
  for (const file of files) {
204
229
  const sourceChild = resolve(source, file)
205
230
  const targetChild = resolve(target, file)
206
231
  await copyFilesRecursively(
232
+ environment,
207
233
  sourceChild,
208
234
  targetChild,
209
- copyFile,
210
235
  templateFile,
211
236
  )
212
237
  }
@@ -225,17 +250,16 @@ async function copyFilesRecursively(
225
250
 
226
251
  const targetPath = resolve(dirname(target), targetFile)
227
252
 
228
- await mkdir(dirname(targetPath), {
229
- recursive: true,
230
- })
231
-
232
253
  if (isTemplate) {
233
254
  await templateFile(source, targetPath)
234
255
  } else {
235
256
  if (isAppend) {
236
- await appendFile(targetPath, (await readFile(source)).toString())
257
+ await environment.appendFile(
258
+ targetPath,
259
+ (await environment.readFile(source)).toString(),
260
+ )
237
261
  } else {
238
- await copyFile(source, targetPath)
262
+ await environment.copyFile(source, targetPath)
239
263
  }
240
264
  }
241
265
  }
@@ -245,9 +269,11 @@ export async function createApp(
245
269
  options: Required<Options>,
246
270
  {
247
271
  silent = false,
272
+ environment,
248
273
  }: {
249
274
  silent?: boolean
250
- } = {},
275
+ environment: Environment
276
+ },
251
277
  ) {
252
278
  const templateDirBase = fileURLToPath(
253
279
  new URL(`../templates/${options.framework}/base`, import.meta.url),
@@ -260,15 +286,16 @@ export async function createApp(
260
286
  )
261
287
  const targetDir = resolve(process.cwd(), options.projectName)
262
288
 
263
- if (existsSync(targetDir)) {
289
+ if (environment.exists(targetDir)) {
264
290
  if (!silent) {
265
291
  log.error(`Directory "${options.projectName}" already exists`)
266
292
  }
267
293
  return
268
294
  }
269
295
 
270
- const copyFiles = createCopyFiles(targetDir)
296
+ const copyFiles = createCopyFiles(environment, targetDir)
271
297
  const templateFile = createTemplateFile(
298
+ environment,
272
299
  options.projectName,
273
300
  options,
274
301
  targetDir,
@@ -277,18 +304,23 @@ export async function createApp(
277
304
  const isAddOnEnabled = (id: string) =>
278
305
  options.chosenAddOns.find((a) => a.id === id)
279
306
 
280
- // Make the root directory
281
- await mkdir(targetDir, { recursive: true })
282
-
283
307
  // Setup the .vscode directory
284
- await mkdir(resolve(targetDir, '.vscode'), { recursive: true })
285
- await copyFile(
286
- resolve(templateDirBase, '_dot_vscode/settings.json'),
287
- resolve(targetDir, '.vscode/settings.json'),
288
- )
308
+ switch (options.toolchain) {
309
+ case 'biome':
310
+ await environment.copyFile(
311
+ resolve(templateDirBase, '_dot_vscode/settings.biome.json'),
312
+ resolve(targetDir, '.vscode/settings.json'),
313
+ )
314
+ break
315
+ case 'none':
316
+ default:
317
+ await environment.copyFile(
318
+ resolve(templateDirBase, '_dot_vscode/settings.json'),
319
+ resolve(targetDir, '.vscode/settings.json'),
320
+ )
321
+ }
289
322
 
290
323
  // Fill the public directory
291
- await mkdir(resolve(targetDir, 'public'), { recursive: true })
292
324
  copyFiles(templateDirBase, [
293
325
  './public/robots.txt',
294
326
  './public/favicon.ico',
@@ -297,16 +329,9 @@ export async function createApp(
297
329
  './public/logo512.png',
298
330
  ])
299
331
 
300
- // Make the src directory
301
- await mkdir(resolve(targetDir, 'src'), { recursive: true })
302
- if (options.mode === FILE_ROUTER) {
303
- await mkdir(resolve(targetDir, 'src/routes'), { recursive: true })
304
- await mkdir(resolve(targetDir, 'src/components'), { recursive: true })
305
- }
306
-
307
332
  // Check for a .cursorrules file
308
- if (existsSync(resolve(templateDirBase, '.cursorrules'))) {
309
- await copyFile(
333
+ if (environment.exists(resolve(templateDirBase, '.cursorrules'))) {
334
+ await environment.copyFile(
310
335
  resolve(templateDirBase, '.cursorrules'),
311
336
  resolve(targetDir, '.cursorrules'),
312
337
  )
@@ -321,6 +346,10 @@ export async function createApp(
321
346
 
322
347
  copyFiles(templateDirBase, ['./src/logo.svg'])
323
348
 
349
+ if (options.toolchain === 'biome') {
350
+ copyFiles(templateDirBase, ['./toolchain/biome.json'], true)
351
+ }
352
+
324
353
  // Setup the main, reportWebVitals and index.html files
325
354
  if (!isAddOnEnabled('start') && options.framework === 'react') {
326
355
  if (options.typescript) {
@@ -346,8 +375,9 @@ export async function createApp(
346
375
  )
347
376
  }
348
377
 
349
- // Setup the package.json file, optionally with typescript and tailwind
378
+ // Setup the package.json file, optionally with typescript, tailwind and biome
350
379
  await createPackageJSON(
380
+ environment,
351
381
  options.projectName,
352
382
  options,
353
383
  templateDirBase,
@@ -364,21 +394,24 @@ export async function createApp(
364
394
  )) {
365
395
  s?.start(`Setting up ${addOn.name}...`)
366
396
  const addOnDir = resolve(addOn.directory, 'assets')
367
- if (existsSync(addOnDir)) {
397
+ if (environment.exists(addOnDir)) {
368
398
  await copyFilesRecursively(
399
+ environment,
369
400
  addOnDir,
370
401
  targetDir,
371
- copyFile,
372
402
  async (file: string, targetFileName?: string) =>
373
403
  templateFile(addOnDir, file, targetFileName),
374
404
  )
375
405
  }
376
406
 
377
407
  if (addOn.command) {
378
- await execa(addOn.command.command, addOn.command.args || [], {
379
- cwd: targetDir,
380
- })
408
+ await environment.execute(
409
+ addOn.command.command,
410
+ addOn.command.args || [],
411
+ targetDir,
412
+ )
381
413
  }
414
+
382
415
  s?.stop(`${addOn.name} setup complete`)
383
416
  }
384
417
  }
@@ -397,9 +430,11 @@ export async function createApp(
397
430
  s?.start(
398
431
  `Installing shadcn components (${Array.from(shadcnComponents).join(', ')})...`,
399
432
  )
400
- await execa('npx', ['shadcn@canary', 'add', ...shadcnComponents], {
401
- cwd: targetDir,
402
- })
433
+ await environment.execute(
434
+ 'npx',
435
+ ['shadcn@canary', 'add', ...shadcnComponents],
436
+ targetDir,
437
+ )
403
438
  s?.stop(`Installed shadcn components`)
404
439
  }
405
440
  }
@@ -409,13 +444,13 @@ export async function createApp(
409
444
  name: string
410
445
  path: string
411
446
  }> = []
412
- if (existsSync(resolve(targetDir, 'src/integrations'))) {
413
- for (const integration of readdirSync(
447
+ if (environment.exists(resolve(targetDir, 'src/integrations'))) {
448
+ for (const integration of environment.readdir(
414
449
  resolve(targetDir, 'src/integrations'),
415
450
  )) {
416
451
  const integrationName = jsSafeName(integration)
417
452
  if (
418
- existsSync(
453
+ environment.exists(
419
454
  resolve(targetDir, 'src/integrations', integration, 'layout.tsx'),
420
455
  )
421
456
  ) {
@@ -426,7 +461,7 @@ export async function createApp(
426
461
  })
427
462
  }
428
463
  if (
429
- existsSync(
464
+ environment.exists(
430
465
  resolve(targetDir, 'src/integrations', integration, 'provider.tsx'),
431
466
  )
432
467
  ) {
@@ -437,7 +472,7 @@ export async function createApp(
437
472
  })
438
473
  }
439
474
  if (
440
- existsSync(
475
+ environment.exists(
441
476
  resolve(
442
477
  targetDir,
443
478
  'src/integrations',
@@ -459,8 +494,8 @@ export async function createApp(
459
494
  path: string
460
495
  name: string
461
496
  }> = []
462
- if (existsSync(resolve(targetDir, 'src/routes'))) {
463
- for (const file of readdirSync(resolve(targetDir, 'src/routes'))) {
497
+ if (environment.exists(resolve(targetDir, 'src/routes'))) {
498
+ for (const file of environment.readdir(resolve(targetDir, 'src/routes'))) {
464
499
  const name = file.replace(/\.tsx?|\.jsx?/, '')
465
500
  const safeRouteName = jsSafeName(name)
466
501
  routes.push({
@@ -548,7 +583,7 @@ export async function createApp(
548
583
  }
549
584
 
550
585
  // Add .gitignore
551
- await copyFile(
586
+ await environment.copyFile(
552
587
  resolve(templateDirBase, '_dot_gitignore'),
553
588
  resolve(targetDir, '.gitignore'),
554
589
  )
@@ -558,7 +593,7 @@ export async function createApp(
558
593
 
559
594
  // Install dependencies
560
595
  s?.start(`Installing dependencies via ${options.packageManager}...`)
561
- await execa(options.packageManager, ['install'], { cwd: targetDir })
596
+ await environment.execute(options.packageManager, ['install'], targetDir)
562
597
  s?.stop(`Installed dependencies`)
563
598
 
564
599
  if (warnings.length > 0) {
@@ -567,9 +602,31 @@ export async function createApp(
567
602
  }
568
603
  }
569
604
 
605
+ if (options.toolchain === 'biome') {
606
+ s?.start(`Applying toolchain ${options.toolchain}...`)
607
+ switch (options.packageManager) {
608
+ case 'pnpm':
609
+ // pnpm automatically forwards extra arguments
610
+ await environment.execute(
611
+ options.packageManager,
612
+ ['run', 'check', '--fix'],
613
+ targetDir,
614
+ )
615
+ break
616
+ default:
617
+ await environment.execute(
618
+ options.packageManager,
619
+ ['run', 'check', '--', '--fix'],
620
+ targetDir,
621
+ )
622
+ break
623
+ }
624
+ s?.stop(`Applied toolchain ${options.toolchain}...`)
625
+ }
626
+
570
627
  if (options.git) {
571
628
  s?.start(`Initializing git repository...`)
572
- await execa('git', ['init'], { cwd: targetDir })
629
+ await environment.execute('git', ['init'], targetDir)
573
630
  s?.stop(`Initialized git repository`)
574
631
  }
575
632
 
@@ -0,0 +1,53 @@
1
+ import {
2
+ appendFile,
3
+ copyFile,
4
+ mkdir,
5
+ readFile,
6
+ writeFile,
7
+ } from 'node:fs/promises'
8
+ import { existsSync, readdirSync, statSync } from 'node:fs'
9
+ import { dirname } from 'node:path'
10
+ import { execa } from 'execa'
11
+
12
+ export type Environment = {
13
+ appendFile: (path: string, contents: string) => Promise<void>
14
+ copyFile: (from: string, to: string) => Promise<void>
15
+ writeFile: (path: string, contents: string) => Promise<void>
16
+ execute: (command: string, args: Array<string>, cwd: string) => Promise<void>
17
+
18
+ readFile: (path: string, encoding?: BufferEncoding) => Promise<string>
19
+ exists: (path: string) => boolean
20
+ readdir: (path: string) => Array<string>
21
+ isDirectory: (path: string) => boolean
22
+ }
23
+
24
+ export function createDefaultEnvironment(): Environment {
25
+ return {
26
+ appendFile: async (path: string, contents: string) => {
27
+ await mkdir(dirname(path), { recursive: true })
28
+ return appendFile(path, contents)
29
+ },
30
+ copyFile: async (from: string, to: string) => {
31
+ await mkdir(dirname(to), { recursive: true })
32
+ return copyFile(from, to)
33
+ },
34
+ writeFile: async (path: string, contents: string) => {
35
+ await mkdir(dirname(path), { recursive: true })
36
+ return writeFile(path, contents)
37
+ },
38
+ execute: async (command: string, args: Array<string>, cwd: string) => {
39
+ await execa(command, args, {
40
+ cwd,
41
+ })
42
+ },
43
+
44
+ readFile: (path: string, encoding?: BufferEncoding) =>
45
+ readFile(path, { encoding: encoding || 'utf8' }),
46
+ exists: (path: string) => existsSync(path),
47
+ readdir: (path) => readdirSync(path),
48
+ isDirectory: (path) => {
49
+ const stat = statSync(path)
50
+ return stat.isDirectory()
51
+ },
52
+ }
53
+ }
package/src/mcp.ts CHANGED
@@ -6,6 +6,7 @@ import { z } from 'zod'
6
6
 
7
7
  import { createApp } from './create-app.js'
8
8
  import { finalizeAddOns } from './add-ons.js'
9
+ import { createDefaultEnvironment } from './environment.js'
9
10
 
10
11
  const server = new McpServer({
11
12
  name: 'Demo',
@@ -50,6 +51,10 @@ const tanStackReactAddOns = [
50
51
  id: 'store',
51
52
  description: 'Enable the TanStack Store state management library',
52
53
  },
54
+ {
55
+ id: 'tanchat',
56
+ description: 'Add an AI chatbot example to the application',
57
+ },
53
58
  ]
54
59
 
55
60
  server.tool('listTanStackReactAddOns', {}, () => {
@@ -79,6 +84,7 @@ server.tool(
79
84
  'start',
80
85
  'store',
81
86
  'tanstack-query',
87
+ 'tanchat',
82
88
  ]),
83
89
  )
84
90
  .describe('The IDs of the add-ons to install'),
@@ -98,6 +104,7 @@ server.tool(
98
104
  typescript: true,
99
105
  tailwind: true,
100
106
  packageManager: 'pnpm',
107
+ toolchain: 'none',
101
108
  mode: 'file-router',
102
109
  addOns: true,
103
110
  chosenAddOns,
@@ -106,6 +113,7 @@ server.tool(
106
113
  },
107
114
  {
108
115
  silent: true,
116
+ environment: createDefaultEnvironment(),
109
117
  },
110
118
  )
111
119
  return {
@@ -142,6 +150,10 @@ const tanStackSolidAddOns = [
142
150
  id: 'tanstack-query',
143
151
  description: 'Enable TanStack Query for data fetching',
144
152
  },
153
+ {
154
+ id: 'tanchat',
155
+ description: 'Add an AI chatbot example to the application',
156
+ },
145
157
  ]
146
158
 
147
159
  server.tool('listTanStackSolidAddOns', {}, () => {
@@ -160,7 +172,16 @@ server.tool(
160
172
  ),
161
173
  cwd: z.string().describe('The directory to create the application in'),
162
174
  addOns: z
163
- .array(z.enum(['solid-ui', 'form', 'sentry', 'store', 'tanstack-query']))
175
+ .array(
176
+ z.enum([
177
+ 'solid-ui',
178
+ 'form',
179
+ 'sentry',
180
+ 'store',
181
+ 'tanstack-query',
182
+ 'tanchat',
183
+ ]),
184
+ )
164
185
  .describe('The IDs of the add-ons to install'),
165
186
  },
166
187
  async ({ projectName, addOns, cwd }) => {
@@ -178,6 +199,7 @@ server.tool(
178
199
  typescript: true,
179
200
  tailwind: true,
180
201
  packageManager: 'pnpm',
202
+ toolchain: 'none',
181
203
  mode: 'file-router',
182
204
  addOns: true,
183
205
  chosenAddOns,
@@ -186,6 +208,7 @@ server.tool(
186
208
  },
187
209
  {
188
210
  silent: true,
211
+ environment: createDefaultEnvironment(),
189
212
  },
190
213
  )
191
214
  return {
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(
@@ -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