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.
- package/package.json +1 -1
- package/src/args.d.ts +7 -0
- package/src/args.js +13 -0
- package/src/args.js.map +1 -1
- package/src/cli.js +17 -3
- package/src/cli.js.map +1 -1
- package/src/commands/build/adapters/cloudflare.d.ts +8 -0
- package/src/commands/build/adapters/cloudflare.js +80 -0
- package/src/commands/build/adapters/cloudflare.js.map +1 -0
- package/src/commands/build/adapters/index.d.ts +12 -0
- package/src/commands/build/adapters/index.js +23 -0
- package/src/commands/build/adapters/index.js.map +1 -0
- package/src/commands/build/adapters/lambda.d.ts +11 -0
- package/src/commands/build/adapters/lambda.js +43 -0
- package/src/commands/build/adapters/lambda.js.map +1 -0
- package/src/commands/build/adapters/node.d.ts +7 -0
- package/src/commands/build/adapters/node.js +13 -0
- package/src/commands/build/adapters/node.js.map +1 -0
- package/src/commands/build/adapters/vercel.d.ts +8 -0
- package/src/commands/build/adapters/vercel.js +36 -0
- package/src/commands/build/adapters/vercel.js.map +1 -0
- package/src/commands/build/index.d.ts +22 -0
- package/src/commands/build/index.js +120 -0
- package/src/commands/build/index.js.map +1 -0
- package/src/commands/build/types.d.ts +23 -0
- package/src/commands/build/types.js +3 -0
- package/src/commands/build/types.js.map +1 -0
- package/src/commands/create.d.ts +15 -1
- package/src/commands/create.js +1199 -91
- package/src/commands/create.js.map +1 -1
- package/src/tsconfig.d.ts +1 -1
- package/src/tsconfig.js +1 -1
- package/src/tsconfig.js.map +1 -1
- package/src/commands/build.d.ts +0 -2
- package/src/commands/build.js +0 -43
- package/src/commands/build.js.map +0 -1
package/src/commands/create.js
CHANGED
|
@@ -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
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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
|
+

|
|
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
|
-
|
|
236
|
-
const
|
|
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
|
|
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(
|
|
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
|
|
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
|