frontmcp 0.5.1 → 0.6.0

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 (36) hide show
  1. package/package.json +1 -1
  2. package/src/args.d.ts +7 -0
  3. package/src/args.js +13 -0
  4. package/src/args.js.map +1 -1
  5. package/src/cli.js +17 -3
  6. package/src/cli.js.map +1 -1
  7. package/src/commands/build/adapters/cloudflare.d.ts +8 -0
  8. package/src/commands/build/adapters/cloudflare.js +80 -0
  9. package/src/commands/build/adapters/cloudflare.js.map +1 -0
  10. package/src/commands/build/adapters/index.d.ts +12 -0
  11. package/src/commands/build/adapters/index.js +23 -0
  12. package/src/commands/build/adapters/index.js.map +1 -0
  13. package/src/commands/build/adapters/lambda.d.ts +11 -0
  14. package/src/commands/build/adapters/lambda.js +43 -0
  15. package/src/commands/build/adapters/lambda.js.map +1 -0
  16. package/src/commands/build/adapters/node.d.ts +7 -0
  17. package/src/commands/build/adapters/node.js +13 -0
  18. package/src/commands/build/adapters/node.js.map +1 -0
  19. package/src/commands/build/adapters/vercel.d.ts +8 -0
  20. package/src/commands/build/adapters/vercel.js +36 -0
  21. package/src/commands/build/adapters/vercel.js.map +1 -0
  22. package/src/commands/build/index.d.ts +22 -0
  23. package/src/commands/build/index.js +120 -0
  24. package/src/commands/build/index.js.map +1 -0
  25. package/src/commands/build/types.d.ts +23 -0
  26. package/src/commands/build/types.js +3 -0
  27. package/src/commands/build/types.js.map +1 -0
  28. package/src/commands/create.d.ts +15 -1
  29. package/src/commands/create.js +1199 -91
  30. package/src/commands/create.js.map +1 -1
  31. package/src/tsconfig.d.ts +1 -1
  32. package/src/tsconfig.js +1 -1
  33. package/src/tsconfig.js.map +1 -1
  34. package/src/commands/build.d.ts +0 -2
  35. package/src/commands/build.js +0 -43
  36. package/src/commands/build.js.map +0 -1
@@ -3,12 +3,45 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.runCreate = runCreate;
4
4
  const tslib_1 = require("tslib");
5
5
  const path = tslib_1.__importStar(require("path"));
6
+ const readline = tslib_1.__importStar(require("readline"));
6
7
  const fs_1 = require("fs");
7
8
  const colors_1 = require("../colors");
8
9
  const fs_2 = require("../utils/fs");
9
10
  const tsconfig_1 = require("../tsconfig");
10
11
  const version_1 = require("../version");
11
12
  const fs_3 = require("../utils/fs");
13
+ function createPrompt() {
14
+ const rl = readline.createInterface({
15
+ input: process.stdin,
16
+ output: process.stdout,
17
+ });
18
+ return {
19
+ ask: (question) => new Promise((resolve) => rl.question(question, (ans) => resolve(ans.trim()))),
20
+ select: async (question, options, defaultIndex = 0) => {
21
+ console.log(question);
22
+ options.forEach((opt, i) => {
23
+ const marker = i === defaultIndex ? (0, colors_1.c)('green', '●') : (0, colors_1.c)('gray', '○');
24
+ console.log(` ${marker} ${(0, colors_1.c)('cyan', `${i + 1})`)} ${opt.label}`);
25
+ });
26
+ const answer = await new Promise((resolve) => rl.question(`${(0, colors_1.c)('gray', `Select [1-${options.length}]:`)} `, resolve));
27
+ const idx = parseInt(answer.trim(), 10) - 1;
28
+ if (!isNaN(idx) && idx >= 0 && idx < options.length)
29
+ return options[idx].value;
30
+ return options[defaultIndex].value;
31
+ },
32
+ confirm: async (question, defaultValue = true) => {
33
+ const hint = defaultValue ? '[Y/n]' : '[y/N]';
34
+ const answer = await new Promise((resolve) => rl.question(`${question} ${(0, colors_1.c)('gray', hint)} `, resolve));
35
+ if (!answer.trim())
36
+ return defaultValue;
37
+ return answer.trim().toLowerCase().startsWith('y');
38
+ },
39
+ close: () => rl.close(),
40
+ };
41
+ }
42
+ function isInteractive() {
43
+ return process.stdin.isTTY === true;
44
+ }
12
45
  function sanitizeForFolder(name) {
13
46
  const seg = name.startsWith('@') && name.includes('/') ? name.split('/')[1] : name;
14
47
  return (seg
@@ -32,87 +65,6 @@ function pkgNameFromCwd(cwd) {
32
65
  .replace(/[^a-zA-Z0-9._-]/g, '-')
33
66
  .toLowerCase() || 'frontmcp-app');
34
67
  }
35
- async function upsertPackageJson(cwd, nameOverride, selfVersion) {
36
- const pkgPath = path.join(cwd, 'package.json');
37
- const existing = await (0, fs_3.readJSON)(pkgPath);
38
- const frontmcpLibRange = `~${selfVersion}`;
39
- const base = {
40
- name: nameOverride ?? pkgNameFromCwd(cwd),
41
- version: '0.1.0',
42
- private: true,
43
- type: 'commonjs',
44
- main: 'src/main.ts',
45
- scripts: {
46
- dev: 'frontmcp dev',
47
- build: 'frontmcp build',
48
- inspect: 'frontmcp inspector',
49
- doctor: 'frontmcp doctor',
50
- 'test:e2e': 'jest --config jest.e2e.config.ts --runInBand',
51
- },
52
- engines: {
53
- node: '>=22',
54
- npm: '>=10',
55
- },
56
- dependencies: {
57
- '@frontmcp/sdk': frontmcpLibRange,
58
- '@frontmcp/plugins': frontmcpLibRange,
59
- '@frontmcp/adapters': frontmcpLibRange,
60
- zod: '^4.0.0',
61
- 'reflect-metadata': '^0.2.2',
62
- },
63
- devDependencies: {
64
- frontmcp: selfVersion,
65
- '@frontmcp/testing': frontmcpLibRange,
66
- '@swc/core': '^1.11.29',
67
- '@swc/jest': '^0.2.37',
68
- jest: '^29.7.0',
69
- tsx: '^4.20.6',
70
- '@types/node': '^24.0.0',
71
- typescript: '^5.5.3',
72
- },
73
- };
74
- if (!existing) {
75
- await (0, fs_2.writeJSON)(pkgPath, base);
76
- console.log((0, colors_1.c)('green', '✅ Created package.json (synced @frontmcp libs to CLI version + exact frontmcp)'));
77
- return;
78
- }
79
- const merged = { ...base, ...existing };
80
- merged.name = existing.name || base.name;
81
- merged.main = existing.main || base.main;
82
- merged.type = existing.type || base.type;
83
- merged.scripts = {
84
- ...base.scripts,
85
- ...(existing.scripts || {}),
86
- dev: existing.scripts?.dev ?? base.scripts.dev,
87
- build: existing.scripts?.build ?? base.scripts.build,
88
- inspect: existing.scripts?.inspect ?? base.scripts.inspect,
89
- doctor: existing.scripts?.doctor ?? base.scripts.doctor,
90
- 'test:e2e': existing.scripts?.['test:e2e'] ?? base.scripts['test:e2e'],
91
- };
92
- merged.engines = {
93
- ...(existing.engines || {}),
94
- node: existing.engines?.node || base.engines.node,
95
- npm: existing.engines?.npm || base.engines.npm,
96
- };
97
- merged.dependencies = {
98
- ...(existing.dependencies || {}),
99
- ...base.dependencies,
100
- '@frontmcp/sdk': frontmcpLibRange,
101
- '@frontmcp/plugins': frontmcpLibRange,
102
- '@frontmcp/adapters': frontmcpLibRange,
103
- zod: '^4.0.0',
104
- 'reflect-metadata': '^0.2.2',
105
- };
106
- merged.devDependencies = {
107
- ...(existing.devDependencies || {}),
108
- ...base.devDependencies,
109
- frontmcp: selfVersion,
110
- tsx: '^4.20.6',
111
- typescript: '^5.5.3',
112
- };
113
- await (0, fs_2.writeJSON)(pkgPath, merged);
114
- console.log((0, colors_1.c)('green', '✅ Updated package.json (synced @frontmcp libs + frontmcp to current CLI version)'));
115
- }
116
68
  async function scaffoldFileIfMissing(baseDir, p, content) {
117
69
  if (await (0, fs_2.fileExists)(p)) {
118
70
  console.log((0, colors_1.c)('gray', `skip: ${path.relative(baseDir, p)} already exists`));
@@ -187,6 +139,44 @@ test.describe('Server E2E', () => {
187
139
  });
188
140
  });
189
141
  `;
142
+ const TEMPLATE_GITIGNORE = `
143
+ # Dependencies
144
+ node_modules/
145
+
146
+ # Build output
147
+ dist/
148
+ *.tsbuildinfo
149
+
150
+ # IDE
151
+ .idea/
152
+ .vscode/
153
+ *.swp
154
+ *.swo
155
+
156
+ # OS files
157
+ .DS_Store
158
+ Thumbs.db
159
+
160
+ # Logs
161
+ *.log
162
+ npm-debug.log*
163
+ yarn-debug.log*
164
+ yarn-error.log*
165
+
166
+ # Environment variables
167
+ .env
168
+ .env.local
169
+ .env.*.local
170
+
171
+ # FrontMCP development keys (contains private keys - never commit!)
172
+ .frontmcp/
173
+
174
+ # Coverage
175
+ coverage/
176
+
177
+ # Test output
178
+ test-output/
179
+ `;
190
180
  const TEMPLATE_JEST_E2E_CONFIG = `
191
181
  /* eslint-disable */
192
182
  export default {
@@ -226,15 +216,955 @@ export default {
226
216
  transformIgnorePatterns: ['node_modules/(?!(jose)/)'],
227
217
  };
228
218
  `;
229
- async function runCreate(projectArg) {
230
- if (!projectArg) {
231
- console.error((0, colors_1.c)('red', 'Error: project name is required.\n'));
232
- console.log(`Usage: ${(0, colors_1.c)('bold', 'npx frontmcp create <project-name>')}`);
233
- process.exit(1);
219
+ const TEMPLATE_DOCKER_COMPOSE = `
220
+ version: '3.8'
221
+
222
+ services:
223
+ redis:
224
+ image: redis:7-alpine
225
+ ports:
226
+ - '6379:6379'
227
+ volumes:
228
+ - redis-data:/data
229
+ command: redis-server --appendonly yes
230
+ healthcheck:
231
+ test: ['CMD', 'redis-cli', 'ping']
232
+ interval: 10s
233
+ timeout: 5s
234
+ retries: 3
235
+
236
+ app:
237
+ build:
238
+ context: .
239
+ dockerfile: Dockerfile
240
+ ports:
241
+ - '\${PORT:-3000}:3000'
242
+ environment:
243
+ - NODE_ENV=\${NODE_ENV:-development}
244
+ - REDIS_HOST=redis
245
+ - REDIS_PORT=6379
246
+ depends_on:
247
+ redis:
248
+ condition: service_healthy
249
+ volumes:
250
+ - ./src:/app/src
251
+ command: npm run dev
252
+
253
+ volumes:
254
+ redis-data:
255
+ `;
256
+ const TEMPLATE_DOCKERFILE = `
257
+ # Build stage
258
+ FROM node:22-alpine AS builder
259
+
260
+ WORKDIR /app
261
+
262
+ # Install all dependencies (including devDependencies for build)
263
+ COPY package*.json ./
264
+ RUN npm ci
265
+
266
+ # Copy source and build
267
+ COPY . .
268
+ RUN npm run build
269
+
270
+ # Production stage
271
+ FROM node:22-alpine AS runner
272
+
273
+ WORKDIR /app
274
+ ENV NODE_ENV=production
275
+
276
+ # Install production dependencies only
277
+ COPY package*.json ./
278
+ RUN npm ci --omit=dev
279
+
280
+ # Copy built artifacts from builder
281
+ COPY --from=builder /app/dist ./dist
282
+
283
+ EXPOSE 3000
284
+
285
+ CMD ["node", "dist/main.js"]
286
+ `;
287
+ const TEMPLATE_ENV_EXAMPLE = `
288
+ # Application
289
+ PORT=3000
290
+ NODE_ENV=development
291
+
292
+ # Redis (recommended for development, required for production)
293
+ REDIS_HOST=localhost
294
+ REDIS_PORT=6379
295
+ # SECURITY: Set a strong password in production
296
+ REDIS_PASSWORD=
297
+ REDIS_DB=0
298
+
299
+ # Optional: Redis TLS (enable for production)
300
+ REDIS_TLS=false
301
+ `;
302
+ const TEMPLATE_ENV_DOCKER = `
303
+ # Docker-specific environment
304
+ # Copy this to .env when running with docker compose
305
+
306
+ # Application
307
+ PORT=3000
308
+ NODE_ENV=development
309
+
310
+ # Redis - use 'redis' (service name) as host inside Docker network
311
+ REDIS_HOST=redis
312
+ REDIS_PORT=6379
313
+ # SECURITY: Set a strong password in production
314
+ REDIS_PASSWORD=
315
+ REDIS_DB=0
316
+ REDIS_TLS=false
317
+ `;
318
+ const TEMPLATE_README = `
319
+ # FrontMCP Server
320
+
321
+ A TypeScript MCP server built with [FrontMCP](https://github.com/agentfront/frontmcp).
322
+
323
+ ## Quick Start
324
+
325
+ \`\`\`bash
326
+ # Install dependencies
327
+ npm install
328
+
329
+ # Start development server
330
+ npm run dev
331
+
332
+ # Run MCP Inspector
333
+ npm run inspect
334
+ \`\`\`
335
+
336
+ ## Development with Docker
337
+
338
+ ### Prerequisites
339
+ - Docker & Docker Compose installed
340
+
341
+ ### Quick Start
342
+
343
+ \`\`\`bash
344
+ # Start Redis and app in development mode
345
+ docker compose up
346
+
347
+ # Start only Redis (for local development)
348
+ docker compose up redis -d
349
+
350
+ # Stop all services
351
+ docker compose down
352
+ \`\`\`
353
+
354
+ ### Environment Variables
355
+
356
+ | Variable | Default | Description |
357
+ |----------|---------|-------------|
358
+ | \`PORT\` | 3000 | Application port |
359
+ | \`NODE_ENV\` | development | Environment mode |
360
+ | \`REDIS_HOST\` | localhost | Redis host (use \`redis\` in Docker) |
361
+ | \`REDIS_PORT\` | 6379 | Redis port |
362
+
363
+ ## Redis Configuration
364
+
365
+ ### Development
366
+ Redis is **recommended** for development to enable caching and session persistence.
367
+ Use the included \`docker-compose.yml\` to run Redis locally:
368
+
369
+ \`\`\`bash
370
+ docker compose up redis -d
371
+ \`\`\`
372
+
373
+ ### Production
374
+ Redis is **required** in production for:
375
+ - Session storage (multi-instance deployments)
376
+ - Caching (performance optimization)
377
+ - Rate limiting (if enabled)
378
+
379
+ See the [Redis Setup Guide](https://docs.agentfront.dev/docs/deployment/redis-setup) for production configuration.
380
+
381
+ ## Scripts
382
+
383
+ | Script | Description |
384
+ |--------|-------------|
385
+ | \`npm run dev\` | Start development server with hot reload |
386
+ | \`npm run build\` | Build for production |
387
+ | \`npm run inspect\` | Launch MCP Inspector |
388
+ | \`npm run doctor\` | Check project configuration |
389
+ | \`npm run test\` | Run unit tests |
390
+ | \`npm run test:e2e\` | Run E2E tests |
391
+
392
+ ## Project Structure
393
+
394
+ \`\`\`
395
+ ├── .env.example # Environment variables template
396
+ ├── .gitignore # Git ignore rules
397
+ ├── docker-compose.yml # Docker services config
398
+ ├── Dockerfile # Container build config
399
+ ├── e2e/ # E2E tests
400
+ ├── jest.e2e.config.ts # Jest E2E configuration
401
+ ├── package.json # Dependencies and scripts
402
+ ├── src/
403
+ │ ├── main.ts # Server entry point
404
+ │ ├── calc.app.ts # Example app
405
+ │ └── tools/
406
+ │ └── add.tool.ts # Example tool
407
+ └── tsconfig.json # TypeScript config
408
+ \`\`\`
409
+
410
+ ## Learn More
411
+
412
+ - [FrontMCP Documentation](https://docs.agentfront.dev)
413
+ - [MCP Specification](https://modelcontextprotocol.io)
414
+ `;
415
+ // =============================================================================
416
+ // Deployment Target Templates
417
+ // =============================================================================
418
+ // Docker templates (moved to ci/ folder)
419
+ const TEMPLATE_DOCKERFILE_CI = `
420
+ # Build stage
421
+ FROM node:22-alpine AS builder
422
+
423
+ WORKDIR /app
424
+
425
+ # Install all dependencies (including devDependencies for build)
426
+ COPY package*.json ./
427
+ RUN npm ci
428
+
429
+ # Copy source and build
430
+ COPY . .
431
+ RUN npm run build
432
+
433
+ # Production stage
434
+ FROM node:22-alpine AS runner
435
+
436
+ WORKDIR /app
437
+ ENV NODE_ENV=production
438
+
439
+ # Install production dependencies only
440
+ COPY package*.json ./
441
+ RUN npm ci --omit=dev
442
+
443
+ # Copy built artifacts from builder
444
+ COPY --from=builder /app/dist ./dist
445
+
446
+ EXPOSE 3000
447
+
448
+ CMD ["node", "dist/main.js"]
449
+ `;
450
+ const TEMPLATE_DOCKER_COMPOSE_WITH_REDIS = `
451
+ version: '3.8'
452
+
453
+ services:
454
+ redis:
455
+ image: redis:7-alpine
456
+ ports:
457
+ - '6379:6379'
458
+ volumes:
459
+ - redis-data:/data
460
+ command: redis-server --appendonly yes
461
+ healthcheck:
462
+ test: ['CMD', 'redis-cli', 'ping']
463
+ interval: 10s
464
+ timeout: 5s
465
+ retries: 3
466
+
467
+ app:
468
+ build:
469
+ context: .
470
+ dockerfile: ci/Dockerfile
471
+ ports:
472
+ - '\${PORT:-3000}:3000'
473
+ environment:
474
+ - NODE_ENV=\${NODE_ENV:-development}
475
+ - REDIS_HOST=redis
476
+ - REDIS_PORT=6379
477
+ depends_on:
478
+ redis:
479
+ condition: service_healthy
480
+ volumes:
481
+ - ./src:/app/src
482
+ command: npm run dev
483
+
484
+ volumes:
485
+ redis-data:
486
+ `;
487
+ const TEMPLATE_DOCKER_COMPOSE_NO_REDIS = `
488
+ version: '3.8'
489
+
490
+ services:
491
+ app:
492
+ build:
493
+ context: .
494
+ dockerfile: ci/Dockerfile
495
+ ports:
496
+ - '\${PORT:-3000}:3000'
497
+ environment:
498
+ - NODE_ENV=\${NODE_ENV:-development}
499
+ volumes:
500
+ - ./src:/app/src
501
+ command: npm run dev
502
+ `;
503
+ const TEMPLATE_ENV_DOCKER_CI = `
504
+ # Docker-specific environment
505
+ # Use with: docker compose -f ci/docker-compose.yml --env-file ci/.env.docker up
506
+
507
+ # Application
508
+ PORT=3000
509
+ NODE_ENV=development
510
+
511
+ # Redis - use 'redis' (service name) as host inside Docker network
512
+ REDIS_HOST=redis
513
+ REDIS_PORT=6379
514
+ # SECURITY: Set a strong password in production
515
+ REDIS_PASSWORD=
516
+ REDIS_DB=0
517
+ REDIS_TLS=false
518
+ `;
519
+ // Vercel template
520
+ const TEMPLATE_VERCEL_JSON = (projectName) => JSON.stringify({
521
+ $schema: 'https://openapi.vercel.sh/vercel.json',
522
+ name: projectName,
523
+ version: 2,
524
+ builds: [
525
+ {
526
+ src: 'dist/main.js',
527
+ use: '@vercel/node',
528
+ },
529
+ ],
530
+ routes: [
531
+ {
532
+ src: '/(.*)',
533
+ dest: '/dist/main.js',
534
+ },
535
+ ],
536
+ env: {
537
+ NODE_ENV: 'production',
538
+ },
539
+ }, null, 2);
540
+ // AWS Lambda SAM template
541
+ const TEMPLATE_SAM_YAML = (projectName) => `
542
+ AWSTemplateFormatVersion: '2010-09-09'
543
+ Transform: AWS::Serverless-2016-10-31
544
+ Description: ${projectName} - FrontMCP Lambda Function
545
+
546
+ Globals:
547
+ Function:
548
+ Timeout: 30
549
+ Runtime: nodejs22.x
550
+ MemorySize: 256
551
+
552
+ Resources:
553
+ FrontMCPFunction:
554
+ Type: AWS::Serverless::Function
555
+ Properties:
556
+ CodeUri: ../dist/
557
+ Handler: main.handler
558
+ Events:
559
+ ApiEvent:
560
+ Type: HttpApi
561
+ Properties:
562
+ Path: /{proxy+}
563
+ Method: ANY
564
+
565
+ Outputs:
566
+ ApiEndpoint:
567
+ Description: API Gateway endpoint URL
568
+ Value: !Sub "https://\${ServerlessHttpApi}.execute-api.\${AWS::Region}.amazonaws.com"
569
+ `;
570
+ // Cloudflare Workers template
571
+ const TEMPLATE_WRANGLER_TOML = (projectName) => `
572
+ name = "${projectName}"
573
+ main = "dist/main.js"
574
+ compatibility_date = "2024-01-01"
575
+
576
+ [vars]
577
+ NODE_ENV = "production"
578
+
579
+ # Uncomment to enable KV namespace for caching
580
+ # [[kv_namespaces]]
581
+ # binding = "CACHE"
582
+ # id = "your-kv-namespace-id"
583
+ `;
584
+ // =============================================================================
585
+ // GitHub Actions Templates
586
+ // =============================================================================
587
+ const TEMPLATE_GH_CI = `
588
+ name: CI
589
+
590
+ on:
591
+ push:
592
+ branches: [main]
593
+ pull_request:
594
+ branches: [main]
595
+
596
+ jobs:
597
+ lint-and-test:
598
+ runs-on: ubuntu-latest
599
+
600
+ steps:
601
+ - uses: actions/checkout@v4
602
+
603
+ - name: Setup Node.js
604
+ uses: actions/setup-node@v4
605
+ with:
606
+ node-version: '22'
607
+ cache: 'npm'
608
+
609
+ - name: Install dependencies
610
+ run: npm ci
611
+
612
+ - name: Type check
613
+ run: npx tsc --noEmit
614
+
615
+ - name: Run tests
616
+ run: npm test
617
+ `;
618
+ const TEMPLATE_GH_E2E = `
619
+ name: E2E Tests
620
+
621
+ on:
622
+ push:
623
+ branches: [main]
624
+ pull_request:
625
+ branches: [main]
626
+
627
+ jobs:
628
+ e2e:
629
+ runs-on: ubuntu-latest
630
+
631
+ steps:
632
+ - uses: actions/checkout@v4
633
+
634
+ - name: Setup Node.js
635
+ uses: actions/setup-node@v4
636
+ with:
637
+ node-version: '22'
638
+ cache: 'npm'
639
+
640
+ - name: Install dependencies
641
+ run: npm ci
642
+
643
+ - name: Build
644
+ run: npm run build
645
+
646
+ - name: Run E2E tests
647
+ run: npm run test:e2e
648
+ `;
649
+ const TEMPLATE_GH_DEPLOY_DOCKER = `
650
+ name: Build and Push Docker Image
651
+
652
+ on:
653
+ push:
654
+ branches: [main]
655
+ tags: ['v*']
656
+
657
+ env:
658
+ REGISTRY: ghcr.io
659
+ IMAGE_NAME: \${{ github.repository }}
660
+
661
+ jobs:
662
+ build-and-push:
663
+ runs-on: ubuntu-latest
664
+ permissions:
665
+ contents: read
666
+ packages: write
667
+
668
+ steps:
669
+ - uses: actions/checkout@v4
670
+
671
+ - name: Log in to Container Registry
672
+ uses: docker/login-action@v3
673
+ with:
674
+ registry: \${{ env.REGISTRY }}
675
+ username: \${{ github.actor }}
676
+ password: \${{ secrets.GITHUB_TOKEN }}
677
+
678
+ - name: Extract metadata
679
+ id: meta
680
+ uses: docker/metadata-action@v5
681
+ with:
682
+ images: \${{ env.REGISTRY }}/\${{ env.IMAGE_NAME }}
683
+
684
+ - name: Build and push
685
+ uses: docker/build-push-action@v5
686
+ with:
687
+ context: .
688
+ file: ./ci/Dockerfile
689
+ push: true
690
+ tags: \${{ steps.meta.outputs.tags }}
691
+ labels: \${{ steps.meta.outputs.labels }}
692
+ `;
693
+ const TEMPLATE_GH_DEPLOY_VERCEL = `
694
+ name: Deploy to Vercel
695
+
696
+ on:
697
+ push:
698
+ branches: [main]
699
+
700
+ jobs:
701
+ deploy:
702
+ runs-on: ubuntu-latest
703
+
704
+ steps:
705
+ - uses: actions/checkout@v4
706
+
707
+ - name: Setup Node.js
708
+ uses: actions/setup-node@v4
709
+ with:
710
+ node-version: '22'
711
+ cache: 'npm'
712
+
713
+ - name: Install dependencies
714
+ run: npm ci
715
+
716
+ - name: Build
717
+ run: npm run build
718
+
719
+ - name: Deploy to Vercel
720
+ uses: amondnet/vercel-action@v25
721
+ with:
722
+ vercel-token: \${{ secrets.VERCEL_TOKEN }}
723
+ vercel-org-id: \${{ secrets.VERCEL_ORG_ID }}
724
+ vercel-project-id: \${{ secrets.VERCEL_PROJECT_ID }}
725
+ vercel-args: '--prod'
726
+ `;
727
+ const TEMPLATE_GH_DEPLOY_LAMBDA = `
728
+ name: Deploy to AWS Lambda
729
+
730
+ on:
731
+ push:
732
+ branches: [main]
733
+
734
+ jobs:
735
+ deploy:
736
+ runs-on: ubuntu-latest
737
+
738
+ steps:
739
+ - uses: actions/checkout@v4
740
+
741
+ - name: Setup Node.js
742
+ uses: actions/setup-node@v4
743
+ with:
744
+ node-version: '22'
745
+ cache: 'npm'
746
+
747
+ - name: Install dependencies
748
+ run: npm ci
749
+
750
+ - name: Build
751
+ run: npm run build
752
+
753
+ - name: Configure AWS credentials
754
+ uses: aws-actions/configure-aws-credentials@v4
755
+ with:
756
+ aws-access-key-id: \${{ secrets.AWS_ACCESS_KEY_ID }}
757
+ aws-secret-access-key: \${{ secrets.AWS_SECRET_ACCESS_KEY }}
758
+ aws-region: \${{ secrets.AWS_REGION }}
759
+
760
+ - name: Setup SAM
761
+ uses: aws-actions/setup-sam@v2
762
+
763
+ - name: Deploy with SAM
764
+ run: |
765
+ cd ci
766
+ sam build
767
+ sam deploy --no-confirm-changeset --no-fail-on-empty-changeset
768
+ `;
769
+ const TEMPLATE_GH_DEPLOY_CLOUDFLARE = `
770
+ name: Deploy to Cloudflare Workers
771
+
772
+ on:
773
+ push:
774
+ branches: [main]
775
+
776
+ jobs:
777
+ deploy:
778
+ runs-on: ubuntu-latest
779
+
780
+ steps:
781
+ - uses: actions/checkout@v4
782
+
783
+ - name: Setup Node.js
784
+ uses: actions/setup-node@v4
785
+ with:
786
+ node-version: '22'
787
+ cache: 'npm'
788
+
789
+ - name: Install dependencies
790
+ run: npm ci
791
+
792
+ - name: Build
793
+ run: npm run build
794
+
795
+ - name: Deploy to Cloudflare
796
+ uses: cloudflare/wrangler-action@v3
797
+ with:
798
+ apiToken: \${{ secrets.CLOUDFLARE_API_TOKEN }}
799
+ `;
800
+ // =============================================================================
801
+ // Dynamic README Templates
802
+ // =============================================================================
803
+ function generateReadme(options) {
804
+ const { projectName, deploymentTarget, redisSetup, enableGitHubActions } = options;
805
+ let readme = `# ${projectName}
806
+
807
+ A TypeScript MCP server built with [FrontMCP](https://github.com/agentfront/frontmcp).
808
+ `;
809
+ // Add CI badge if GitHub Actions enabled
810
+ if (enableGitHubActions) {
811
+ readme += `
812
+ ![CI](https://github.com/YOUR_USERNAME/${projectName}/actions/workflows/ci.yml/badge.svg)
813
+ `;
814
+ }
815
+ readme += `
816
+ ## Quick Start
817
+
818
+ \`\`\`bash
819
+ # Install dependencies
820
+ npm install
821
+
822
+ # Start development server
823
+ npm run dev
824
+
825
+ # Run MCP Inspector
826
+ npm run inspect
827
+ \`\`\`
828
+ `;
829
+ // Deployment-specific sections
830
+ if (deploymentTarget === 'node') {
831
+ readme += `
832
+ ## Docker Development
833
+
834
+ \`\`\`bash
835
+ # Start all services${redisSetup === 'docker' ? ' (includes Redis)' : ''}
836
+ npm run docker:up
837
+
838
+ # Stop all services
839
+ npm run docker:down
840
+
841
+ # Rebuild Docker image
842
+ npm run docker:build
843
+ \`\`\`
844
+ `;
845
+ if (redisSetup === 'docker') {
846
+ readme += `
847
+ ### Redis
848
+
849
+ Redis is included in the Docker Compose setup. For local development without Docker:
850
+
851
+ \`\`\`bash
852
+ # Start only Redis
853
+ docker compose -f ci/docker-compose.yml up redis -d
854
+ \`\`\`
855
+ `;
856
+ }
857
+ readme += `
858
+ ## Production Deployment
859
+
860
+ Build and push the Docker image:
861
+
862
+ \`\`\`bash
863
+ docker build -f ci/Dockerfile -t ${projectName}:latest .
864
+ docker push your-registry/${projectName}:latest
865
+ \`\`\`
866
+ `;
867
+ }
868
+ if (deploymentTarget === 'vercel') {
869
+ readme += `
870
+ ## Deploy to Vercel
871
+
872
+ \`\`\`bash
873
+ # Build for production
874
+ npm run build
875
+
876
+ # Deploy using Vercel CLI
877
+ npx vercel --prod
878
+ \`\`\`
879
+
880
+ Or connect your repository to Vercel for automatic deployments.
881
+ `;
882
+ }
883
+ if (deploymentTarget === 'lambda') {
884
+ readme += `
885
+ ## Deploy to AWS Lambda
886
+
887
+ \`\`\`bash
888
+ # Build the project
889
+ npm run build
890
+
891
+ # Deploy using AWS SAM
892
+ npm run deploy
893
+ \`\`\`
894
+
895
+ ### Prerequisites
896
+
897
+ - AWS CLI configured with appropriate credentials
898
+ - AWS SAM CLI installed (\`brew install aws-sam-cli\` or \`pip install aws-sam-cli\`)
899
+ `;
900
+ }
901
+ if (deploymentTarget === 'cloudflare') {
902
+ readme += `
903
+ ## Deploy to Cloudflare Workers
904
+
905
+ \`\`\`bash
906
+ # Build the project
907
+ npm run build
908
+
909
+ # Deploy using Wrangler
910
+ npm run deploy
911
+ \`\`\`
912
+
913
+ ### Prerequisites
914
+
915
+ - Wrangler CLI installed (\`npm install -g wrangler\`)
916
+ - Cloudflare account configured (\`wrangler login\`)
917
+ `;
918
+ }
919
+ // Environment variables section
920
+ readme += `
921
+ ## Environment Variables
922
+
923
+ | Variable | Default | Description |
924
+ |----------|---------|-------------|
925
+ | \`PORT\` | 3000 | Application port |
926
+ | \`NODE_ENV\` | development | Environment mode |
927
+ `;
928
+ if (deploymentTarget === 'node' && redisSetup !== 'none') {
929
+ readme += `| \`REDIS_HOST\` | localhost | Redis host (use \`redis\` in Docker) |
930
+ | \`REDIS_PORT\` | 6379 | Redis port |
931
+ | \`REDIS_PASSWORD\` | - | Redis password (set in production) |
932
+ `;
933
+ }
934
+ // GitHub Actions section
935
+ if (enableGitHubActions) {
936
+ readme += `
937
+ ## CI/CD
938
+
939
+ This project includes GitHub Actions workflows:
940
+
941
+ - **ci.yml**: Runs on every push/PR - type checking and tests
942
+ - **e2e.yml**: Runs E2E tests
943
+ - **deploy.yml**: Deploys to ${deploymentTarget === 'node'
944
+ ? 'GitHub Container Registry'
945
+ : deploymentTarget === 'vercel'
946
+ ? 'Vercel'
947
+ : deploymentTarget === 'lambda'
948
+ ? 'AWS Lambda'
949
+ : 'Cloudflare Workers'}
950
+
951
+ ### Required Secrets
952
+ `;
953
+ if (deploymentTarget === 'vercel') {
954
+ readme += `
955
+ - \`VERCEL_TOKEN\`: Vercel API token
956
+ - \`VERCEL_ORG_ID\`: Vercel organization ID
957
+ - \`VERCEL_PROJECT_ID\`: Vercel project ID
958
+ `;
959
+ }
960
+ else if (deploymentTarget === 'lambda') {
961
+ readme += `
962
+ - \`AWS_ACCESS_KEY_ID\`: AWS access key
963
+ - \`AWS_SECRET_ACCESS_KEY\`: AWS secret key
964
+ - \`AWS_REGION\`: AWS region (e.g., us-east-1)
965
+ `;
966
+ }
967
+ else if (deploymentTarget === 'cloudflare') {
968
+ readme += `
969
+ - \`CLOUDFLARE_API_TOKEN\`: Cloudflare API token with Workers permissions
970
+ `;
971
+ }
972
+ else {
973
+ readme += `
974
+ No additional secrets required - uses \`GITHUB_TOKEN\` for GHCR.
975
+ `;
976
+ }
977
+ }
978
+ readme += `
979
+ ## Scripts
980
+
981
+ | Script | Description |
982
+ |--------|-------------|
983
+ | \`npm run dev\` | Start development server with hot reload |
984
+ | \`npm run build\` | Build for production |
985
+ | \`npm run inspect\` | Launch MCP Inspector |
986
+ | \`npm run doctor\` | Check project configuration |
987
+ | \`npm run test\` | Run unit tests |
988
+ | \`npm run test:e2e\` | Run E2E tests |
989
+ `;
990
+ if (deploymentTarget === 'node') {
991
+ readme += `| \`npm run docker:up\` | Start Docker services |
992
+ | \`npm run docker:down\` | Stop Docker services |
993
+ | \`npm run docker:build\` | Rebuild Docker image |
994
+ `;
995
+ }
996
+ if (deploymentTarget === 'lambda' || deploymentTarget === 'cloudflare') {
997
+ readme += `| \`npm run deploy\` | Deploy to ${deploymentTarget === 'lambda' ? 'AWS Lambda' : 'Cloudflare Workers'} |
998
+ `;
999
+ }
1000
+ readme += `
1001
+ ## Project Structure
1002
+
1003
+ \`\`\`
1004
+ `;
1005
+ // Dynamic project structure based on options
1006
+ readme += `├── .env.example # Environment variables template
1007
+ ├── .gitignore # Git ignore rules
1008
+ `;
1009
+ if (deploymentTarget === 'node') {
1010
+ readme += `├── ci/
1011
+ │ ├── Dockerfile # Container build config
1012
+ │ ├── docker-compose.yml # Docker services config
1013
+ │ └── .env.docker # Docker-specific env vars
1014
+ `;
1015
+ }
1016
+ if (deploymentTarget === 'vercel') {
1017
+ readme += `├── vercel.json # Vercel deployment config
1018
+ `;
1019
+ }
1020
+ if (deploymentTarget === 'lambda') {
1021
+ readme += `├── ci/
1022
+ │ └── template.yaml # AWS SAM template
1023
+ `;
1024
+ }
1025
+ if (deploymentTarget === 'cloudflare') {
1026
+ readme += `├── wrangler.toml # Cloudflare Workers config
1027
+ `;
1028
+ }
1029
+ if (enableGitHubActions) {
1030
+ readme += `├── .github/workflows/
1031
+ │ ├── ci.yml # CI workflow
1032
+ │ ├── e2e.yml # E2E test workflow
1033
+ │ └── deploy.yml # Deployment workflow
1034
+ `;
1035
+ }
1036
+ readme += `├── e2e/ # E2E tests
1037
+ ├── jest.e2e.config.ts # Jest E2E configuration
1038
+ ├── package.json # Dependencies and scripts
1039
+ ├── src/
1040
+ │ ├── main.ts # Server entry point
1041
+ │ ├── calc.app.ts # Example app
1042
+ │ └── tools/
1043
+ │ └── add.tool.ts # Example tool
1044
+ └── tsconfig.json # TypeScript config
1045
+ \`\`\`
1046
+
1047
+ ## Learn More
1048
+
1049
+ - [FrontMCP Documentation](https://docs.agentfront.dev)
1050
+ - [MCP Specification](https://modelcontextprotocol.io)
1051
+ `;
1052
+ return readme;
1053
+ }
1054
+ // =============================================================================
1055
+ // Scaffolding Functions
1056
+ // =============================================================================
1057
+ function getDefaults(projectArg) {
1058
+ return {
1059
+ projectName: projectArg || 'frontmcp-app',
1060
+ deploymentTarget: 'node',
1061
+ redisSetup: 'docker',
1062
+ enableGitHubActions: true,
1063
+ };
1064
+ }
1065
+ async function collectOptions(prompt, projectArg, flags) {
1066
+ // Project name
1067
+ let projectName = projectArg;
1068
+ if (!projectName) {
1069
+ projectName = await prompt.ask(`${(0, colors_1.c)('cyan', '?')} Project name: `);
1070
+ if (!projectName) {
1071
+ throw new Error('Project name is required');
1072
+ }
1073
+ }
1074
+ else {
1075
+ console.log(`${(0, colors_1.c)('cyan', '?')} Project name: ${(0, colors_1.c)('bold', projectName)}`);
234
1076
  }
235
- const folder = sanitizeForFolder(projectArg);
236
- const pkgName = sanitizeForNpm(projectArg);
1077
+ // Deployment target
1078
+ const deploymentTarget = flags?.target ||
1079
+ (await prompt.select(`\n${(0, colors_1.c)('cyan', '?')} Select deployment target:`, [
1080
+ { label: 'Node.js (Docker) - Recommended for production', value: 'node' },
1081
+ { label: 'Vercel (Serverless)', value: 'vercel' },
1082
+ { label: 'AWS Lambda', value: 'lambda' },
1083
+ { label: 'Cloudflare Workers', value: 'cloudflare' },
1084
+ ]));
1085
+ // Redis setup (only for Node.js/Docker)
1086
+ let redisSetup = 'none';
1087
+ if (deploymentTarget === 'node') {
1088
+ redisSetup =
1089
+ flags?.redis ||
1090
+ (await prompt.select(`\n${(0, colors_1.c)('cyan', '?')} Redis setup:`, [
1091
+ { label: 'Docker Compose (recommended for development)', value: 'docker' },
1092
+ { label: 'Existing Redis (I have my own Redis)', value: 'existing' },
1093
+ { label: 'None (skip Redis)', value: 'none' },
1094
+ ]));
1095
+ }
1096
+ // GitHub Actions
1097
+ const enableGitHubActions = flags?.cicd ?? (await prompt.confirm(`\n${(0, colors_1.c)('cyan', '?')} Set up GitHub Actions CI/CD?`, true));
1098
+ return {
1099
+ projectName,
1100
+ deploymentTarget,
1101
+ redisSetup,
1102
+ enableGitHubActions,
1103
+ };
1104
+ }
1105
+ async function scaffoldDeploymentFiles(targetDir, options) {
1106
+ const { deploymentTarget, redisSetup, projectName } = options;
1107
+ switch (deploymentTarget) {
1108
+ case 'node': {
1109
+ const ciDir = path.join(targetDir, 'ci');
1110
+ await (0, fs_2.ensureDir)(ciDir);
1111
+ await scaffoldFileIfMissing(targetDir, path.join(ciDir, 'Dockerfile'), TEMPLATE_DOCKERFILE_CI);
1112
+ const dockerCompose = redisSetup === 'docker' ? TEMPLATE_DOCKER_COMPOSE_WITH_REDIS : TEMPLATE_DOCKER_COMPOSE_NO_REDIS;
1113
+ await scaffoldFileIfMissing(targetDir, path.join(ciDir, 'docker-compose.yml'), dockerCompose);
1114
+ await scaffoldFileIfMissing(targetDir, path.join(ciDir, '.env.docker'), TEMPLATE_ENV_DOCKER_CI);
1115
+ break;
1116
+ }
1117
+ case 'vercel':
1118
+ await scaffoldFileIfMissing(targetDir, path.join(targetDir, 'vercel.json'), TEMPLATE_VERCEL_JSON(sanitizeForFolder(projectName)));
1119
+ break;
1120
+ case 'lambda': {
1121
+ const ciDir = path.join(targetDir, 'ci');
1122
+ await (0, fs_2.ensureDir)(ciDir);
1123
+ await scaffoldFileIfMissing(targetDir, path.join(ciDir, 'template.yaml'), TEMPLATE_SAM_YAML(projectName));
1124
+ break;
1125
+ }
1126
+ case 'cloudflare':
1127
+ await scaffoldFileIfMissing(targetDir, path.join(targetDir, 'wrangler.toml'), TEMPLATE_WRANGLER_TOML(sanitizeForFolder(projectName)));
1128
+ break;
1129
+ }
1130
+ // Always create .env.example at root
1131
+ const envExample = deploymentTarget === 'node' && redisSetup !== 'none' ? TEMPLATE_ENV_EXAMPLE : TEMPLATE_ENV_EXAMPLE_BASIC;
1132
+ await scaffoldFileIfMissing(targetDir, path.join(targetDir, '.env.example'), envExample);
1133
+ }
1134
+ // Basic .env.example without Redis
1135
+ const TEMPLATE_ENV_EXAMPLE_BASIC = `
1136
+ # Application
1137
+ PORT=3000
1138
+ NODE_ENV=development
1139
+ `;
1140
+ async function scaffoldGitHubActions(targetDir, deploymentTarget) {
1141
+ const workflowDir = path.join(targetDir, '.github', 'workflows');
1142
+ await (0, fs_2.ensureDir)(workflowDir);
1143
+ // Always create CI and E2E workflows
1144
+ await scaffoldFileIfMissing(targetDir, path.join(workflowDir, 'ci.yml'), TEMPLATE_GH_CI);
1145
+ await scaffoldFileIfMissing(targetDir, path.join(workflowDir, 'e2e.yml'), TEMPLATE_GH_E2E);
1146
+ // Create deployment workflow based on target
1147
+ const deployTemplate = getDeployWorkflowTemplate(deploymentTarget);
1148
+ await scaffoldFileIfMissing(targetDir, path.join(workflowDir, 'deploy.yml'), deployTemplate);
1149
+ }
1150
+ function getDeployWorkflowTemplate(target) {
1151
+ switch (target) {
1152
+ case 'node':
1153
+ return TEMPLATE_GH_DEPLOY_DOCKER;
1154
+ case 'vercel':
1155
+ return TEMPLATE_GH_DEPLOY_VERCEL;
1156
+ case 'lambda':
1157
+ return TEMPLATE_GH_DEPLOY_LAMBDA;
1158
+ case 'cloudflare':
1159
+ return TEMPLATE_GH_DEPLOY_CLOUDFLARE;
1160
+ }
1161
+ }
1162
+ async function scaffoldProject(options) {
1163
+ const { projectName, deploymentTarget, redisSetup, enableGitHubActions } = options;
1164
+ const folder = sanitizeForFolder(projectName);
1165
+ const pkgName = sanitizeForNpm(projectName);
237
1166
  const targetDir = path.resolve(process.cwd(), folder);
1167
+ // Validate directory
238
1168
  try {
239
1169
  const stat = await fs_1.promises.stat(targetDir);
240
1170
  if (!stat.isDirectory()) {
@@ -249,24 +1179,47 @@ async function runCreate(projectArg) {
249
1179
  }
250
1180
  }
251
1181
  catch (e) {
252
- if (e?.code === 'ENOENT') {
1182
+ if (e && typeof e === 'object' && 'code' in e && e.code === 'ENOENT') {
253
1183
  await (0, fs_2.ensureDir)(targetDir);
254
1184
  }
255
1185
  else {
256
1186
  throw e;
257
1187
  }
258
1188
  }
259
- console.log(`${(0, colors_1.c)('cyan', '[create]')} Creating project in ${(0, colors_1.c)('bold', './' + path.relative(process.cwd(), targetDir))}`);
1189
+ console.log(`\n${(0, colors_1.c)('cyan', '[create]')} Creating project in ${(0, colors_1.c)('bold', './' + folder)}`);
1190
+ console.log((0, colors_1.c)('gray', ` Deployment: ${deploymentTarget}`));
1191
+ if (deploymentTarget === 'node') {
1192
+ console.log((0, colors_1.c)('gray', ` Redis: ${redisSetup}`));
1193
+ }
1194
+ console.log((0, colors_1.c)('gray', ` GitHub Actions: ${enableGitHubActions ? 'Yes' : 'No'}`));
1195
+ console.log('');
260
1196
  process.chdir(targetDir);
1197
+ // Initialize tsconfig
261
1198
  await (0, tsconfig_1.runInit)(targetDir);
1199
+ // Create package.json with deployment-specific scripts
262
1200
  const selfVersion = (0, version_1.getSelfVersion)();
263
- await upsertPackageJson(targetDir, pkgName, selfVersion);
1201
+ await upsertPackageJsonWithTarget(targetDir, pkgName, selfVersion, deploymentTarget);
1202
+ // Scaffold base files
264
1203
  await scaffoldFileIfMissing(targetDir, path.join(targetDir, 'src', 'main.ts'), TEMPLATE_MAIN_TS);
265
1204
  await scaffoldFileIfMissing(targetDir, path.join(targetDir, 'src', 'calc.app.ts'), TEMPLATE_CALC_APP_TS);
266
1205
  await scaffoldFileIfMissing(targetDir, path.join(targetDir, 'src', 'tools', 'add.tool.ts'), TEMPLATE_ADD_TOOL_TS);
267
1206
  // E2E scaffolding
268
1207
  await scaffoldFileIfMissing(targetDir, path.join(targetDir, 'e2e', 'server.e2e.test.ts'), TEMPLATE_E2E_TEST_TS);
269
1208
  await scaffoldFileIfMissing(targetDir, path.join(targetDir, 'jest.e2e.config.ts'), TEMPLATE_JEST_E2E_CONFIG);
1209
+ // Git configuration
1210
+ await scaffoldFileIfMissing(targetDir, path.join(targetDir, '.gitignore'), TEMPLATE_GITIGNORE);
1211
+ // Deployment-specific files
1212
+ await scaffoldDeploymentFiles(targetDir, options);
1213
+ // GitHub Actions
1214
+ if (enableGitHubActions) {
1215
+ await scaffoldGitHubActions(targetDir, deploymentTarget);
1216
+ }
1217
+ // Dynamic README
1218
+ await scaffoldFileIfMissing(targetDir, path.join(targetDir, 'README.md'), generateReadme(options));
1219
+ // Print next steps
1220
+ printNextSteps(folder, deploymentTarget, redisSetup, enableGitHubActions);
1221
+ }
1222
+ function printNextSteps(folder, deploymentTarget, redisSetup, enableGitHubActions) {
270
1223
  console.log('\nNext steps:');
271
1224
  console.log(` 1) cd ${folder}`);
272
1225
  console.log(' 2) npm install');
@@ -274,5 +1227,160 @@ async function runCreate(projectArg) {
274
1227
  console.log(' 4) npm run inspect ', (0, colors_1.c)('gray', '# launch MCP Inspector'));
275
1228
  console.log(' 5) npm run build ', (0, colors_1.c)('gray', '# compile with tsc via frontmcp build'));
276
1229
  console.log(' 6) npm run test:e2e ', (0, colors_1.c)('gray', '# run E2E tests'));
1230
+ if (deploymentTarget === 'node') {
1231
+ console.log('');
1232
+ console.log((0, colors_1.c)('cyan', 'Docker:'));
1233
+ console.log(' npm run docker:up ', (0, colors_1.c)('gray', `# start${redisSetup === 'docker' ? ' Redis +' : ''} app in Docker`));
1234
+ console.log(' npm run docker:down ', (0, colors_1.c)('gray', '# stop Docker services'));
1235
+ }
1236
+ if (deploymentTarget === 'vercel') {
1237
+ console.log('');
1238
+ console.log((0, colors_1.c)('cyan', 'Deploy to Vercel:'));
1239
+ console.log(' npx vercel ', (0, colors_1.c)('gray', '# deploy to Vercel'));
1240
+ }
1241
+ if (deploymentTarget === 'lambda') {
1242
+ console.log('');
1243
+ console.log((0, colors_1.c)('cyan', 'Deploy to AWS Lambda:'));
1244
+ console.log(' npm run deploy ', (0, colors_1.c)('gray', '# deploy with SAM'));
1245
+ }
1246
+ if (deploymentTarget === 'cloudflare') {
1247
+ console.log('');
1248
+ console.log((0, colors_1.c)('cyan', 'Deploy to Cloudflare:'));
1249
+ console.log(' npm run deploy ', (0, colors_1.c)('gray', '# deploy with Wrangler'));
1250
+ }
1251
+ if (enableGitHubActions) {
1252
+ console.log('');
1253
+ console.log((0, colors_1.c)('cyan', 'GitHub Actions:'));
1254
+ console.log(' .github/workflows/ ', (0, colors_1.c)('gray', '# CI, E2E, and deploy workflows ready'));
1255
+ }
1256
+ }
1257
+ // =============================================================================
1258
+ // Package.json with Target-Specific Scripts
1259
+ // =============================================================================
1260
+ async function upsertPackageJsonWithTarget(cwd, nameOverride, selfVersion, deploymentTarget) {
1261
+ const pkgPath = path.join(cwd, 'package.json');
1262
+ const existing = await (0, fs_3.readJSON)(pkgPath);
1263
+ const frontmcpLibRange = `~${selfVersion}`;
1264
+ const baseScripts = {
1265
+ dev: 'frontmcp dev',
1266
+ build: 'frontmcp build',
1267
+ inspect: 'frontmcp inspector',
1268
+ doctor: 'frontmcp doctor',
1269
+ test: 'frontmcp test',
1270
+ 'test:e2e': 'jest --config jest.e2e.config.ts --runInBand',
1271
+ };
1272
+ // Add target-specific scripts
1273
+ if (deploymentTarget === 'node') {
1274
+ baseScripts['docker:up'] = 'docker compose -f ci/docker-compose.yml up';
1275
+ baseScripts['docker:down'] = 'docker compose -f ci/docker-compose.yml down';
1276
+ baseScripts['docker:build'] = 'docker compose -f ci/docker-compose.yml build';
1277
+ }
1278
+ if (deploymentTarget === 'lambda') {
1279
+ baseScripts['deploy'] = 'cd ci && sam build && sam deploy';
1280
+ }
1281
+ if (deploymentTarget === 'cloudflare') {
1282
+ baseScripts['deploy'] = 'wrangler deploy';
1283
+ }
1284
+ const base = {
1285
+ name: nameOverride ?? pkgNameFromCwd(cwd),
1286
+ version: '0.1.0',
1287
+ private: true,
1288
+ type: 'commonjs',
1289
+ main: 'src/main.ts',
1290
+ scripts: baseScripts,
1291
+ engines: {
1292
+ node: '>=22',
1293
+ npm: '>=10',
1294
+ },
1295
+ dependencies: {
1296
+ '@frontmcp/sdk': frontmcpLibRange,
1297
+ '@frontmcp/plugins': frontmcpLibRange,
1298
+ '@frontmcp/adapters': frontmcpLibRange,
1299
+ zod: '^4.0.0',
1300
+ 'reflect-metadata': '^0.2.2',
1301
+ },
1302
+ devDependencies: {
1303
+ frontmcp: selfVersion,
1304
+ '@frontmcp/testing': frontmcpLibRange,
1305
+ '@swc/core': '^1.11.29',
1306
+ '@swc/jest': '^0.2.37',
1307
+ jest: '^29.7.0',
1308
+ '@types/jest': '^29.5.14',
1309
+ tsx: '^4.20.6',
1310
+ '@types/node': '^24.0.0',
1311
+ typescript: '^5.5.3',
1312
+ },
1313
+ };
1314
+ if (!existing) {
1315
+ await (0, fs_2.writeJSON)(pkgPath, base);
1316
+ console.log((0, colors_1.c)('green', '✅ Created package.json (synced @frontmcp libs to CLI version + exact frontmcp)'));
1317
+ return;
1318
+ }
1319
+ const merged = { ...base, ...existing };
1320
+ merged.name = existing.name || base.name;
1321
+ merged.main = existing.main || base.main;
1322
+ merged.type = existing.type || base.type;
1323
+ // Preserve user scripts, add base scripts only if missing
1324
+ merged.scripts = {
1325
+ ...baseScripts,
1326
+ ...(existing.scripts || {}),
1327
+ };
1328
+ merged.engines = {
1329
+ ...(existing.engines || {}),
1330
+ node: existing.engines?.node || base.engines.node,
1331
+ npm: existing.engines?.npm || base.engines.npm,
1332
+ };
1333
+ merged.dependencies = {
1334
+ ...(existing.dependencies || {}),
1335
+ ...base.dependencies,
1336
+ };
1337
+ merged.devDependencies = {
1338
+ ...(existing.devDependencies || {}),
1339
+ ...base.devDependencies,
1340
+ };
1341
+ await (0, fs_2.writeJSON)(pkgPath, merged);
1342
+ console.log((0, colors_1.c)('green', '✅ Updated package.json (synced @frontmcp libs + frontmcp to current CLI version)'));
1343
+ }
1344
+ // =============================================================================
1345
+ // Main Entry Point
1346
+ // =============================================================================
1347
+ async function runCreate(projectArg, flags) {
1348
+ // Non-interactive mode: use --yes flag or non-TTY environment
1349
+ if (flags?.yes || !isInteractive()) {
1350
+ const options = getDefaults(projectArg);
1351
+ // Override defaults with any provided flags
1352
+ if (flags?.target)
1353
+ options.deploymentTarget = flags.target;
1354
+ if (flags?.redis)
1355
+ options.redisSetup = flags.redis;
1356
+ if (flags?.cicd !== undefined)
1357
+ options.enableGitHubActions = flags.cicd;
1358
+ if (projectArg)
1359
+ options.projectName = projectArg;
1360
+ if (!options.projectName) {
1361
+ console.error((0, colors_1.c)('red', 'Error: project name is required in non-interactive mode.\n'));
1362
+ console.log(`Usage: ${(0, colors_1.c)('bold', 'npx frontmcp create <project-name> --yes')}`);
1363
+ process.exit(1);
1364
+ }
1365
+ await scaffoldProject(options);
1366
+ return;
1367
+ }
1368
+ // Interactive mode
1369
+ console.log(`\n${(0, colors_1.c)('bold', 'Create a new FrontMCP project')}\n`);
1370
+ const prompt = createPrompt();
1371
+ try {
1372
+ const options = await collectOptions(prompt, projectArg, flags);
1373
+ await scaffoldProject(options);
1374
+ }
1375
+ catch (err) {
1376
+ if (err instanceof Error && err.message === 'Project name is required') {
1377
+ console.error((0, colors_1.c)('red', '\nError: Project name is required.'));
1378
+ process.exit(1);
1379
+ }
1380
+ throw err;
1381
+ }
1382
+ finally {
1383
+ prompt.close();
1384
+ }
277
1385
  }
278
1386
  //# sourceMappingURL=create.js.map