frontmcp 0.6.0 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -14,6 +14,7 @@ _Made with ❤️ for TypeScript developers_
14
14
  [![NPM - @frontmcp/sdk](https://img.shields.io/npm/v/@frontmcp/sdk.svg?v=2)](https://www.npmjs.com/package/@frontmcp/sdk)
15
15
  [![Node](https://img.shields.io/badge/node-%3E%3D22-339933?logo=node.js&logoColor=white)](https://nodejs.org)
16
16
  [![License](https://img.shields.io/github/license/agentfront/frontmcp.svg?v=1)](https://github.com/agentfront/frontmcp/blob/main/LICENSE)
17
+ [![Snyk](https://snyk.io/test/github/agentfront/frontmcp/badge.svg)](https://snyk.io/test/github/agentfront/frontmcp)
17
18
 
18
19
  </div>
19
20
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "frontmcp",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
4
4
  "description": "FrontMCP command line interface",
5
5
  "author": "AgentFront <info@agentfront.dev>",
6
6
  "homepage": "https://docs.agentfront.dev",
@@ -26,13 +26,14 @@
26
26
  "frontmcp": "./src/cli.js"
27
27
  },
28
28
  "dependencies": {
29
- "tslib": "^2.3.0"
29
+ "tslib": "^2.3.0",
30
+ "@rspack/core": "^1.3.12"
30
31
  },
31
32
  "devDependencies": {
32
33
  "typescript": "^5.5.3",
33
34
  "tsx": "^4.20.6",
34
35
  "@types/node": "^24.0.0",
35
- "@modelcontextprotocol/inspector": "^0.17.2"
36
+ "@modelcontextprotocol/inspector": "^0.18.0"
36
37
  },
37
38
  "types": "./src/index.d.ts",
38
39
  "type": "commonjs"
@@ -71,7 +71,7 @@ module.exports = {
71
71
  }
72
72
  };
73
73
  `,
74
- getConfig: () => `name = "frontmcp-worker"
74
+ getConfig: (_cwd) => `name = "frontmcp-worker"
75
75
  main = "dist/index.js"
76
76
  compatibility_date = "2024-01-01"
77
77
  `,
@@ -1 +1 @@
1
- {"version":3,"file":"cloudflare.js","sourceRoot":"","sources":["../../../../../src/commands/build/adapters/cloudflare.ts"],"names":[],"mappings":";;;AAEA;;;;;GAKG;AACU,QAAA,iBAAiB,GAAoB;IAChD,YAAY,EAAE,UAAU;IAExB,gBAAgB,EAAE,CAAC,cAAsB,EAAE,EAAE,CAAC;;;;WAIrC,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyDxB;IAEC,SAAS,EAAE,GAAG,EAAE,CAAC;;;CAGlB;IAEC,cAAc,EAAE,eAAe;CAChC,CAAC","sourcesContent":["import { AdapterTemplate } from '../types';\n\n/**\n * Cloudflare Workers adapter - edge deployment on Cloudflare.\n * Compiles to CommonJS and adapts the Express app to Cloudflare's fetch API.\n *\n * @see https://developers.cloudflare.com/workers/\n */\nexport const cloudflareAdapter: AdapterTemplate = {\n moduleFormat: 'commonjs',\n\n getEntryTemplate: (mainModulePath: string) => `// Auto-generated Cloudflare Workers entry point\n// Generated by: frontmcp build --adapter cloudflare\nprocess.env.FRONTMCP_SERVERLESS = '1';\n\nrequire('${mainModulePath}');\nconst { getServerlessHandlerAsync } = require('@frontmcp/sdk');\n\nlet app = null;\n\nasync function handleRequest(request) {\n if (!app) {\n app = await getServerlessHandlerAsync();\n }\n\n // Convert Cloudflare Request to Node.js format\n const url = new URL(request.url);\n const req = {\n method: request.method,\n url: url.pathname + url.search,\n headers: Object.fromEntries(request.headers),\n body: request.body,\n };\n\n return new Promise((resolve) => {\n const res = {\n statusCode: 200,\n headers: {},\n body: '',\n status(code) { this.statusCode = code; return this; },\n setHeader(key, value) { this.headers[key] = value; },\n json(data) {\n this.headers['Content-Type'] = 'application/json';\n this.body = JSON.stringify(data);\n resolve(new Response(this.body, {\n status: this.statusCode,\n headers: this.headers,\n }));\n },\n send(data) {\n this.body = data;\n resolve(new Response(this.body, {\n status: this.statusCode,\n headers: this.headers,\n }));\n },\n end() {\n resolve(new Response(this.body, {\n status: this.statusCode,\n headers: this.headers,\n }));\n },\n };\n app(req, res);\n });\n}\n\nmodule.exports = {\n async fetch(request, env, ctx) {\n return handleRequest(request);\n }\n};\n`,\n\n getConfig: () => `name = \"frontmcp-worker\"\nmain = \"dist/index.js\"\ncompatibility_date = \"2024-01-01\"\n`,\n\n configFileName: 'wrangler.toml',\n};\n"]}
1
+ {"version":3,"file":"cloudflare.js","sourceRoot":"","sources":["../../../../../src/commands/build/adapters/cloudflare.ts"],"names":[],"mappings":";;;AAEA;;;;;GAKG;AACU,QAAA,iBAAiB,GAAoB;IAChD,YAAY,EAAE,UAAU;IAExB,gBAAgB,EAAE,CAAC,cAAsB,EAAE,EAAE,CAAC;;;;WAIrC,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyDxB;IAEC,SAAS,EAAE,CAAC,IAAY,EAAE,EAAE,CAAC;;;CAG9B;IAEC,cAAc,EAAE,eAAe;CAChC,CAAC","sourcesContent":["import { AdapterTemplate } from '../types';\n\n/**\n * Cloudflare Workers adapter - edge deployment on Cloudflare.\n * Compiles to CommonJS and adapts the Express app to Cloudflare's fetch API.\n *\n * @see https://developers.cloudflare.com/workers/\n */\nexport const cloudflareAdapter: AdapterTemplate = {\n moduleFormat: 'commonjs',\n\n getEntryTemplate: (mainModulePath: string) => `// Auto-generated Cloudflare Workers entry point\n// Generated by: frontmcp build --adapter cloudflare\nprocess.env.FRONTMCP_SERVERLESS = '1';\n\nrequire('${mainModulePath}');\nconst { getServerlessHandlerAsync } = require('@frontmcp/sdk');\n\nlet app = null;\n\nasync function handleRequest(request) {\n if (!app) {\n app = await getServerlessHandlerAsync();\n }\n\n // Convert Cloudflare Request to Node.js format\n const url = new URL(request.url);\n const req = {\n method: request.method,\n url: url.pathname + url.search,\n headers: Object.fromEntries(request.headers),\n body: request.body,\n };\n\n return new Promise((resolve) => {\n const res = {\n statusCode: 200,\n headers: {},\n body: '',\n status(code) { this.statusCode = code; return this; },\n setHeader(key, value) { this.headers[key] = value; },\n json(data) {\n this.headers['Content-Type'] = 'application/json';\n this.body = JSON.stringify(data);\n resolve(new Response(this.body, {\n status: this.statusCode,\n headers: this.headers,\n }));\n },\n send(data) {\n this.body = data;\n resolve(new Response(this.body, {\n status: this.statusCode,\n headers: this.headers,\n }));\n },\n end() {\n resolve(new Response(this.body, {\n status: this.statusCode,\n headers: this.headers,\n }));\n },\n };\n app(req, res);\n });\n}\n\nmodule.exports = {\n async fetch(request, env, ctx) {\n return handleRequest(request);\n }\n};\n`,\n\n getConfig: (_cwd: string) => `name = \"frontmcp-worker\"\nmain = \"dist/index.js\"\ncompatibility_date = \"2024-01-01\"\n`,\n\n configFileName: 'wrangler.toml',\n};\n"]}
@@ -1,11 +1,17 @@
1
1
  import { AdapterTemplate } from '../types';
2
2
  /**
3
3
  * AWS Lambda adapter - serverless deployment on AWS Lambda.
4
- * Compiles to ESM and uses @codegenie/serverless-express to wrap the Express app.
4
+ * Compiles to ESM, bundles with rspack to CJS for maximum compatibility.
5
5
  *
6
6
  * Prerequisites:
7
7
  * npm install @codegenie/serverless-express
8
8
  *
9
+ * The build process:
10
+ * 1. TypeScript compiles to ESM in dist/
11
+ * 2. serverless-setup.js is generated (sets FRONTMCP_SERVERLESS=1)
12
+ * 3. index.js imports setup first, then main module
13
+ * 4. rspack bundles everything into handler.cjs
14
+ *
9
15
  * @see https://github.com/codegenie/serverless-express
10
16
  */
11
17
  export declare const lambdaAdapter: AdapterTemplate;
@@ -3,23 +3,35 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.lambdaAdapter = void 0;
4
4
  /**
5
5
  * AWS Lambda adapter - serverless deployment on AWS Lambda.
6
- * Compiles to ESM and uses @codegenie/serverless-express to wrap the Express app.
6
+ * Compiles to ESM, bundles with rspack to CJS for maximum compatibility.
7
7
  *
8
8
  * Prerequisites:
9
9
  * npm install @codegenie/serverless-express
10
10
  *
11
+ * The build process:
12
+ * 1. TypeScript compiles to ESM in dist/
13
+ * 2. serverless-setup.js is generated (sets FRONTMCP_SERVERLESS=1)
14
+ * 3. index.js imports setup first, then main module
15
+ * 4. rspack bundles everything into handler.cjs
16
+ *
11
17
  * @see https://github.com/codegenie/serverless-express
12
18
  */
13
19
  exports.lambdaAdapter = {
14
20
  moduleFormat: 'esnext',
21
+ shouldBundle: true,
22
+ bundleOutput: 'handler.cjs',
23
+ getSetupTemplate: () => `// Serverless environment setup - MUST be imported first
24
+ // This sets FRONTMCP_SERVERLESS before any decorators run
25
+ // Required because ESM hoists imports before other statements
26
+ process.env.FRONTMCP_SERVERLESS = '1';
27
+ `,
15
28
  getEntryTemplate: (mainModulePath) => `// Auto-generated AWS Lambda entry point
16
29
  // Generated by: frontmcp build --adapter lambda
17
30
  //
18
31
  // IMPORTANT: This adapter requires @codegenie/serverless-express
19
32
  // Install it with: npm install @codegenie/serverless-express
20
33
  //
21
- process.env.FRONTMCP_SERVERLESS = '1';
22
-
34
+ import './serverless-setup.js';
23
35
  import '${mainModulePath}';
24
36
  import { getServerlessHandlerAsync } from '@frontmcp/sdk';
25
37
  import serverlessExpress from '@codegenie/serverless-express';
@@ -1 +1 @@
1
- {"version":3,"file":"lambda.js","sourceRoot":"","sources":["../../../../../src/commands/build/adapters/lambda.ts"],"names":[],"mappings":";;;AAEA;;;;;;;;GAQG;AACU,QAAA,aAAa,GAAoB;IAC5C,YAAY,EAAE,QAAQ;IAEtB,gBAAgB,EAAE,CAAC,cAAsB,EAAE,EAAE,CAAC;;;;;;;;UAQtC,cAAc;;;;;;;;;;;;;;;;;CAiBvB;IAEC,qEAAqE;CACtE,CAAC","sourcesContent":["import { AdapterTemplate } from '../types';\n\n/**\n * AWS Lambda adapter - serverless deployment on AWS Lambda.\n * Compiles to ESM and uses @codegenie/serverless-express to wrap the Express app.\n *\n * Prerequisites:\n * npm install @codegenie/serverless-express\n *\n * @see https://github.com/codegenie/serverless-express\n */\nexport const lambdaAdapter: AdapterTemplate = {\n moduleFormat: 'esnext',\n\n getEntryTemplate: (mainModulePath: string) => `// Auto-generated AWS Lambda entry point\n// Generated by: frontmcp build --adapter lambda\n//\n// IMPORTANT: This adapter requires @codegenie/serverless-express\n// Install it with: npm install @codegenie/serverless-express\n//\nprocess.env.FRONTMCP_SERVERLESS = '1';\n\nimport '${mainModulePath}';\nimport { getServerlessHandlerAsync } from '@frontmcp/sdk';\nimport serverlessExpress from '@codegenie/serverless-express';\n\nlet serverlessExpressInstance = null;\n\nasync function setup() {\n const app = await getServerlessHandlerAsync();\n serverlessExpressInstance = serverlessExpress({ app });\n}\n\nexport const handler = async (event, context) => {\n if (!serverlessExpressInstance) {\n await setup();\n }\n return serverlessExpressInstance(event, context);\n};\n`,\n\n // No config file - user manages serverless.yml, SAM template, or CDK\n};\n"]}
1
+ {"version":3,"file":"lambda.js","sourceRoot":"","sources":["../../../../../src/commands/build/adapters/lambda.ts"],"names":[],"mappings":";;;AAEA;;;;;;;;;;;;;;GAcG;AACU,QAAA,aAAa,GAAoB;IAC5C,YAAY,EAAE,QAAQ;IACtB,YAAY,EAAE,IAAI;IAClB,YAAY,EAAE,aAAa;IAE3B,gBAAgB,EAAE,GAAG,EAAE,CAAC;;;;CAIzB;IAEC,gBAAgB,EAAE,CAAC,cAAsB,EAAE,EAAE,CAAC;;;;;;;UAOtC,cAAc;;;;;;;;;;;;;;;;;CAiBvB;IAEC,qEAAqE;CACtE,CAAC","sourcesContent":["import { AdapterTemplate } from '../types';\n\n/**\n * AWS Lambda adapter - serverless deployment on AWS Lambda.\n * Compiles to ESM, bundles with rspack to CJS for maximum compatibility.\n *\n * Prerequisites:\n * npm install @codegenie/serverless-express\n *\n * The build process:\n * 1. TypeScript compiles to ESM in dist/\n * 2. serverless-setup.js is generated (sets FRONTMCP_SERVERLESS=1)\n * 3. index.js imports setup first, then main module\n * 4. rspack bundles everything into handler.cjs\n *\n * @see https://github.com/codegenie/serverless-express\n */\nexport const lambdaAdapter: AdapterTemplate = {\n moduleFormat: 'esnext',\n shouldBundle: true,\n bundleOutput: 'handler.cjs',\n\n getSetupTemplate: () => `// Serverless environment setup - MUST be imported first\n// This sets FRONTMCP_SERVERLESS before any decorators run\n// Required because ESM hoists imports before other statements\nprocess.env.FRONTMCP_SERVERLESS = '1';\n`,\n\n getEntryTemplate: (mainModulePath: string) => `// Auto-generated AWS Lambda entry point\n// Generated by: frontmcp build --adapter lambda\n//\n// IMPORTANT: This adapter requires @codegenie/serverless-express\n// Install it with: npm install @codegenie/serverless-express\n//\nimport './serverless-setup.js';\nimport '${mainModulePath}';\nimport { getServerlessHandlerAsync } from '@frontmcp/sdk';\nimport serverlessExpress from '@codegenie/serverless-express';\n\nlet serverlessExpressInstance = null;\n\nasync function setup() {\n const app = await getServerlessHandlerAsync();\n serverlessExpressInstance = serverlessExpress({ app });\n}\n\nexport const handler = async (event, context) => {\n if (!serverlessExpressInstance) {\n await setup();\n }\n return serverlessExpressInstance(event, context);\n};\n`,\n\n // No config file - user manages serverless.yml, SAM template, or CDK\n};\n"]}
@@ -1,8 +1,19 @@
1
1
  import { AdapterTemplate } from '../types';
2
2
  /**
3
3
  * Vercel adapter - serverless deployment on Vercel.
4
- * Compiles to ESM and generates a handler that exports the Express app.
4
+ * Compiles to ESM, bundles with rspack to CJS for maximum compatibility.
5
5
  *
6
- * @see https://vercel.com/docs/frameworks/express
6
+ * Uses Vercel Build Output API for deployment:
7
+ * - Creates .vercel/output/config.json with routing
8
+ * - Creates .vercel/output/functions/index.func/ with handler
9
+ *
10
+ * The build process:
11
+ * 1. TypeScript compiles to ESM in dist/
12
+ * 2. serverless-setup.js is generated (sets FRONTMCP_SERVERLESS=1)
13
+ * 3. index.js imports setup first, then main module
14
+ * 4. rspack bundles everything into handler.cjs
15
+ * 5. Build Output API structure is created in .vercel/output/
16
+ *
17
+ * @see https://vercel.com/docs/build-output-api/v3
7
18
  */
8
19
  export declare const vercelAdapter: AdapterTemplate;
@@ -1,18 +1,63 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.vercelAdapter = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const path = tslib_1.__importStar(require("path"));
6
+ const fs = tslib_1.__importStar(require("fs/promises"));
7
+ const fs_1 = require("fs");
8
+ const PACKAGE_MANAGERS = {
9
+ bun: { install: 'bun install', run: 'bun run build' },
10
+ pnpm: { install: 'pnpm install', run: 'pnpm run build' },
11
+ yarn: { install: 'yarn install', run: 'yarn build' },
12
+ npm: { install: 'npm install', run: 'npm run build' },
13
+ };
14
+ const LOCKFILE_TO_PM = {
15
+ 'bun.lockb': 'bun',
16
+ 'pnpm-lock.yaml': 'pnpm',
17
+ 'yarn.lock': 'yarn',
18
+ 'package-lock.json': 'npm',
19
+ };
20
+ /**
21
+ * Detect package manager based on lockfile presence.
22
+ * Priority: bun > pnpm > yarn > npm (fastest to slowest install times)
23
+ */
24
+ function detectPackageManager(cwd) {
25
+ for (const [lockfile, pm] of Object.entries(LOCKFILE_TO_PM)) {
26
+ if ((0, fs_1.existsSync)(path.join(cwd, lockfile))) {
27
+ return pm;
28
+ }
29
+ }
30
+ return 'npm'; // Default fallback
31
+ }
4
32
  /**
5
33
  * Vercel adapter - serverless deployment on Vercel.
6
- * Compiles to ESM and generates a handler that exports the Express app.
34
+ * Compiles to ESM, bundles with rspack to CJS for maximum compatibility.
35
+ *
36
+ * Uses Vercel Build Output API for deployment:
37
+ * - Creates .vercel/output/config.json with routing
38
+ * - Creates .vercel/output/functions/index.func/ with handler
7
39
  *
8
- * @see https://vercel.com/docs/frameworks/express
40
+ * The build process:
41
+ * 1. TypeScript compiles to ESM in dist/
42
+ * 2. serverless-setup.js is generated (sets FRONTMCP_SERVERLESS=1)
43
+ * 3. index.js imports setup first, then main module
44
+ * 4. rspack bundles everything into handler.cjs
45
+ * 5. Build Output API structure is created in .vercel/output/
46
+ *
47
+ * @see https://vercel.com/docs/build-output-api/v3
9
48
  */
10
49
  exports.vercelAdapter = {
11
50
  moduleFormat: 'esnext',
51
+ shouldBundle: true,
52
+ bundleOutput: 'handler.cjs',
53
+ getSetupTemplate: () => `// Serverless environment setup - MUST be imported first
54
+ // This sets FRONTMCP_SERVERLESS before any decorators run
55
+ // Required because ESM hoists imports before other statements
56
+ process.env.FRONTMCP_SERVERLESS = '1';
57
+ `,
12
58
  getEntryTemplate: (mainModulePath) => `// Auto-generated Vercel entry point
13
59
  // Generated by: frontmcp build --adapter vercel
14
- process.env.FRONTMCP_SERVERLESS = '1';
15
-
60
+ import './serverless-setup.js';
16
61
  import '${mainModulePath}';
17
62
  import { getServerlessHandlerAsync } from '@frontmcp/sdk';
18
63
 
@@ -26,11 +71,63 @@ export default async function handler(req, res) {
26
71
  return app(req, res);
27
72
  }
28
73
  `,
29
- getConfig: () => ({
30
- version: 2,
31
- builds: [{ src: 'dist/index.js', use: '@vercel/node' }],
32
- routes: [{ src: '/(.*)', dest: '/dist/index.js' }],
33
- }),
74
+ // Detect package manager and generate appropriate vercel.json
75
+ getConfig: (cwd) => {
76
+ const pm = detectPackageManager(cwd);
77
+ const config = PACKAGE_MANAGERS[pm];
78
+ return {
79
+ version: 2,
80
+ buildCommand: config.run,
81
+ installCommand: config.install,
82
+ };
83
+ },
34
84
  configFileName: 'vercel.json',
85
+ /**
86
+ * Create Vercel Build Output API structure after bundling.
87
+ * This allows Vercel to deploy the function without needing an /api folder.
88
+ *
89
+ * Structure created:
90
+ * .vercel/output/
91
+ * ├── config.json (routes all requests to index function)
92
+ * └── functions/
93
+ * └── index.func/
94
+ * ├── .vc-config.json (Node.js 22 runtime config)
95
+ * └── handler.cjs (bundled handler + chunks)
96
+ */
97
+ postBundle: async (outDir, cwd, bundleOutput) => {
98
+ const outputDir = path.join(cwd, '.vercel', 'output');
99
+ const funcDir = path.join(outputDir, 'functions', 'index.func');
100
+ // Create directories
101
+ await fs.mkdir(funcDir, { recursive: true });
102
+ // Copy all files from dist to the function directory
103
+ // This includes handler.cjs and any chunk files (*.handler.cjs)
104
+ const distFiles = await fs.readdir(outDir);
105
+ for (const file of distFiles) {
106
+ const srcPath = path.join(outDir, file);
107
+ const destPath = path.join(funcDir, file);
108
+ const stat = await fs.stat(srcPath);
109
+ if (stat.isDirectory()) {
110
+ // Recursively copy directories
111
+ await fs.cp(srcPath, destPath, { recursive: true });
112
+ }
113
+ else {
114
+ // Copy files
115
+ await fs.copyFile(srcPath, destPath);
116
+ }
117
+ }
118
+ // Create function config (.vc-config.json)
119
+ const vcConfig = {
120
+ runtime: 'nodejs22.x',
121
+ handler: bundleOutput,
122
+ launcherType: 'Nodejs',
123
+ };
124
+ await fs.writeFile(path.join(funcDir, '.vc-config.json'), JSON.stringify(vcConfig, null, 2));
125
+ // Create output config (config.json) with routing
126
+ const outputConfig = {
127
+ version: 3,
128
+ routes: [{ src: '/(.*)', dest: '/index' }],
129
+ };
130
+ await fs.writeFile(path.join(outputDir, 'config.json'), JSON.stringify(outputConfig, null, 2));
131
+ },
35
132
  };
36
133
  //# sourceMappingURL=vercel.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"vercel.js","sourceRoot":"","sources":["../../../../../src/commands/build/adapters/vercel.ts"],"names":[],"mappings":";;;AAEA;;;;;GAKG;AACU,QAAA,aAAa,GAAoB;IAC5C,YAAY,EAAE,QAAQ;IAEtB,gBAAgB,EAAE,CAAC,cAAsB,EAAE,EAAE,CAAC;;;;UAItC,cAAc;;;;;;;;;;;;CAYvB;IAEC,SAAS,EAAE,GAAG,EAAE,CAAC,CAAC;QAChB,OAAO,EAAE,CAAC;QACV,MAAM,EAAE,CAAC,EAAE,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,EAAE,CAAC;QACvD,MAAM,EAAE,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,gBAAgB,EAAE,CAAC;KACnD,CAAC;IAEF,cAAc,EAAE,aAAa;CAC9B,CAAC","sourcesContent":["import { AdapterTemplate } from '../types';\n\n/**\n * Vercel adapter - serverless deployment on Vercel.\n * Compiles to ESM and generates a handler that exports the Express app.\n *\n * @see https://vercel.com/docs/frameworks/express\n */\nexport const vercelAdapter: AdapterTemplate = {\n moduleFormat: 'esnext',\n\n getEntryTemplate: (mainModulePath: string) => `// Auto-generated Vercel entry point\n// Generated by: frontmcp build --adapter vercel\nprocess.env.FRONTMCP_SERVERLESS = '1';\n\nimport '${mainModulePath}';\nimport { getServerlessHandlerAsync } from '@frontmcp/sdk';\n\nlet handlerPromise = null;\n\nexport default async function handler(req, res) {\n if (!handlerPromise) {\n handlerPromise = getServerlessHandlerAsync();\n }\n const app = await handlerPromise;\n return app(req, res);\n}\n`,\n\n getConfig: () => ({\n version: 2,\n builds: [{ src: 'dist/index.js', use: '@vercel/node' }],\n routes: [{ src: '/(.*)', dest: '/dist/index.js' }],\n }),\n\n configFileName: 'vercel.json',\n};\n"]}
1
+ {"version":3,"file":"vercel.js","sourceRoot":"","sources":["../../../../../src/commands/build/adapters/vercel.ts"],"names":[],"mappings":"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","sourcesContent":["import * as path from 'path';\nimport * as fs from 'fs/promises';\nimport { existsSync } from 'fs';\nimport { AdapterTemplate } from '../types';\n\ntype PackageManager = 'npm' | 'yarn' | 'pnpm' | 'bun';\n\ninterface PackageManagerConfig {\n install: string;\n run: string;\n}\n\nconst PACKAGE_MANAGERS: Record<PackageManager, PackageManagerConfig> = {\n bun: { install: 'bun install', run: 'bun run build' },\n pnpm: { install: 'pnpm install', run: 'pnpm run build' },\n yarn: { install: 'yarn install', run: 'yarn build' },\n npm: { install: 'npm install', run: 'npm run build' },\n};\n\nconst LOCKFILE_TO_PM: Record<string, PackageManager> = {\n 'bun.lockb': 'bun',\n 'pnpm-lock.yaml': 'pnpm',\n 'yarn.lock': 'yarn',\n 'package-lock.json': 'npm',\n};\n\n/**\n * Detect package manager based on lockfile presence.\n * Priority: bun > pnpm > yarn > npm (fastest to slowest install times)\n */\nfunction detectPackageManager(cwd: string): PackageManager {\n for (const [lockfile, pm] of Object.entries(LOCKFILE_TO_PM)) {\n if (existsSync(path.join(cwd, lockfile))) {\n return pm;\n }\n }\n return 'npm'; // Default fallback\n}\n\n/**\n * Vercel adapter - serverless deployment on Vercel.\n * Compiles to ESM, bundles with rspack to CJS for maximum compatibility.\n *\n * Uses Vercel Build Output API for deployment:\n * - Creates .vercel/output/config.json with routing\n * - Creates .vercel/output/functions/index.func/ with handler\n *\n * The build process:\n * 1. TypeScript compiles to ESM in dist/\n * 2. serverless-setup.js is generated (sets FRONTMCP_SERVERLESS=1)\n * 3. index.js imports setup first, then main module\n * 4. rspack bundles everything into handler.cjs\n * 5. Build Output API structure is created in .vercel/output/\n *\n * @see https://vercel.com/docs/build-output-api/v3\n */\nexport const vercelAdapter: AdapterTemplate = {\n moduleFormat: 'esnext',\n shouldBundle: true,\n bundleOutput: 'handler.cjs',\n\n getSetupTemplate: () => `// Serverless environment setup - MUST be imported first\n// This sets FRONTMCP_SERVERLESS before any decorators run\n// Required because ESM hoists imports before other statements\nprocess.env.FRONTMCP_SERVERLESS = '1';\n`,\n\n getEntryTemplate: (mainModulePath: string) => `// Auto-generated Vercel entry point\n// Generated by: frontmcp build --adapter vercel\nimport './serverless-setup.js';\nimport '${mainModulePath}';\nimport { getServerlessHandlerAsync } from '@frontmcp/sdk';\n\nlet handlerPromise = null;\n\nexport default async function handler(req, res) {\n if (!handlerPromise) {\n handlerPromise = getServerlessHandlerAsync();\n }\n const app = await handlerPromise;\n return app(req, res);\n}\n`,\n\n // Detect package manager and generate appropriate vercel.json\n getConfig: (cwd: string) => {\n const pm = detectPackageManager(cwd);\n const config = PACKAGE_MANAGERS[pm];\n return {\n version: 2,\n buildCommand: config.run,\n installCommand: config.install,\n };\n },\n\n configFileName: 'vercel.json',\n\n /**\n * Create Vercel Build Output API structure after bundling.\n * This allows Vercel to deploy the function without needing an /api folder.\n *\n * Structure created:\n * .vercel/output/\n * ├── config.json (routes all requests to index function)\n * └── functions/\n * └── index.func/\n * ├── .vc-config.json (Node.js 22 runtime config)\n * └── handler.cjs (bundled handler + chunks)\n */\n postBundle: async (outDir: string, cwd: string, bundleOutput: string) => {\n const outputDir = path.join(cwd, '.vercel', 'output');\n const funcDir = path.join(outputDir, 'functions', 'index.func');\n\n // Create directories\n await fs.mkdir(funcDir, { recursive: true });\n\n // Copy all files from dist to the function directory\n // This includes handler.cjs and any chunk files (*.handler.cjs)\n const distFiles = await fs.readdir(outDir);\n for (const file of distFiles) {\n const srcPath = path.join(outDir, file);\n const destPath = path.join(funcDir, file);\n const stat = await fs.stat(srcPath);\n\n if (stat.isDirectory()) {\n // Recursively copy directories\n await fs.cp(srcPath, destPath, { recursive: true });\n } else {\n // Copy files\n await fs.copyFile(srcPath, destPath);\n }\n }\n\n // Create function config (.vc-config.json)\n const vcConfig = {\n runtime: 'nodejs22.x',\n handler: bundleOutput,\n launcherType: 'Nodejs',\n };\n await fs.writeFile(\n path.join(funcDir, '.vc-config.json'),\n JSON.stringify(vcConfig, null, 2),\n );\n\n // Create output config (config.json) with routing\n const outputConfig = {\n version: 3,\n routes: [{ src: '/(.*)', dest: '/index' }],\n };\n await fs.writeFile(\n path.join(outputDir, 'config.json'),\n JSON.stringify(outputConfig, null, 2),\n );\n },\n};\n"]}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Bundle the serverless entry point into a single CJS file using rspack.
3
+ * This resolves ESM/CJS compatibility issues and dynamic import problems.
4
+ *
5
+ * @param entryPath - Absolute path to the entry file (e.g., dist/index.js)
6
+ * @param outDir - Output directory for the bundled file
7
+ * @param outputFilename - Name of the output bundle (e.g., 'handler.cjs')
8
+ */
9
+ export declare function bundleForServerless(entryPath: string, outDir: string, outputFilename: string): Promise<void>;
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.bundleForServerless = bundleForServerless;
4
+ const core_1 = require("@rspack/core");
5
+ const colors_1 = require("../../colors");
6
+ /**
7
+ * Bundle the serverless entry point into a single CJS file using rspack.
8
+ * This resolves ESM/CJS compatibility issues and dynamic import problems.
9
+ *
10
+ * @param entryPath - Absolute path to the entry file (e.g., dist/index.js)
11
+ * @param outDir - Output directory for the bundled file
12
+ * @param outputFilename - Name of the output bundle (e.g., 'handler.cjs')
13
+ */
14
+ async function bundleForServerless(entryPath, outDir, outputFilename) {
15
+ const compiler = (0, core_1.rspack)({
16
+ mode: 'production',
17
+ target: 'node',
18
+ entry: entryPath,
19
+ output: {
20
+ path: outDir,
21
+ filename: outputFilename,
22
+ library: { type: 'commonjs2' },
23
+ clean: false,
24
+ },
25
+ // Use node externals preset for built-in modules
26
+ externalsPresets: { node: true },
27
+ // Exclude problematic optional dependencies
28
+ externals: {
29
+ '@swc/core': '@swc/core',
30
+ fsevents: 'fsevents',
31
+ esbuild: 'esbuild',
32
+ // React is optional - only needed for MDX/JSX rendering
33
+ react: 'react',
34
+ 'react-dom': 'react-dom',
35
+ 'react-dom/server': 'react-dom/server',
36
+ 'react/jsx-runtime': 'react/jsx-runtime',
37
+ },
38
+ resolve: {
39
+ extensions: ['.js', '.mjs', '.cjs', '.json'],
40
+ // Allow imports without file extensions (TypeScript compiles without .js but ESM requires them)
41
+ fullySpecified: false,
42
+ },
43
+ // Don't minimize to preserve readability for debugging
44
+ optimization: {
45
+ minimize: false,
46
+ },
47
+ // Suppress known third-party library warnings that don't affect runtime
48
+ ignoreWarnings: [
49
+ // Express view engine dynamic require - expected behavior, harmless at runtime
50
+ /Critical dependency: the request of a dependency is an expression/,
51
+ // Handlebars require.extensions - deprecated Node.js API but works at runtime
52
+ /require\.extensions is not supported by Rspack/,
53
+ ],
54
+ // Suppress verbose output
55
+ stats: 'errors-warnings',
56
+ });
57
+ return new Promise((resolve, reject) => {
58
+ compiler.run((err, stats) => {
59
+ if (err) {
60
+ return reject(err);
61
+ }
62
+ if (stats?.hasErrors()) {
63
+ const info = stats.toJson();
64
+ const errorMessages = info.errors?.map((e) => e.message).join('\n') || 'Unknown error';
65
+ return reject(new Error(`Bundle failed:\n${errorMessages}`));
66
+ }
67
+ if (stats?.hasWarnings()) {
68
+ const info = stats.toJson();
69
+ info.warnings?.forEach((w) => {
70
+ console.log((0, colors_1.c)('yellow', ` Warning: ${w.message}`));
71
+ });
72
+ }
73
+ compiler.close((closeErr) => {
74
+ if (closeErr) {
75
+ console.log((0, colors_1.c)('yellow', ` Warning closing compiler: ${closeErr.message}`));
76
+ }
77
+ resolve();
78
+ });
79
+ });
80
+ });
81
+ }
82
+ //# sourceMappingURL=bundler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bundler.js","sourceRoot":"","sources":["../../../../src/commands/build/bundler.ts"],"names":[],"mappings":";;AAWA,kDAwEC;AAnFD,uCAAsC;AACtC,yCAAiC;AAEjC;;;;;;;GAOG;AACI,KAAK,UAAU,mBAAmB,CACvC,SAAiB,EACjB,MAAc,EACd,cAAsB;IAEtB,MAAM,QAAQ,GAAG,IAAA,aAAM,EAAC;QACtB,IAAI,EAAE,YAAY;QAClB,MAAM,EAAE,MAAM;QACd,KAAK,EAAE,SAAS;QAChB,MAAM,EAAE;YACN,IAAI,EAAE,MAAM;YACZ,QAAQ,EAAE,cAAc;YACxB,OAAO,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE;YAC9B,KAAK,EAAE,KAAK;SACb;QACD,iDAAiD;QACjD,gBAAgB,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE;QAChC,4CAA4C;QAC5C,SAAS,EAAE;YACT,WAAW,EAAE,WAAW;YACxB,QAAQ,EAAE,UAAU;YACpB,OAAO,EAAE,SAAS;YAClB,wDAAwD;YACxD,KAAK,EAAE,OAAO;YACd,WAAW,EAAE,WAAW;YACxB,kBAAkB,EAAE,kBAAkB;YACtC,mBAAmB,EAAE,mBAAmB;SACzC;QACD,OAAO,EAAE;YACP,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC;YAC5C,gGAAgG;YAChG,cAAc,EAAE,KAAK;SACtB;QACD,uDAAuD;QACvD,YAAY,EAAE;YACZ,QAAQ,EAAE,KAAK;SAChB;QACD,wEAAwE;QACxE,cAAc,EAAE;YACd,+EAA+E;YAC/E,mEAAmE;YACnE,8EAA8E;YAC9E,gDAAgD;SACjD;QACD,0BAA0B;QAC1B,KAAK,EAAE,iBAAiB;KACzB,CAAC,CAAC;IAEH,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,QAAQ,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE;YAC1B,IAAI,GAAG,EAAE,CAAC;gBACR,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;YACrB,CAAC;YACD,IAAI,KAAK,EAAE,SAAS,EAAE,EAAE,CAAC;gBACvB,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;gBAC5B,MAAM,aAAa,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,eAAe,CAAC;gBACvF,OAAO,MAAM,CAAC,IAAI,KAAK,CAAC,mBAAmB,aAAa,EAAE,CAAC,CAAC,CAAC;YAC/D,CAAC;YACD,IAAI,KAAK,EAAE,WAAW,EAAE,EAAE,CAAC;gBACzB,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;gBAC5B,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE;oBAC3B,OAAO,CAAC,GAAG,CAAC,IAAA,UAAC,EAAC,QAAQ,EAAE,cAAc,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;gBACtD,CAAC,CAAC,CAAC;YACL,CAAC;YACD,QAAQ,CAAC,KAAK,CAAC,CAAC,QAAQ,EAAE,EAAE;gBAC1B,IAAI,QAAQ,EAAE,CAAC;oBACb,OAAO,CAAC,GAAG,CAAC,IAAA,UAAC,EAAC,QAAQ,EAAE,+BAA+B,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;gBAC9E,CAAC;gBACD,OAAO,EAAE,CAAC;YACZ,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["import { rspack } from '@rspack/core';\nimport { c } from '../../colors';\n\n/**\n * Bundle the serverless entry point into a single CJS file using rspack.\n * This resolves ESM/CJS compatibility issues and dynamic import problems.\n *\n * @param entryPath - Absolute path to the entry file (e.g., dist/index.js)\n * @param outDir - Output directory for the bundled file\n * @param outputFilename - Name of the output bundle (e.g., 'handler.cjs')\n */\nexport async function bundleForServerless(\n entryPath: string,\n outDir: string,\n outputFilename: string,\n): Promise<void> {\n const compiler = rspack({\n mode: 'production',\n target: 'node',\n entry: entryPath,\n output: {\n path: outDir,\n filename: outputFilename,\n library: { type: 'commonjs2' },\n clean: false,\n },\n // Use node externals preset for built-in modules\n externalsPresets: { node: true },\n // Exclude problematic optional dependencies\n externals: {\n '@swc/core': '@swc/core',\n fsevents: 'fsevents',\n esbuild: 'esbuild',\n // React is optional - only needed for MDX/JSX rendering\n react: 'react',\n 'react-dom': 'react-dom',\n 'react-dom/server': 'react-dom/server',\n 'react/jsx-runtime': 'react/jsx-runtime',\n },\n resolve: {\n extensions: ['.js', '.mjs', '.cjs', '.json'],\n // Allow imports without file extensions (TypeScript compiles without .js but ESM requires them)\n fullySpecified: false,\n },\n // Don't minimize to preserve readability for debugging\n optimization: {\n minimize: false,\n },\n // Suppress known third-party library warnings that don't affect runtime\n ignoreWarnings: [\n // Express view engine dynamic require - expected behavior, harmless at runtime\n /Critical dependency: the request of a dependency is an expression/,\n // Handlebars require.extensions - deprecated Node.js API but works at runtime\n /require\\.extensions is not supported by Rspack/,\n ],\n // Suppress verbose output\n stats: 'errors-warnings',\n });\n\n return new Promise((resolve, reject) => {\n compiler.run((err, stats) => {\n if (err) {\n return reject(err);\n }\n if (stats?.hasErrors()) {\n const info = stats.toJson();\n const errorMessages = info.errors?.map((e) => e.message).join('\\n') || 'Unknown error';\n return reject(new Error(`Bundle failed:\\n${errorMessages}`));\n }\n if (stats?.hasWarnings()) {\n const info = stats.toJson();\n info.warnings?.forEach((w) => {\n console.log(c('yellow', ` Warning: ${w.message}`));\n });\n }\n compiler.close((closeErr) => {\n if (closeErr) {\n console.log(c('yellow', ` Warning closing compiler: ${closeErr.message}`));\n }\n resolve();\n });\n });\n });\n}\n"]}
@@ -7,6 +7,7 @@ const colors_1 = require("../../colors");
7
7
  const fs_1 = require("../../utils/fs");
8
8
  const tsconfig_1 = require("../../tsconfig");
9
9
  const adapters_1 = require("./adapters");
10
+ const bundler_1 = require("./bundler");
10
11
  function isTsLike(p) {
11
12
  return /\.tsx?$/i.test(p);
12
13
  }
@@ -15,6 +16,14 @@ function isTsLike(p) {
15
16
  */
16
17
  async function generateAdapterFiles(adapter, outDir, entryBasename, cwd) {
17
18
  const template = adapters_1.ADAPTERS[adapter];
19
+ // Generate serverless setup file first (if adapter has one)
20
+ // This file sets FRONTMCP_SERVERLESS=1 before any imports run
21
+ if (template.getSetupTemplate) {
22
+ const setupContent = template.getSetupTemplate();
23
+ const setupPath = path.join(outDir, 'serverless-setup.js');
24
+ await fs_1.fsp.writeFile(setupPath, setupContent, 'utf8');
25
+ console.log((0, colors_1.c)('green', ` Generated serverless setup at ${path.relative(cwd, setupPath)}`));
26
+ }
18
27
  // Generate index.js entry point
19
28
  const mainModuleName = entryBasename.replace(/\.tsx?$/, '.js');
20
29
  const entryContent = template.getEntryTemplate(`./${mainModuleName}`);
@@ -24,6 +33,19 @@ async function generateAdapterFiles(adapter, outDir, entryBasename, cwd) {
24
33
  await fs_1.fsp.writeFile(entryPath, entryContent, 'utf8');
25
34
  console.log((0, colors_1.c)('green', ` Generated ${adapter} entry at ${path.relative(cwd, entryPath)}`));
26
35
  }
36
+ // Bundle if adapter requires it (creates single CJS file for serverless)
37
+ if (template.shouldBundle && template.bundleOutput) {
38
+ console.log((0, colors_1.c)('cyan', `[build] Bundling for ${adapter}...`));
39
+ const entryPath = path.join(outDir, 'index.js');
40
+ await (0, bundler_1.bundleForServerless)(entryPath, outDir, template.bundleOutput);
41
+ console.log((0, colors_1.c)('green', ` Created bundle: ${template.bundleOutput}`));
42
+ // Run post-bundle hook if defined (e.g., create Build Output API structure)
43
+ if (template.postBundle) {
44
+ console.log((0, colors_1.c)('cyan', `[build] Creating ${adapter} deployment structure...`));
45
+ await template.postBundle(outDir, cwd, template.bundleOutput);
46
+ console.log((0, colors_1.c)('green', ` Created deployment output structure`));
47
+ }
48
+ }
27
49
  // Generate config file if adapter has one (skip if already exists)
28
50
  if (template.getConfig && template.configFileName) {
29
51
  const configPath = path.join(cwd, template.configFileName);
@@ -31,7 +53,7 @@ async function generateAdapterFiles(adapter, outDir, entryBasename, cwd) {
31
53
  console.log((0, colors_1.c)('yellow', ` ${template.configFileName} already exists (skipping)`));
32
54
  }
33
55
  else {
34
- const configContent = template.getConfig();
56
+ const configContent = template.getConfig(cwd);
35
57
  if (typeof configContent === 'string') {
36
58
  // Write as plain text (e.g., TOML for wrangler.toml)
37
59
  await fs_1.fsp.writeFile(configPath, configContent, 'utf8');
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/commands/build/index.ts"],"names":[],"mappings":"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","sourcesContent":["import * as path from 'path';\nimport { ParsedArgs } from '../../args';\nimport { c } from '../../colors';\nimport { ensureDir, fileExists, fsp, runCmd, resolveEntry, writeJSON } from '../../utils/fs';\nimport { REQUIRED_DECORATOR_FIELDS } from '../../tsconfig';\nimport { ADAPTERS } from './adapters';\nimport { AdapterName } from './types';\n\nfunction isTsLike(p: string): boolean {\n return /\\.tsx?$/i.test(p);\n}\n\n/**\n * Generate adapter-specific entry point and config files.\n */\nasync function generateAdapterFiles(\n adapter: AdapterName,\n outDir: string,\n entryBasename: string,\n cwd: string,\n): Promise<void> {\n const template = ADAPTERS[adapter];\n\n // Generate index.js entry point\n const mainModuleName = entryBasename.replace(/\\.tsx?$/, '.js');\n const entryContent = template.getEntryTemplate(`./${mainModuleName}`);\n\n // Skip if no entry template (e.g., node adapter)\n if (entryContent) {\n const entryPath = path.join(outDir, 'index.js');\n await fsp.writeFile(entryPath, entryContent, 'utf8');\n console.log(c('green', ` Generated ${adapter} entry at ${path.relative(cwd, entryPath)}`));\n }\n\n // Generate config file if adapter has one (skip if already exists)\n if (template.getConfig && template.configFileName) {\n const configPath = path.join(cwd, template.configFileName);\n\n if (await fileExists(configPath)) {\n console.log(c('yellow', ` ${template.configFileName} already exists (skipping)`));\n } else {\n const configContent = template.getConfig();\n\n if (typeof configContent === 'string') {\n // Write as plain text (e.g., TOML for wrangler.toml)\n await fsp.writeFile(configPath, configContent, 'utf8');\n } else {\n // Write as JSON\n await writeJSON(configPath, configContent);\n }\n console.log(c('green', ` Generated ${template.configFileName}`));\n }\n }\n}\n\n/**\n * Build the FrontMCP server for a specific deployment target.\n *\n * @param opts - Build options from CLI arguments\n *\n * @example\n * ```bash\n * # Build for Node.js (default)\n * frontmcp build\n *\n * # Build for Vercel\n * frontmcp build --adapter vercel\n *\n * # Build for AWS Lambda\n * frontmcp build --adapter lambda\n *\n * # Build for Cloudflare Workers\n * frontmcp build --adapter cloudflare\n * ```\n */\nexport async function runBuild(opts: ParsedArgs): Promise<void> {\n const cwd = process.cwd();\n const entry = await resolveEntry(cwd, opts.entry);\n const outDir = path.resolve(cwd, opts.outDir || 'dist');\n const adapter = (opts.adapter || 'node') as AdapterName;\n await ensureDir(outDir);\n\n // Validate adapter\n const template = ADAPTERS[adapter];\n if (!template) {\n const available = Object.keys(ADAPTERS).join(', ');\n throw new Error(`Unknown adapter: ${adapter}. Available: ${available}`);\n }\n\n // Warn about experimental adapters\n if (adapter === 'cloudflare') {\n console.log(\n c('yellow', '⚠️ Cloudflare Workers adapter is experimental. See docs for limitations.'),\n );\n }\n\n const moduleFormat = template.moduleFormat;\n\n console.log(`${c('cyan', '[build]')} entry: ${path.relative(cwd, entry)}`);\n console.log(`${c('cyan', '[build]')} outDir: ${path.relative(cwd, outDir)}`);\n console.log(`${c('cyan', '[build]')} adapter: ${adapter} (${moduleFormat})`);\n\n // Build TypeScript compiler arguments\n const tsconfigPath = path.join(cwd, 'tsconfig.json');\n const hasTsconfig = await fileExists(tsconfigPath);\n const args: string[] = ['-y', 'tsc'];\n\n if (hasTsconfig) {\n console.log(c('gray', `[build] tsconfig.json detected — compiling with project settings`));\n args.push('--project', tsconfigPath);\n } else {\n args.push(entry);\n args.push('--rootDir', path.dirname(entry));\n if (!isTsLike(entry)) {\n args.push('--allowJs');\n console.log(c('yellow', '[build] Entry is not TypeScript; enabling --allowJs'));\n }\n args.push('--experimentalDecorators', '--emitDecoratorMetadata');\n args.push('--target', REQUIRED_DECORATOR_FIELDS.target);\n }\n\n // Always pass module format to override tsconfig\n args.push('--module', moduleFormat);\n args.push('--outDir', outDir);\n args.push('--skipLibCheck');\n\n // Run TypeScript compiler\n await runCmd('npx', args);\n\n // Generate adapter-specific files\n if (adapter !== 'node') {\n console.log(c('cyan', `[build] Generating ${adapter} deployment files...`));\n const entryBasename = path.basename(entry);\n await generateAdapterFiles(adapter, outDir, entryBasename, cwd);\n }\n\n console.log(c('green', '✅ Build completed.'));\n console.log(c('gray', `Output placed in ${path.relative(cwd, outDir)}`));\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/commands/build/index.ts"],"names":[],"mappings":"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","sourcesContent":["import * as path from 'path';\nimport { ParsedArgs } from '../../args';\nimport { c } from '../../colors';\nimport { ensureDir, fileExists, fsp, runCmd, resolveEntry, writeJSON } from '../../utils/fs';\nimport { REQUIRED_DECORATOR_FIELDS } from '../../tsconfig';\nimport { ADAPTERS } from './adapters';\nimport { AdapterName } from './types';\nimport { bundleForServerless } from './bundler';\n\nfunction isTsLike(p: string): boolean {\n return /\\.tsx?$/i.test(p);\n}\n\n/**\n * Generate adapter-specific entry point and config files.\n */\nasync function generateAdapterFiles(\n adapter: AdapterName,\n outDir: string,\n entryBasename: string,\n cwd: string,\n): Promise<void> {\n const template = ADAPTERS[adapter];\n\n // Generate serverless setup file first (if adapter has one)\n // This file sets FRONTMCP_SERVERLESS=1 before any imports run\n if (template.getSetupTemplate) {\n const setupContent = template.getSetupTemplate();\n const setupPath = path.join(outDir, 'serverless-setup.js');\n await fsp.writeFile(setupPath, setupContent, 'utf8');\n console.log(c('green', ` Generated serverless setup at ${path.relative(cwd, setupPath)}`));\n }\n\n // Generate index.js entry point\n const mainModuleName = entryBasename.replace(/\\.tsx?$/, '.js');\n const entryContent = template.getEntryTemplate(`./${mainModuleName}`);\n\n // Skip if no entry template (e.g., node adapter)\n if (entryContent) {\n const entryPath = path.join(outDir, 'index.js');\n await fsp.writeFile(entryPath, entryContent, 'utf8');\n console.log(c('green', ` Generated ${adapter} entry at ${path.relative(cwd, entryPath)}`));\n }\n\n // Bundle if adapter requires it (creates single CJS file for serverless)\n if (template.shouldBundle && template.bundleOutput) {\n console.log(c('cyan', `[build] Bundling for ${adapter}...`));\n const entryPath = path.join(outDir, 'index.js');\n await bundleForServerless(entryPath, outDir, template.bundleOutput);\n console.log(c('green', ` Created bundle: ${template.bundleOutput}`));\n\n // Run post-bundle hook if defined (e.g., create Build Output API structure)\n if (template.postBundle) {\n console.log(c('cyan', `[build] Creating ${adapter} deployment structure...`));\n await template.postBundle(outDir, cwd, template.bundleOutput);\n console.log(c('green', ` Created deployment output structure`));\n }\n }\n\n // Generate config file if adapter has one (skip if already exists)\n if (template.getConfig && template.configFileName) {\n const configPath = path.join(cwd, template.configFileName);\n\n if (await fileExists(configPath)) {\n console.log(c('yellow', ` ${template.configFileName} already exists (skipping)`));\n } else {\n const configContent = template.getConfig(cwd);\n\n if (typeof configContent === 'string') {\n // Write as plain text (e.g., TOML for wrangler.toml)\n await fsp.writeFile(configPath, configContent, 'utf8');\n } else {\n // Write as JSON\n await writeJSON(configPath, configContent);\n }\n console.log(c('green', ` Generated ${template.configFileName}`));\n }\n }\n}\n\n/**\n * Build the FrontMCP server for a specific deployment target.\n *\n * @param opts - Build options from CLI arguments\n *\n * @example\n * ```bash\n * # Build for Node.js (default)\n * frontmcp build\n *\n * # Build for Vercel\n * frontmcp build --adapter vercel\n *\n * # Build for AWS Lambda\n * frontmcp build --adapter lambda\n *\n * # Build for Cloudflare Workers\n * frontmcp build --adapter cloudflare\n * ```\n */\nexport async function runBuild(opts: ParsedArgs): Promise<void> {\n const cwd = process.cwd();\n const entry = await resolveEntry(cwd, opts.entry);\n const outDir = path.resolve(cwd, opts.outDir || 'dist');\n const adapter = (opts.adapter || 'node') as AdapterName;\n await ensureDir(outDir);\n\n // Validate adapter\n const template = ADAPTERS[adapter];\n if (!template) {\n const available = Object.keys(ADAPTERS).join(', ');\n throw new Error(`Unknown adapter: ${adapter}. Available: ${available}`);\n }\n\n // Warn about experimental adapters\n if (adapter === 'cloudflare') {\n console.log(\n c('yellow', '⚠️ Cloudflare Workers adapter is experimental. See docs for limitations.'),\n );\n }\n\n const moduleFormat = template.moduleFormat;\n\n console.log(`${c('cyan', '[build]')} entry: ${path.relative(cwd, entry)}`);\n console.log(`${c('cyan', '[build]')} outDir: ${path.relative(cwd, outDir)}`);\n console.log(`${c('cyan', '[build]')} adapter: ${adapter} (${moduleFormat})`);\n\n // Build TypeScript compiler arguments\n const tsconfigPath = path.join(cwd, 'tsconfig.json');\n const hasTsconfig = await fileExists(tsconfigPath);\n const args: string[] = ['-y', 'tsc'];\n\n if (hasTsconfig) {\n console.log(c('gray', `[build] tsconfig.json detected — compiling with project settings`));\n args.push('--project', tsconfigPath);\n } else {\n args.push(entry);\n args.push('--rootDir', path.dirname(entry));\n if (!isTsLike(entry)) {\n args.push('--allowJs');\n console.log(c('yellow', '[build] Entry is not TypeScript; enabling --allowJs'));\n }\n args.push('--experimentalDecorators', '--emitDecoratorMetadata');\n args.push('--target', REQUIRED_DECORATOR_FIELDS.target);\n }\n\n // Always pass module format to override tsconfig\n args.push('--module', moduleFormat);\n args.push('--outDir', outDir);\n args.push('--skipLibCheck');\n\n // Run TypeScript compiler\n await runCmd('npx', args);\n\n // Generate adapter-specific files\n if (adapter !== 'node') {\n console.log(c('cyan', `[build] Generating ${adapter} deployment files...`));\n const entryBasename = path.basename(entry);\n await generateAdapterFiles(adapter, outDir, entryBasename, cwd);\n }\n\n console.log(c('green', '✅ Build completed.'));\n console.log(c('gray', `Output placed in ${path.relative(cwd, outDir)}`));\n}\n"]}
@@ -12,12 +12,37 @@ export type AdapterTemplate = {
12
12
  * @returns The content for index.js, or empty string if no wrapper needed
13
13
  */
14
14
  getEntryTemplate: (mainModulePath: string) => string;
15
+ /**
16
+ * Generate the serverless setup file content.
17
+ * This file is imported first to set environment variables before decorators run.
18
+ * @returns The content for serverless-setup.js, or undefined if not needed
19
+ */
20
+ getSetupTemplate?: () => string;
21
+ /**
22
+ * Whether to bundle the output with rspack.
23
+ * Recommended for serverless deployments to avoid ESM/CJS issues.
24
+ */
25
+ shouldBundle?: boolean;
26
+ /**
27
+ * Output filename for the bundled file (e.g., 'handler.cjs').
28
+ * Only used when shouldBundle is true.
29
+ */
30
+ bundleOutput?: string;
15
31
  /**
16
32
  * Generate the deployment platform config file content.
33
+ * @param cwd - Current working directory (for detecting package manager, etc.)
17
34
  * @returns Object (for JSON) or string (for TOML/YAML)
18
35
  */
19
- getConfig?: () => object | string;
36
+ getConfig?: (cwd: string) => object | string;
20
37
  /** Name of the config file (e.g., 'vercel.json', 'wrangler.toml') */
21
38
  configFileName?: string;
39
+ /**
40
+ * Post-bundle hook for creating deployment-specific output structure.
41
+ * Called after bundling is complete.
42
+ * @param outDir - The output directory (e.g., 'dist')
43
+ * @param cwd - Current working directory
44
+ * @param bundleOutput - Name of the bundled file (e.g., 'handler.cjs')
45
+ */
46
+ postBundle?: (outDir: string, cwd: string, bundleOutput: string) => Promise<void>;
22
47
  };
23
48
  export type AdapterName = 'node' | 'vercel' | 'lambda' | 'cloudflare';
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/commands/build/types.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * Configuration for a deployment adapter.\n * Each adapter defines how to compile and package the FrontMCP server\n * for a specific deployment target.\n */\nexport type AdapterTemplate = {\n /** Module format for TypeScript compilation */\n moduleFormat: 'commonjs' | 'esnext';\n\n /**\n * Generate the entry point file content.\n * @param mainModulePath - Relative path to the compiled main module (e.g., './main.js')\n * @returns The content for index.js, or empty string if no wrapper needed\n */\n getEntryTemplate: (mainModulePath: string) => string;\n\n /**\n * Generate the deployment platform config file content.\n * @returns Object (for JSON) or string (for TOML/YAML)\n */\n getConfig?: () => object | string;\n\n /** Name of the config file (e.g., 'vercel.json', 'wrangler.toml') */\n configFileName?: string;\n};\n\nexport type AdapterName = 'node' | 'vercel' | 'lambda' | 'cloudflare';\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/commands/build/types.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * Configuration for a deployment adapter.\n * Each adapter defines how to compile and package the FrontMCP server\n * for a specific deployment target.\n */\nexport type AdapterTemplate = {\n /** Module format for TypeScript compilation */\n moduleFormat: 'commonjs' | 'esnext';\n\n /**\n * Generate the entry point file content.\n * @param mainModulePath - Relative path to the compiled main module (e.g., './main.js')\n * @returns The content for index.js, or empty string if no wrapper needed\n */\n getEntryTemplate: (mainModulePath: string) => string;\n\n /**\n * Generate the serverless setup file content.\n * This file is imported first to set environment variables before decorators run.\n * @returns The content for serverless-setup.js, or undefined if not needed\n */\n getSetupTemplate?: () => string;\n\n /**\n * Whether to bundle the output with rspack.\n * Recommended for serverless deployments to avoid ESM/CJS issues.\n */\n shouldBundle?: boolean;\n\n /**\n * Output filename for the bundled file (e.g., 'handler.cjs').\n * Only used when shouldBundle is true.\n */\n bundleOutput?: string;\n\n /**\n * Generate the deployment platform config file content.\n * @param cwd - Current working directory (for detecting package manager, etc.)\n * @returns Object (for JSON) or string (for TOML/YAML)\n */\n getConfig?: (cwd: string) => object | string;\n\n /** Name of the config file (e.g., 'vercel.json', 'wrangler.toml') */\n configFileName?: string;\n\n /**\n * Post-bundle hook for creating deployment-specific output structure.\n * Called after bundling is complete.\n * @param outDir - The output directory (e.g., 'dist')\n * @param cwd - Current working directory\n * @param bundleOutput - Name of the bundled file (e.g., 'handler.cjs')\n */\n postBundle?: (outDir: string, cwd: string, bundleOutput: string) => Promise<void>;\n};\n\nexport type AdapterName = 'node' | 'vercel' | 'lambda' | 'cloudflare';\n"]}