frontmcp 1.4.0 → 1.5.0-rc.1

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "frontmcp",
3
- "version": "1.4.0",
3
+ "version": "1.5.0-rc.1",
4
4
  "description": "FrontMCP command line interface",
5
5
  "author": "AgentFront <info@agentfront.dev>",
6
6
  "homepage": "https://docs.agentfront.dev",
@@ -23,31 +23,29 @@
23
23
  },
24
24
  "main": "./src/index.js",
25
25
  "types": "./src/index.d.ts",
26
- "bin": {
27
- "frontmcp": "./src/core/cli.js"
28
- },
26
+ "bin": "./src/core/cli.js",
29
27
  "engines": {
30
28
  "node": ">=24.0.0"
31
29
  },
32
30
  "dependencies": {
33
31
  "@clack/prompts": "^0.10.0",
34
- "@frontmcp/lazy-zod": "1.4.0",
35
- "@frontmcp/utils": "1.4.0",
36
- "@frontmcp/skills": "1.4.0",
37
- "commander": "^13.0.0",
38
- "tslib": "^2.3.0",
39
- "vectoriadb": "^2.2.0",
32
+ "@frontmcp/lazy-zod": "1.5.0-rc.1",
33
+ "@frontmcp/skills": "1.5.0-rc.1",
34
+ "@frontmcp/utils": "1.5.0-rc.1",
40
35
  "@rspack/core": "^1.7.6",
36
+ "commander": "^13.0.0",
41
37
  "esbuild": "^0.27.3",
38
+ "tslib": "^2.3.0",
39
+ "vectoriadb": "^2.3.2",
42
40
  "yauzl": "^3.2.0",
43
41
  "yazl": "^3.3.1"
44
42
  },
45
43
  "devDependencies": {
46
- "typescript": "^5.5.3",
47
- "tsx": "^4.20.6",
48
44
  "@types/node": "^24.0.0",
49
45
  "@types/yauzl": "^2.10.3",
50
- "@types/yazl": "^2.4.5"
46
+ "@types/yazl": "^2.4.5",
47
+ "tsx": "^4.20.6",
48
+ "typescript": "^5.5.3"
51
49
  },
52
50
  "type": "commonjs"
53
51
  }
@@ -1,7 +1,16 @@
1
1
  import type { AdapterTemplate } from '../types';
2
2
  /**
3
3
  * Cloudflare Workers adapter - edge deployment on Cloudflare.
4
- * Compiles to CommonJS and adapts the Express app to Cloudflare's fetch API.
4
+ * Compiles to an ES Module Worker that routes the native Web `Request` into the
5
+ * SDK's web-fetch handler (`getServerlessHandlerAsync` in worker mode) — no
6
+ * Express, no Node `req`/`res` shim.
7
+ *
8
+ * NOTE: this decorator-build path does NOT emit KV / Durable Object / R2 / D1
9
+ * bindings or `[triggers] crontabs` into `wrangler.toml`, and the entry passes
10
+ * only the `Request` to the handler (not `env`/`ctx`). Workers needing bindings
11
+ * or the managed auto-update Cron (`scheduled`) should use `@frontmcp/edge`
12
+ * `createEdgeMcp` with a hand-written `wrangler.toml`. See
13
+ * docs/frontmcp/deployment/cloudflare-worker.mdx.
5
14
  *
6
15
  * @see https://developers.cloudflare.com/workers/
7
16
  */
@@ -3,73 +3,56 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.cloudflareAdapter = void 0;
4
4
  /**
5
5
  * Cloudflare Workers adapter - edge deployment on Cloudflare.
6
- * Compiles to CommonJS and adapts the Express app to Cloudflare's fetch API.
6
+ * Compiles to an ES Module Worker that routes the native Web `Request` into the
7
+ * SDK's web-fetch handler (`getServerlessHandlerAsync` in worker mode) — no
8
+ * Express, no Node `req`/`res` shim.
9
+ *
10
+ * NOTE: this decorator-build path does NOT emit KV / Durable Object / R2 / D1
11
+ * bindings or `[triggers] crontabs` into `wrangler.toml`, and the entry passes
12
+ * only the `Request` to the handler (not `env`/`ctx`). Workers needing bindings
13
+ * or the managed auto-update Cron (`scheduled`) should use `@frontmcp/edge`
14
+ * `createEdgeMcp` with a hand-written `wrangler.toml`. See
15
+ * docs/frontmcp/deployment/cloudflare-worker.mdx.
7
16
  *
8
17
  * @see https://developers.cloudflare.com/workers/
9
18
  */
10
19
  exports.cloudflareAdapter = {
11
- moduleFormat: 'commonjs',
12
- getEntryTemplate: (mainModulePath) => `// Auto-generated Cloudflare Workers entry point
13
- // Generated by: frontmcp build --target cloudflare
20
+ // ES Module format — modern Cloudflare Workers (and `nodejs_compat`) require
21
+ // the Module Worker shape (`export default { fetch }`). The legacy CommonJS
22
+ // `module.exports` is interpreted as a Service Worker, where `nodejs_compat`
23
+ // cannot externalize Node builtins and the build fails.
24
+ moduleFormat: 'esnext',
25
+ // Emitted as `serverless-setup.js` and imported FIRST by the entry, so the
26
+ // env flags are set before the user module's `@FrontMcp` decorator evaluates
27
+ // (ESM import evaluation is ordered, and the decorator reads these at import
28
+ // time). FRONTMCP_WORKER selects FrontMCP's Web-standard fetch handler.
29
+ getSetupTemplate: () => `// Auto-generated — sets env before the @FrontMcp decorator runs.
14
30
  process.env.FRONTMCP_SERVERLESS = '1';
15
31
  process.env.FRONTMCP_DEPLOYMENT_MODE = 'serverless';
32
+ process.env.FRONTMCP_WORKER = '1';
33
+ `,
34
+ getEntryTemplate: (mainModulePath) => `// Auto-generated Cloudflare Workers entry point (ES Module / Module Worker).
35
+ // Generated by: frontmcp build --target cloudflare
36
+ //
37
+ // The Worker receives the native Web Request and the SDK routes it straight
38
+ // into the MCP WebStandard transport (Request/Response). No Express, no Node
39
+ // req/res shim.
40
+ import './serverless-setup.js';
41
+ import '${mainModulePath}';
42
+ import { getServerlessHandlerAsync } from '@frontmcp/sdk';
16
43
 
17
- require('${mainModulePath}');
18
- const { getServerlessHandlerAsync } = require('@frontmcp/sdk');
19
-
20
- let app = null;
21
-
22
- async function handleRequest(request) {
23
- if (!app) {
24
- app = await getServerlessHandlerAsync();
25
- }
26
-
27
- // Convert Cloudflare Request to Node.js format
28
- const url = new URL(request.url);
29
- const req = {
30
- method: request.method,
31
- url: url.pathname + url.search,
32
- headers: Object.fromEntries(request.headers),
33
- body: request.body,
34
- };
35
-
36
- return new Promise((resolve) => {
37
- const res = {
38
- statusCode: 200,
39
- headers: {},
40
- body: '',
41
- status(code) { this.statusCode = code; return this; },
42
- setHeader(key, value) { this.headers[key] = value; },
43
- json(data) {
44
- this.headers['Content-Type'] = 'application/json';
45
- this.body = JSON.stringify(data);
46
- resolve(new Response(this.body, {
47
- status: this.statusCode,
48
- headers: this.headers,
49
- }));
50
- },
51
- send(data) {
52
- this.body = data;
53
- resolve(new Response(this.body, {
54
- status: this.statusCode,
55
- headers: this.headers,
56
- }));
57
- },
58
- end() {
59
- resolve(new Response(this.body, {
60
- status: this.statusCode,
61
- headers: this.headers,
62
- }));
63
- },
64
- };
65
- app(req, res);
66
- });
67
- }
44
+ let handlerPromise = null;
68
45
 
69
- module.exports = {
46
+ export default {
70
47
  async fetch(request, env, ctx) {
71
- return handleRequest(request);
72
- }
48
+ if (!handlerPromise) {
49
+ handlerPromise = getServerlessHandlerAsync();
50
+ }
51
+ // In worker mode the stored handler is a Web fetch handler:
52
+ // (Request) => Promise<Response>.
53
+ const handler = await handlerPromise;
54
+ return handler(request);
55
+ },
73
56
  };
74
57
  `,
75
58
  // #375 — fail the build when the user's @FrontMcp config references
@@ -116,16 +99,28 @@ module.exports = {
116
99
  // wrangler deploy silently failed.
117
100
  alwaysWriteConfig: true,
118
101
  // #374 round-2 — merge `frontmcp.config.deployments[].wrangler.{name,
119
- // compatibilityDate}` into the rendered TOML so values declared in the
120
- // user's config actually reach `wrangler deploy`. Defaults preserved when
121
- // the field is absent or no deployment was matched.
102
+ // compatibilityDate, compatibilityFlags}` into the rendered TOML so values
103
+ // declared in the user's config actually reach `wrangler deploy`. Defaults
104
+ // preserved when the field is absent or no deployment was matched.
122
105
  getConfig: (_cwd, deployment) => {
123
106
  const wrangler = deployment?.wrangler ?? {};
124
107
  const name = wrangler.name ?? 'frontmcp-worker';
125
- const compatibilityDate = wrangler.compatibilityDate ?? '2024-01-01';
108
+ // The cloudflare entry (getEntryTemplate) is an ES Module that imports the
109
+ // SDK's web-fetch handler, which still transitively pulls in Node builtins
110
+ // (node:*, Buffer, process, streams) through the SDK runtime. On Workers
111
+ // those exist ONLY behind the `nodejs_compat` flag — without it the Worker
112
+ // fails to even load. The flag is therefore non-negotiable for this target;
113
+ // we always emit it and merge in any extra flags the user declared (deduped,
114
+ // `nodejs_compat` guaranteed first).
115
+ const flags = Array.from(new Set(['nodejs_compat', ...(wrangler.compatibilityFlags ?? [])]));
116
+ // `nodejs_compat` only provides the full Node API surface (incl. `require`
117
+ // of builtins) when compatibility_date >= 2024-09-23; default to that so a
118
+ // freshly-built worker boots. Users can still pin an older/newer date.
119
+ const compatibilityDate = wrangler.compatibilityDate ?? '2024-09-23';
126
120
  return `name = "${name}"
127
121
  main = "dist/cloudflare/index.js"
128
122
  compatibility_date = "${compatibilityDate}"
123
+ compatibility_flags = [${flags.map((f) => `"${f}"`).join(', ')}]
129
124
  `;
130
125
  },
131
126
  configFileName: 'wrangler.toml',
@@ -1 +1 @@
1
- {"version":3,"file":"cloudflare.js","sourceRoot":"","sources":["../../../../../src/commands/build/adapters/cloudflare.ts"],"names":[],"mappings":";;;AAGA;;;;;GAKG;AACU,QAAA,iBAAiB,GAAoB;IAChD,YAAY,EAAE,UAAU;IAExB,gBAAgB,EAAE,CAAC,cAAsB,EAAE,EAAE,CAAC;;;;;WAKrC,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyDxB;IAEC,oEAAoE;IACpE,yDAAyD;IACzD,EAAE;IACF,6EAA6E;IAC7E,8EAA8E;IAC9E,4EAA4E;IAC5E,0EAA0E;IAC1E,0EAA0E;IAC1E,8EAA8E;IAC9E,6EAA6E;IAC7E,4EAA4E;IAC5E,iDAAiD;IACjD,QAAQ,EAAE,CAAC,eAAe,EAAE,IAAI,EAAE,EAAE;QAClC,MAAM,MAAM,GAAa,EAAE,CAAC;QAE5B,MAAM,eAAe,GACnB,eAAe,EAAE,CAAC,QAAQ,CAAC,KAAK,SAAS;YACzC,OAAO,eAAe,CAAC,QAAQ,CAAC,KAAK,QAAQ;YAC7C,eAAe,CAAC,QAAQ,CAAC,KAAK,IAAI,CAAC;QACrC,MAAM,cAAc,GAClB,eAAe,EAAE,CAAC,OAAO,CAAC,KAAK,SAAS;YACxC,OAAO,eAAe,CAAC,OAAO,CAAC,KAAK,QAAQ;YAC5C,eAAe,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC;QACpC,MAAM,UAAU,GAAG,eAAe,IAAI,CAAC,CAAC,IAAI,EAAE,gBAAgB,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC;QACnF,MAAM,SAAS,GAAG,cAAc,IAAI,CAAC,CAAC,IAAI,EAAE,gBAAgB,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;QAEhF,IAAI,UAAU,EAAE,CAAC;YACf,MAAM,CAAC,IAAI,CACT,8FAA8F;gBAC5F,0FAA0F;gBAC1F,uFAAuF;gBACvF,gFAAgF,CACnF,CAAC;QACJ,CAAC;QACD,IAAI,SAAS,EAAE,CAAC;YACd,MAAM,CAAC,IAAI,CACT,uFAAuF;gBACrF,yFAAyF;gBACzF,uFAAuF;gBACvF,gFAAgF,CACnF,CAAC;QACJ,CAAC;QACD,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;YAClB,MAAM,IAAI,KAAK,CACb,2EAA2E,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CACnG,CAAC;QACJ,CAAC;IACH,CAAC;IAED,yEAAyE;IACzE,0EAA0E;IAC1E,sEAAsE;IACtE,mCAAmC;IACnC,iBAAiB,EAAE,IAAI;IAEvB,sEAAsE;IACtE,uEAAuE;IACvE,0EAA0E;IAC1E,oDAAoD;IACpD,SAAS,EAAE,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE;QAC9B,MAAM,QAAQ,GAAI,UAA+C,EAAE,QAAQ,IAAI,EAAE,CAAC;QAClF,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,IAAI,iBAAiB,CAAC;QAChD,MAAM,iBAAiB,GAAG,QAAQ,CAAC,iBAAiB,IAAI,YAAY,CAAC;QACrE,OAAO,WAAW,IAAI;;wBAEF,iBAAiB;CACxC,CAAC;IACA,CAAC;IAED,cAAc,EAAE,eAAe;CAChC,CAAC","sourcesContent":["import type { CloudflareDeployment } from '../../../config/frontmcp-config.types';\nimport type { 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 --target cloudflare\nprocess.env.FRONTMCP_SERVERLESS = '1';\nprocess.env.FRONTMCP_DEPLOYMENT_MODE = 'serverless';\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 // #375 — fail the build when the user's @FrontMcp config references\n // Node-only storage providers that can't run on Workers.\n //\n // Round 1 only inspected the runtime-evaluated config object. The reporter's\n // failure case was `sqlite: process.env.REDIS_HOST ? {…} : { sqlite: {…} }` —\n // a ternary that may evaluate to `undefined` at decorator-load time (so the\n // runtime check sees nothing) but still ships the Node-only branch in the\n // bundled worker. Round 2 also inspects `info.keysSeenInSource`, which is\n // collected by walking the @FrontMcp({...}) source expression — that captures\n // the property name regardless of whether its value is a literal, a ternary,\n // or a function call. If `sqlite`/`redis` appear at all, fail loud and tell\n // the user how to gate them at the source level.\n validate: (decoratorConfig, info) => {\n const errors: string[] = [];\n\n const sqliteIsLiteral =\n decoratorConfig?.['sqlite'] !== undefined &&\n typeof decoratorConfig['sqlite'] === 'object' &&\n decoratorConfig['sqlite'] !== null;\n const redisIsLiteral =\n decoratorConfig?.['redis'] !== undefined &&\n typeof decoratorConfig['redis'] === 'object' &&\n decoratorConfig['redis'] !== null;\n const sqliteSeen = sqliteIsLiteral || !!info?.keysSeenInSource?.includes('sqlite');\n const redisSeen = redisIsLiteral || !!info?.keysSeenInSource?.includes('redis');\n\n if (sqliteSeen) {\n errors.push(\n 'sqlite storage is not supported on --target cloudflare (no fs / native modules on Workers). ' +\n 'Even an env-gated `sqlite: process.env.X ? {...} : undefined` still ships the Node-only ' +\n 'branch in the worker bundle. Use Cloudflare KV / Durable Objects, or move the sqlite ' +\n 'config behind a build-time `define` so the bundler can dead-code-eliminate it.',\n );\n }\n if (redisSeen) {\n errors.push(\n 'ioredis-style `redis` storage is not supported on --target cloudflare (no Node net). ' +\n 'Even an env-gated `redis: process.env.X ? {...} : undefined` still ships the Node-only ' +\n 'branch in the worker bundle. Use Vercel KV / Upstash Redis (HTTP), or move the redis ' +\n 'config behind a build-time `define` so the bundler can dead-code-eliminate it.',\n );\n }\n if (errors.length) {\n throw new Error(\n `[--target cloudflare] config incompatible with Cloudflare Workers:\\n - ${errors.join('\\n - ')}`,\n );\n }\n },\n\n // #374 — always write wrangler.toml from the build output. Skipping when\n // the file already exists left users with a wrangler.toml that pointed at\n // dist/index.js while the build emitted dist/cloudflare/index.js, and\n // wrangler deploy silently failed.\n alwaysWriteConfig: true,\n\n // #374 round-2 — merge `frontmcp.config.deployments[].wrangler.{name,\n // compatibilityDate}` into the rendered TOML so values declared in the\n // user's config actually reach `wrangler deploy`. Defaults preserved when\n // the field is absent or no deployment was matched.\n getConfig: (_cwd, deployment) => {\n const wrangler = (deployment as CloudflareDeployment | undefined)?.wrangler ?? {};\n const name = wrangler.name ?? 'frontmcp-worker';\n const compatibilityDate = wrangler.compatibilityDate ?? '2024-01-01';\n return `name = \"${name}\"\nmain = \"dist/cloudflare/index.js\"\ncompatibility_date = \"${compatibilityDate}\"\n`;\n },\n\n configFileName: 'wrangler.toml',\n};\n"]}
1
+ {"version":3,"file":"cloudflare.js","sourceRoot":"","sources":["../../../../../src/commands/build/adapters/cloudflare.ts"],"names":[],"mappings":"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","sourcesContent":["import type { CloudflareDeployment } from '../../../config/frontmcp-config.types';\nimport type { AdapterTemplate } from '../types';\n\n/**\n * Cloudflare Workers adapter - edge deployment on Cloudflare.\n * Compiles to an ES Module Worker that routes the native Web `Request` into the\n * SDK's web-fetch handler (`getServerlessHandlerAsync` in worker mode) — no\n * Express, no Node `req`/`res` shim.\n *\n * NOTE: this decorator-build path does NOT emit KV / Durable Object / R2 / D1\n * bindings or `[triggers] crontabs` into `wrangler.toml`, and the entry passes\n * only the `Request` to the handler (not `env`/`ctx`). Workers needing bindings\n * or the managed auto-update Cron (`scheduled`) should use `@frontmcp/edge`\n * `createEdgeMcp` with a hand-written `wrangler.toml`. See\n * docs/frontmcp/deployment/cloudflare-worker.mdx.\n *\n * @see https://developers.cloudflare.com/workers/\n */\nexport const cloudflareAdapter: AdapterTemplate = {\n // ES Module format modern Cloudflare Workers (and `nodejs_compat`) require\n // the Module Worker shape (`export default { fetch }`). The legacy CommonJS\n // `module.exports` is interpreted as a Service Worker, where `nodejs_compat`\n // cannot externalize Node builtins and the build fails.\n moduleFormat: 'esnext',\n\n // Emitted as `serverless-setup.js` and imported FIRST by the entry, so the\n // env flags are set before the user module's `@FrontMcp` decorator evaluates\n // (ESM import evaluation is ordered, and the decorator reads these at import\n // time). FRONTMCP_WORKER selects FrontMCP's Web-standard fetch handler.\n getSetupTemplate: () => `// Auto-generated sets env before the @FrontMcp decorator runs.\nprocess.env.FRONTMCP_SERVERLESS = '1';\nprocess.env.FRONTMCP_DEPLOYMENT_MODE = 'serverless';\nprocess.env.FRONTMCP_WORKER = '1';\n`,\n\n getEntryTemplate: (mainModulePath: string) => `// Auto-generated Cloudflare Workers entry point (ES Module / Module Worker).\n// Generated by: frontmcp build --target cloudflare\n//\n// The Worker receives the native Web Request and the SDK routes it straight\n// into the MCP WebStandard transport (Request/Response). No Express, no Node\n// req/res shim.\nimport './serverless-setup.js';\nimport '${mainModulePath}';\nimport { getServerlessHandlerAsync } from '@frontmcp/sdk';\n\nlet handlerPromise = null;\n\nexport default {\n async fetch(request, env, ctx) {\n if (!handlerPromise) {\n handlerPromise = getServerlessHandlerAsync();\n }\n // In worker mode the stored handler is a Web fetch handler:\n // (Request) => Promise<Response>.\n const handler = await handlerPromise;\n return handler(request);\n },\n};\n`,\n\n // #375 — fail the build when the user's @FrontMcp config references\n // Node-only storage providers that can't run on Workers.\n //\n // Round 1 only inspected the runtime-evaluated config object. The reporter's\n // failure case was `sqlite: process.env.REDIS_HOST ? {…} : { sqlite: {…} }` —\n // a ternary that may evaluate to `undefined` at decorator-load time (so the\n // runtime check sees nothing) but still ships the Node-only branch in the\n // bundled worker. Round 2 also inspects `info.keysSeenInSource`, which is\n // collected by walking the @FrontMcp({...}) source expression — that captures\n // the property name regardless of whether its value is a literal, a ternary,\n // or a function call. If `sqlite`/`redis` appear at all, fail loud and tell\n // the user how to gate them at the source level.\n validate: (decoratorConfig, info) => {\n const errors: string[] = [];\n\n const sqliteIsLiteral =\n decoratorConfig?.['sqlite'] !== undefined &&\n typeof decoratorConfig['sqlite'] === 'object' &&\n decoratorConfig['sqlite'] !== null;\n const redisIsLiteral =\n decoratorConfig?.['redis'] !== undefined &&\n typeof decoratorConfig['redis'] === 'object' &&\n decoratorConfig['redis'] !== null;\n const sqliteSeen = sqliteIsLiteral || !!info?.keysSeenInSource?.includes('sqlite');\n const redisSeen = redisIsLiteral || !!info?.keysSeenInSource?.includes('redis');\n\n if (sqliteSeen) {\n errors.push(\n 'sqlite storage is not supported on --target cloudflare (no fs / native modules on Workers). ' +\n 'Even an env-gated `sqlite: process.env.X ? {...} : undefined` still ships the Node-only ' +\n 'branch in the worker bundle. Use Cloudflare KV / Durable Objects, or move the sqlite ' +\n 'config behind a build-time `define` so the bundler can dead-code-eliminate it.',\n );\n }\n if (redisSeen) {\n errors.push(\n 'ioredis-style `redis` storage is not supported on --target cloudflare (no Node net). ' +\n 'Even an env-gated `redis: process.env.X ? {...} : undefined` still ships the Node-only ' +\n 'branch in the worker bundle. Use Vercel KV / Upstash Redis (HTTP), or move the redis ' +\n 'config behind a build-time `define` so the bundler can dead-code-eliminate it.',\n );\n }\n if (errors.length) {\n throw new Error(\n `[--target cloudflare] config incompatible with Cloudflare Workers:\\n - ${errors.join('\\n - ')}`,\n );\n }\n },\n\n // #374 — always write wrangler.toml from the build output. Skipping when\n // the file already exists left users with a wrangler.toml that pointed at\n // dist/index.js while the build emitted dist/cloudflare/index.js, and\n // wrangler deploy silently failed.\n alwaysWriteConfig: true,\n\n // #374 round-2 — merge `frontmcp.config.deployments[].wrangler.{name,\n // compatibilityDate, compatibilityFlags}` into the rendered TOML so values\n // declared in the user's config actually reach `wrangler deploy`. Defaults\n // preserved when the field is absent or no deployment was matched.\n getConfig: (_cwd, deployment) => {\n const wrangler = (deployment as CloudflareDeployment | undefined)?.wrangler ?? {};\n const name = wrangler.name ?? 'frontmcp-worker';\n // The cloudflare entry (getEntryTemplate) is an ES Module that imports the\n // SDK's web-fetch handler, which still transitively pulls in Node builtins\n // (node:*, Buffer, process, streams) through the SDK runtime. On Workers\n // those exist ONLY behind the `nodejs_compat` flag — without it the Worker\n // fails to even load. The flag is therefore non-negotiable for this target;\n // we always emit it and merge in any extra flags the user declared (deduped,\n // `nodejs_compat` guaranteed first).\n const flags = Array.from(new Set(['nodejs_compat', ...(wrangler.compatibilityFlags ?? [])]));\n // `nodejs_compat` only provides the full Node API surface (incl. `require`\n // of builtins) when compatibility_date >= 2024-09-23; default to that so a\n // freshly-built worker boots. Users can still pin an older/newer date.\n const compatibilityDate = wrangler.compatibilityDate ?? '2024-09-23';\n return `name = \"${name}\"\nmain = \"dist/cloudflare/index.js\"\ncompatibility_date = \"${compatibilityDate}\"\ncompatibility_flags = [${flags.map((f) => `\"${f}\"`).join(', ')}]\n`;\n },\n\n configFileName: 'wrangler.toml',\n};\n"]}
@@ -586,6 +586,7 @@ var _getCmd = promptCmd
586
586
  .description('Render a prompt by name')
587
587
  ${promptGetOptionLines}
588
588
  .allowUnknownOption(true)
589
+ .allowExcessArguments(true) // #382 — without this, an out-of-spec flag like \`--bogus x\` becomes excess operands and Commander throws "too many arguments" before the action can emit the precise "unknown option(s) for prompt" error.
589
590
  .action(async function(name) {
590
591
  try {
591
592
  var spec = promptArgs[name];