create-tsrouter-app 0.6.1 → 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/dist/cli.js +4 -1
- package/dist/create-app.js +43 -72
- package/dist/environment.js +32 -0
- package/dist/mcp.js +3 -0
- package/package.json +2 -1
- package/src/cli.ts +5 -1
- package/src/create-app.ts +78 -79
- package/src/environment.ts +53 -0
- package/src/mcp.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 +1 -1
- package/templates/react/example/tanchat/info.json +1 -1
- 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/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,7 +19,7 @@ function sortObject(obj: Record<string, string>): Record<string, string> {
|
|
|
29
19
|
}, {})
|
|
30
20
|
}
|
|
31
21
|
|
|
32
|
-
function createCopyFiles(targetDir: string) {
|
|
22
|
+
function createCopyFiles(environment: Environment, targetDir: string) {
|
|
33
23
|
return async function copyFiles(
|
|
34
24
|
templateDir: string,
|
|
35
25
|
files: Array<string>,
|
|
@@ -42,7 +32,7 @@ function createCopyFiles(targetDir: string) {
|
|
|
42
32
|
const fileNoPath = targetFileName.split('/').pop()
|
|
43
33
|
targetFileName = fileNoPath ? `./${fileNoPath}` : targetFileName
|
|
44
34
|
}
|
|
45
|
-
await copyFile(
|
|
35
|
+
await environment.copyFile(
|
|
46
36
|
resolve(templateDir, file),
|
|
47
37
|
resolve(targetDir, targetFileName),
|
|
48
38
|
)
|
|
@@ -58,6 +48,7 @@ function jsSafeName(name: string) {
|
|
|
58
48
|
}
|
|
59
49
|
|
|
60
50
|
function createTemplateFile(
|
|
51
|
+
environment: Environment,
|
|
61
52
|
projectName: string,
|
|
62
53
|
options: Required<Options>,
|
|
63
54
|
targetDir: string,
|
|
@@ -89,7 +80,10 @@ function createTemplateFile(
|
|
|
89
80
|
...extraTemplateValues,
|
|
90
81
|
}
|
|
91
82
|
|
|
92
|
-
const template = await readFile(
|
|
83
|
+
const template = await environment.readFile(
|
|
84
|
+
resolve(templateDir, file),
|
|
85
|
+
'utf-8',
|
|
86
|
+
)
|
|
93
87
|
let content = ''
|
|
94
88
|
try {
|
|
95
89
|
content = render(template, templateValues)
|
|
@@ -109,15 +103,12 @@ function createTemplateFile(
|
|
|
109
103
|
})
|
|
110
104
|
}
|
|
111
105
|
|
|
112
|
-
await
|
|
113
|
-
recursive: true,
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
await writeFile(resolve(targetDir, target), content)
|
|
106
|
+
await environment.writeFile(resolve(targetDir, target), content)
|
|
117
107
|
}
|
|
118
108
|
}
|
|
119
109
|
|
|
120
110
|
async function createPackageJSON(
|
|
111
|
+
environment: Environment,
|
|
121
112
|
projectName: string,
|
|
122
113
|
options: Required<Options>,
|
|
123
114
|
templateDir: string,
|
|
@@ -130,12 +121,15 @@ async function createPackageJSON(
|
|
|
130
121
|
}>,
|
|
131
122
|
) {
|
|
132
123
|
let packageJSON = JSON.parse(
|
|
133
|
-
await readFile(resolve(templateDir, 'package.json'), 'utf8'),
|
|
124
|
+
await environment.readFile(resolve(templateDir, 'package.json'), 'utf8'),
|
|
134
125
|
)
|
|
135
126
|
packageJSON.name = projectName
|
|
136
127
|
if (options.typescript) {
|
|
137
128
|
const tsPackageJSON = JSON.parse(
|
|
138
|
-
await readFile(
|
|
129
|
+
await environment.readFile(
|
|
130
|
+
resolve(templateDir, 'package.ts.json'),
|
|
131
|
+
'utf8',
|
|
132
|
+
),
|
|
139
133
|
)
|
|
140
134
|
packageJSON = {
|
|
141
135
|
...packageJSON,
|
|
@@ -147,7 +141,10 @@ async function createPackageJSON(
|
|
|
147
141
|
}
|
|
148
142
|
if (options.tailwind) {
|
|
149
143
|
const twPackageJSON = JSON.parse(
|
|
150
|
-
await readFile(
|
|
144
|
+
await environment.readFile(
|
|
145
|
+
resolve(templateDir, 'package.tw.json'),
|
|
146
|
+
'utf8',
|
|
147
|
+
),
|
|
151
148
|
)
|
|
152
149
|
packageJSON = {
|
|
153
150
|
...packageJSON,
|
|
@@ -159,7 +156,10 @@ async function createPackageJSON(
|
|
|
159
156
|
}
|
|
160
157
|
if (options.toolchain === 'biome') {
|
|
161
158
|
const biomePackageJSON = JSON.parse(
|
|
162
|
-
await readFile(
|
|
159
|
+
await environment.readFile(
|
|
160
|
+
resolve(templateDir, 'package.biome.json'),
|
|
161
|
+
'utf8',
|
|
162
|
+
),
|
|
163
163
|
)
|
|
164
164
|
packageJSON = {
|
|
165
165
|
...packageJSON,
|
|
@@ -175,7 +175,7 @@ async function createPackageJSON(
|
|
|
175
175
|
}
|
|
176
176
|
if (options.mode === FILE_ROUTER) {
|
|
177
177
|
const frPackageJSON = JSON.parse(
|
|
178
|
-
await readFile(resolve(routerDir, 'package.fr.json'), 'utf8'),
|
|
178
|
+
await environment.readFile(resolve(routerDir, 'package.fr.json'), 'utf8'),
|
|
179
179
|
)
|
|
180
180
|
packageJSON = {
|
|
181
181
|
...packageJSON,
|
|
@@ -211,28 +211,27 @@ async function createPackageJSON(
|
|
|
211
211
|
packageJSON.devDependencies as Record<string, string>,
|
|
212
212
|
)
|
|
213
213
|
|
|
214
|
-
await writeFile(
|
|
214
|
+
await environment.writeFile(
|
|
215
215
|
resolve(targetDir, 'package.json'),
|
|
216
216
|
JSON.stringify(packageJSON, null, 2),
|
|
217
217
|
)
|
|
218
218
|
}
|
|
219
219
|
|
|
220
220
|
async function copyFilesRecursively(
|
|
221
|
+
environment: Environment,
|
|
221
222
|
source: string,
|
|
222
223
|
target: string,
|
|
223
|
-
copyFile: (source: string, target: string) => Promise<void>,
|
|
224
224
|
templateFile: (file: string, targetFileName?: string) => Promise<void>,
|
|
225
225
|
) {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
const files = readdirSync(source)
|
|
226
|
+
if (environment.isDirectory(source)) {
|
|
227
|
+
const files = environment.readdir(source)
|
|
229
228
|
for (const file of files) {
|
|
230
229
|
const sourceChild = resolve(source, file)
|
|
231
230
|
const targetChild = resolve(target, file)
|
|
232
231
|
await copyFilesRecursively(
|
|
232
|
+
environment,
|
|
233
233
|
sourceChild,
|
|
234
234
|
targetChild,
|
|
235
|
-
copyFile,
|
|
236
235
|
templateFile,
|
|
237
236
|
)
|
|
238
237
|
}
|
|
@@ -251,17 +250,16 @@ async function copyFilesRecursively(
|
|
|
251
250
|
|
|
252
251
|
const targetPath = resolve(dirname(target), targetFile)
|
|
253
252
|
|
|
254
|
-
await mkdir(dirname(targetPath), {
|
|
255
|
-
recursive: true,
|
|
256
|
-
})
|
|
257
|
-
|
|
258
253
|
if (isTemplate) {
|
|
259
254
|
await templateFile(source, targetPath)
|
|
260
255
|
} else {
|
|
261
256
|
if (isAppend) {
|
|
262
|
-
await appendFile(
|
|
257
|
+
await environment.appendFile(
|
|
258
|
+
targetPath,
|
|
259
|
+
(await environment.readFile(source)).toString(),
|
|
260
|
+
)
|
|
263
261
|
} else {
|
|
264
|
-
await copyFile(source, targetPath)
|
|
262
|
+
await environment.copyFile(source, targetPath)
|
|
265
263
|
}
|
|
266
264
|
}
|
|
267
265
|
}
|
|
@@ -271,9 +269,11 @@ export async function createApp(
|
|
|
271
269
|
options: Required<Options>,
|
|
272
270
|
{
|
|
273
271
|
silent = false,
|
|
272
|
+
environment,
|
|
274
273
|
}: {
|
|
275
274
|
silent?: boolean
|
|
276
|
-
|
|
275
|
+
environment: Environment
|
|
276
|
+
},
|
|
277
277
|
) {
|
|
278
278
|
const templateDirBase = fileURLToPath(
|
|
279
279
|
new URL(`../templates/${options.framework}/base`, import.meta.url),
|
|
@@ -286,15 +286,16 @@ export async function createApp(
|
|
|
286
286
|
)
|
|
287
287
|
const targetDir = resolve(process.cwd(), options.projectName)
|
|
288
288
|
|
|
289
|
-
if (
|
|
289
|
+
if (environment.exists(targetDir)) {
|
|
290
290
|
if (!silent) {
|
|
291
291
|
log.error(`Directory "${options.projectName}" already exists`)
|
|
292
292
|
}
|
|
293
293
|
return
|
|
294
294
|
}
|
|
295
295
|
|
|
296
|
-
const copyFiles = createCopyFiles(targetDir)
|
|
296
|
+
const copyFiles = createCopyFiles(environment, targetDir)
|
|
297
297
|
const templateFile = createTemplateFile(
|
|
298
|
+
environment,
|
|
298
299
|
options.projectName,
|
|
299
300
|
options,
|
|
300
301
|
targetDir,
|
|
@@ -303,28 +304,23 @@ export async function createApp(
|
|
|
303
304
|
const isAddOnEnabled = (id: string) =>
|
|
304
305
|
options.chosenAddOns.find((a) => a.id === id)
|
|
305
306
|
|
|
306
|
-
// Make the root directory
|
|
307
|
-
await mkdir(targetDir, { recursive: true })
|
|
308
|
-
|
|
309
307
|
// Setup the .vscode directory
|
|
310
|
-
await mkdir(resolve(targetDir, '.vscode'), { recursive: true })
|
|
311
308
|
switch (options.toolchain) {
|
|
312
309
|
case 'biome':
|
|
313
|
-
await copyFile(
|
|
310
|
+
await environment.copyFile(
|
|
314
311
|
resolve(templateDirBase, '_dot_vscode/settings.biome.json'),
|
|
315
312
|
resolve(targetDir, '.vscode/settings.json'),
|
|
316
313
|
)
|
|
317
314
|
break
|
|
318
315
|
case 'none':
|
|
319
316
|
default:
|
|
320
|
-
await copyFile(
|
|
317
|
+
await environment.copyFile(
|
|
321
318
|
resolve(templateDirBase, '_dot_vscode/settings.json'),
|
|
322
319
|
resolve(targetDir, '.vscode/settings.json'),
|
|
323
320
|
)
|
|
324
321
|
}
|
|
325
322
|
|
|
326
323
|
// Fill the public directory
|
|
327
|
-
await mkdir(resolve(targetDir, 'public'), { recursive: true })
|
|
328
324
|
copyFiles(templateDirBase, [
|
|
329
325
|
'./public/robots.txt',
|
|
330
326
|
'./public/favicon.ico',
|
|
@@ -333,16 +329,9 @@ export async function createApp(
|
|
|
333
329
|
'./public/logo512.png',
|
|
334
330
|
])
|
|
335
331
|
|
|
336
|
-
// Make the src directory
|
|
337
|
-
await mkdir(resolve(targetDir, 'src'), { recursive: true })
|
|
338
|
-
if (options.mode === FILE_ROUTER) {
|
|
339
|
-
await mkdir(resolve(targetDir, 'src/routes'), { recursive: true })
|
|
340
|
-
await mkdir(resolve(targetDir, 'src/components'), { recursive: true })
|
|
341
|
-
}
|
|
342
|
-
|
|
343
332
|
// Check for a .cursorrules file
|
|
344
|
-
if (
|
|
345
|
-
await copyFile(
|
|
333
|
+
if (environment.exists(resolve(templateDirBase, '.cursorrules'))) {
|
|
334
|
+
await environment.copyFile(
|
|
346
335
|
resolve(templateDirBase, '.cursorrules'),
|
|
347
336
|
resolve(targetDir, '.cursorrules'),
|
|
348
337
|
)
|
|
@@ -388,6 +377,7 @@ export async function createApp(
|
|
|
388
377
|
|
|
389
378
|
// Setup the package.json file, optionally with typescript, tailwind and biome
|
|
390
379
|
await createPackageJSON(
|
|
380
|
+
environment,
|
|
391
381
|
options.projectName,
|
|
392
382
|
options,
|
|
393
383
|
templateDirBase,
|
|
@@ -404,21 +394,24 @@ export async function createApp(
|
|
|
404
394
|
)) {
|
|
405
395
|
s?.start(`Setting up ${addOn.name}...`)
|
|
406
396
|
const addOnDir = resolve(addOn.directory, 'assets')
|
|
407
|
-
if (
|
|
397
|
+
if (environment.exists(addOnDir)) {
|
|
408
398
|
await copyFilesRecursively(
|
|
399
|
+
environment,
|
|
409
400
|
addOnDir,
|
|
410
401
|
targetDir,
|
|
411
|
-
copyFile,
|
|
412
402
|
async (file: string, targetFileName?: string) =>
|
|
413
403
|
templateFile(addOnDir, file, targetFileName),
|
|
414
404
|
)
|
|
415
405
|
}
|
|
416
406
|
|
|
417
407
|
if (addOn.command) {
|
|
418
|
-
await
|
|
419
|
-
|
|
420
|
-
|
|
408
|
+
await environment.execute(
|
|
409
|
+
addOn.command.command,
|
|
410
|
+
addOn.command.args || [],
|
|
411
|
+
targetDir,
|
|
412
|
+
)
|
|
421
413
|
}
|
|
414
|
+
|
|
422
415
|
s?.stop(`${addOn.name} setup complete`)
|
|
423
416
|
}
|
|
424
417
|
}
|
|
@@ -437,9 +430,11 @@ export async function createApp(
|
|
|
437
430
|
s?.start(
|
|
438
431
|
`Installing shadcn components (${Array.from(shadcnComponents).join(', ')})...`,
|
|
439
432
|
)
|
|
440
|
-
await
|
|
441
|
-
|
|
442
|
-
|
|
433
|
+
await environment.execute(
|
|
434
|
+
'npx',
|
|
435
|
+
['shadcn@canary', 'add', ...shadcnComponents],
|
|
436
|
+
targetDir,
|
|
437
|
+
)
|
|
443
438
|
s?.stop(`Installed shadcn components`)
|
|
444
439
|
}
|
|
445
440
|
}
|
|
@@ -449,13 +444,13 @@ export async function createApp(
|
|
|
449
444
|
name: string
|
|
450
445
|
path: string
|
|
451
446
|
}> = []
|
|
452
|
-
if (
|
|
453
|
-
for (const integration of
|
|
447
|
+
if (environment.exists(resolve(targetDir, 'src/integrations'))) {
|
|
448
|
+
for (const integration of environment.readdir(
|
|
454
449
|
resolve(targetDir, 'src/integrations'),
|
|
455
450
|
)) {
|
|
456
451
|
const integrationName = jsSafeName(integration)
|
|
457
452
|
if (
|
|
458
|
-
|
|
453
|
+
environment.exists(
|
|
459
454
|
resolve(targetDir, 'src/integrations', integration, 'layout.tsx'),
|
|
460
455
|
)
|
|
461
456
|
) {
|
|
@@ -466,7 +461,7 @@ export async function createApp(
|
|
|
466
461
|
})
|
|
467
462
|
}
|
|
468
463
|
if (
|
|
469
|
-
|
|
464
|
+
environment.exists(
|
|
470
465
|
resolve(targetDir, 'src/integrations', integration, 'provider.tsx'),
|
|
471
466
|
)
|
|
472
467
|
) {
|
|
@@ -477,7 +472,7 @@ export async function createApp(
|
|
|
477
472
|
})
|
|
478
473
|
}
|
|
479
474
|
if (
|
|
480
|
-
|
|
475
|
+
environment.exists(
|
|
481
476
|
resolve(
|
|
482
477
|
targetDir,
|
|
483
478
|
'src/integrations',
|
|
@@ -499,8 +494,8 @@ export async function createApp(
|
|
|
499
494
|
path: string
|
|
500
495
|
name: string
|
|
501
496
|
}> = []
|
|
502
|
-
if (
|
|
503
|
-
for (const file of
|
|
497
|
+
if (environment.exists(resolve(targetDir, 'src/routes'))) {
|
|
498
|
+
for (const file of environment.readdir(resolve(targetDir, 'src/routes'))) {
|
|
504
499
|
const name = file.replace(/\.tsx?|\.jsx?/, '')
|
|
505
500
|
const safeRouteName = jsSafeName(name)
|
|
506
501
|
routes.push({
|
|
@@ -588,7 +583,7 @@ export async function createApp(
|
|
|
588
583
|
}
|
|
589
584
|
|
|
590
585
|
// Add .gitignore
|
|
591
|
-
await copyFile(
|
|
586
|
+
await environment.copyFile(
|
|
592
587
|
resolve(templateDirBase, '_dot_gitignore'),
|
|
593
588
|
resolve(targetDir, '.gitignore'),
|
|
594
589
|
)
|
|
@@ -598,7 +593,7 @@ export async function createApp(
|
|
|
598
593
|
|
|
599
594
|
// Install dependencies
|
|
600
595
|
s?.start(`Installing dependencies via ${options.packageManager}...`)
|
|
601
|
-
await
|
|
596
|
+
await environment.execute(options.packageManager, ['install'], targetDir)
|
|
602
597
|
s?.stop(`Installed dependencies`)
|
|
603
598
|
|
|
604
599
|
if (warnings.length > 0) {
|
|
@@ -612,14 +607,18 @@ export async function createApp(
|
|
|
612
607
|
switch (options.packageManager) {
|
|
613
608
|
case 'pnpm':
|
|
614
609
|
// pnpm automatically forwards extra arguments
|
|
615
|
-
await
|
|
616
|
-
|
|
617
|
-
|
|
610
|
+
await environment.execute(
|
|
611
|
+
options.packageManager,
|
|
612
|
+
['run', 'check', '--fix'],
|
|
613
|
+
targetDir,
|
|
614
|
+
)
|
|
618
615
|
break
|
|
619
616
|
default:
|
|
620
|
-
await
|
|
621
|
-
|
|
622
|
-
|
|
617
|
+
await environment.execute(
|
|
618
|
+
options.packageManager,
|
|
619
|
+
['run', 'check', '--', '--fix'],
|
|
620
|
+
targetDir,
|
|
621
|
+
)
|
|
623
622
|
break
|
|
624
623
|
}
|
|
625
624
|
s?.stop(`Applied toolchain ${options.toolchain}...`)
|
|
@@ -627,7 +626,7 @@ export async function createApp(
|
|
|
627
626
|
|
|
628
627
|
if (options.git) {
|
|
629
628
|
s?.start(`Initializing git repository...`)
|
|
630
|
-
await
|
|
629
|
+
await environment.execute('git', ['init'], targetDir)
|
|
631
630
|
s?.stop(`Initialized git repository`)
|
|
632
631
|
}
|
|
633
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',
|
|
@@ -112,6 +113,7 @@ server.tool(
|
|
|
112
113
|
},
|
|
113
114
|
{
|
|
114
115
|
silent: true,
|
|
116
|
+
environment: createDefaultEnvironment(),
|
|
115
117
|
},
|
|
116
118
|
)
|
|
117
119
|
return {
|
|
@@ -206,6 +208,7 @@ server.tool(
|
|
|
206
208
|
},
|
|
207
209
|
{
|
|
208
210
|
silent: true,
|
|
211
|
+
environment: createDefaultEnvironment(),
|
|
209
212
|
},
|
|
210
213
|
)
|
|
211
214
|
return {
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { useStore } from '@tanstack/react-form'
|
|
2
|
+
import { useFieldContext, useFormContext } from '../hooks/demo.form-context'
|
|
3
|
+
|
|
4
|
+
export function SubscribeButton({ label }: { label: string }) {
|
|
5
|
+
const form = useFormContext()
|
|
6
|
+
return (
|
|
7
|
+
<form.Subscribe selector={(state) => state.isSubmitting}>
|
|
8
|
+
{(isSubmitting) => (
|
|
9
|
+
<button
|
|
10
|
+
disabled={isSubmitting}
|
|
11
|
+
className="px-6 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 transition-colors disabled:opacity-50"
|
|
12
|
+
>
|
|
13
|
+
{label}
|
|
14
|
+
</button>
|
|
15
|
+
)}
|
|
16
|
+
</form.Subscribe>
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function ErrorMessages({
|
|
21
|
+
errors,
|
|
22
|
+
}: {
|
|
23
|
+
errors: Array<string | { message: string }>
|
|
24
|
+
}) {
|
|
25
|
+
return (
|
|
26
|
+
<>
|
|
27
|
+
{errors.map((error) => (
|
|
28
|
+
<div
|
|
29
|
+
key={typeof error === 'string' ? error : error.message}
|
|
30
|
+
className="text-red-500 mt-1 font-bold"
|
|
31
|
+
>
|
|
32
|
+
{typeof error === 'string' ? error : error.message}
|
|
33
|
+
</div>
|
|
34
|
+
))}
|
|
35
|
+
</>
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function TextField({
|
|
40
|
+
label,
|
|
41
|
+
placeholder,
|
|
42
|
+
}: {
|
|
43
|
+
label: string
|
|
44
|
+
placeholder?: string
|
|
45
|
+
}) {
|
|
46
|
+
const field = useFieldContext<string>()
|
|
47
|
+
const errors = useStore(field.store, (state) => state.meta.errors)
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<div>
|
|
51
|
+
<label htmlFor={label} className="block font-bold mb-1 text-xl">
|
|
52
|
+
{label}
|
|
53
|
+
<input
|
|
54
|
+
value={field.state.value}
|
|
55
|
+
placeholder={placeholder}
|
|
56
|
+
onBlur={field.handleBlur}
|
|
57
|
+
onChange={(e) => field.handleChange(e.target.value)}
|
|
58
|
+
className="w-full px-4 py-2 rounded-md border border-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500"
|
|
59
|
+
/>
|
|
60
|
+
</label>
|
|
61
|
+
{field.state.meta.isTouched && <ErrorMessages errors={errors} />}
|
|
62
|
+
</div>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function TextArea({
|
|
67
|
+
label,
|
|
68
|
+
rows = 3,
|
|
69
|
+
}: {
|
|
70
|
+
label: string
|
|
71
|
+
rows?: number
|
|
72
|
+
}) {
|
|
73
|
+
const field = useFieldContext<string>()
|
|
74
|
+
const errors = useStore(field.store, (state) => state.meta.errors)
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<div>
|
|
78
|
+
<label htmlFor={label} className="block font-bold mb-1 text-xl">
|
|
79
|
+
{label}
|
|
80
|
+
<textarea
|
|
81
|
+
value={field.state.value}
|
|
82
|
+
onBlur={field.handleBlur}
|
|
83
|
+
rows={rows}
|
|
84
|
+
onChange={(e) => field.handleChange(e.target.value)}
|
|
85
|
+
className="w-full px-4 py-2 rounded-md border border-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500"
|
|
86
|
+
/>
|
|
87
|
+
</label>
|
|
88
|
+
{field.state.meta.isTouched && <ErrorMessages errors={errors} />}
|
|
89
|
+
</div>
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function Select({
|
|
94
|
+
label,
|
|
95
|
+
children,
|
|
96
|
+
}: {
|
|
97
|
+
label: string
|
|
98
|
+
children: React.ReactNode
|
|
99
|
+
}) {
|
|
100
|
+
const field = useFieldContext<string>()
|
|
101
|
+
const errors = useStore(field.store, (state) => state.meta.errors)
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<div>
|
|
105
|
+
<label htmlFor={label} className="block font-bold mb-1 text-xl">
|
|
106
|
+
{label}
|
|
107
|
+
</label>
|
|
108
|
+
<select
|
|
109
|
+
name={field.name}
|
|
110
|
+
value={field.state.value}
|
|
111
|
+
onBlur={field.handleBlur}
|
|
112
|
+
onChange={(e) => field.handleChange(e.target.value)}
|
|
113
|
+
className="w-full px-4 py-2 rounded-md border border-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500"
|
|
114
|
+
>
|
|
115
|
+
{children}
|
|
116
|
+
</select>
|
|
117
|
+
{field.state.meta.isTouched && <ErrorMessages errors={errors} />}
|
|
118
|
+
</div>
|
|
119
|
+
)
|
|
120
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { createFormHook } from '@tanstack/react-form'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Select,
|
|
5
|
+
SubscribeButton,
|
|
6
|
+
TextArea,
|
|
7
|
+
TextField,
|
|
8
|
+
} from '../components/demo.FormComponents'
|
|
9
|
+
import { fieldContext, formContext } from './demo.form-context'
|
|
10
|
+
|
|
11
|
+
export const { useAppForm } = createFormHook({
|
|
12
|
+
fieldComponents: {
|
|
13
|
+
TextField,
|
|
14
|
+
Select,
|
|
15
|
+
TextArea,
|
|
16
|
+
},
|
|
17
|
+
formComponents: {
|
|
18
|
+
SubscribeButton,
|
|
19
|
+
},
|
|
20
|
+
fieldContext,
|
|
21
|
+
formContext,
|
|
22
|
+
})
|