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.
- package/README.md +8 -0
- package/dist/cli.js +11 -1
- package/dist/create-app.js +86 -67
- package/dist/environment.js +32 -0
- package/dist/mcp.js +22 -1
- package/dist/options.js +22 -1
- package/dist/toolchain.js +2 -0
- package/package.json +4 -2
- package/src/cli.ts +21 -1
- package/src/create-app.ts +134 -77
- package/src/environment.ts +53 -0
- package/src/mcp.ts +24 -1
- package/src/options.ts +22 -1
- package/src/toolchain.ts +3 -0
- package/src/types.ts +3 -0
- package/templates/react/add-on/form/assets/src/components/demo.FormComponents.tsx +120 -0
- package/templates/react/add-on/form/assets/src/hooks/demo.form-context.ts +4 -0
- package/templates/react/add-on/form/assets/src/hooks/demo.form.ts +22 -0
- package/templates/react/add-on/form/assets/src/routes/demo.form.address.tsx.ejs +203 -0
- package/templates/react/add-on/form/assets/src/routes/demo.form.simple.tsx.ejs +79 -0
- package/templates/react/add-on/form/info.json +6 -2
- package/templates/react/add-on/form/package.json +2 -1
- package/templates/react/base/README.md.ejs +11 -1
- package/templates/react/base/_dot_vscode/settings.biome.json +38 -0
- package/templates/react/base/package.biome.json +10 -0
- 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/info.json +1 -1
- package/templates/react/file-router/src/main.tsx.ejs +2 -2
- package/templates/solid/add-on/form/assets/src/routes/demo.form.tsx.ejs +310 -106
- package/templates/solid/add-on/form/package.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/file-router/src/main.tsx.ejs +4 -2
- package/templates/solid/file-router/src/routes/__root.tsx.ejs +1 -1
- package/tests/cra.test.ts +112 -0
- package/tests/snapshots/cra/cr-js-npm.json +34 -0
- package/tests/snapshots/cra/cr-ts-npm.json +35 -0
- package/tests/snapshots/cra/fr-ts-npm.json +35 -0
- package/tests/snapshots/cra/fr-ts-tw-npm.json +34 -0
- package/tests/test-utilities.ts +69 -0
- 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(
|
|
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
|
-
|
|
36
|
-
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
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
|
-
|
|
201
|
-
|
|
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(
|
|
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 (
|
|
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
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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 (
|
|
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
|
|
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 (
|
|
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
|
|
379
|
-
|
|
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
|
|
401
|
-
|
|
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 (
|
|
413
|
-
for (const integration of
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
463
|
-
for (const file of
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
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(
|
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
|