frontmcp 1.1.0 → 1.1.2-beta.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.
Files changed (53) hide show
  1. package/package.json +5 -5
  2. package/src/commands/build/adapters/cloudflare.js +56 -4
  3. package/src/commands/build/adapters/cloudflare.js.map +1 -1
  4. package/src/commands/build/adapters/distributed.js +4 -1
  5. package/src/commands/build/adapters/distributed.js.map +1 -1
  6. package/src/commands/build/adapters/lambda.js +32 -0
  7. package/src/commands/build/adapters/lambda.js.map +1 -1
  8. package/src/commands/build/bundler.js +23 -1
  9. package/src/commands/build/bundler.js.map +1 -1
  10. package/src/commands/build/exec/cli-runtime/extract-public-message.snippet.d.ts +18 -0
  11. package/src/commands/build/exec/cli-runtime/extract-public-message.snippet.js +58 -0
  12. package/src/commands/build/exec/cli-runtime/extract-public-message.snippet.js.map +1 -0
  13. package/src/commands/build/exec/cli-runtime/generate-cli-entry.d.ts +2 -2
  14. package/src/commands/build/exec/cli-runtime/generate-cli-entry.js +195 -47
  15. package/src/commands/build/exec/cli-runtime/generate-cli-entry.js.map +1 -1
  16. package/src/commands/build/exec/cli-runtime/schema-extractor.d.ts +7 -0
  17. package/src/commands/build/exec/cli-runtime/schema-extractor.js +17 -1
  18. package/src/commands/build/exec/cli-runtime/schema-extractor.js.map +1 -1
  19. package/src/commands/build/exec/index.d.ts +11 -0
  20. package/src/commands/build/exec/index.js +68 -20
  21. package/src/commands/build/exec/index.js.map +1 -1
  22. package/src/commands/build/exec/installer-script.d.ts +8 -2
  23. package/src/commands/build/exec/installer-script.js +34 -15
  24. package/src/commands/build/exec/installer-script.js.map +1 -1
  25. package/src/commands/build/exec/manifest.d.ts +16 -3
  26. package/src/commands/build/exec/manifest.js +17 -5
  27. package/src/commands/build/exec/manifest.js.map +1 -1
  28. package/src/commands/build/exec/runner-script.d.ts +9 -1
  29. package/src/commands/build/exec/runner-script.js +60 -2
  30. package/src/commands/build/exec/runner-script.js.map +1 -1
  31. package/src/commands/build/index.js +86 -22
  32. package/src/commands/build/index.js.map +1 -1
  33. package/src/commands/build/load-entry-config.d.ts +33 -0
  34. package/src/commands/build/load-entry-config.js +206 -0
  35. package/src/commands/build/load-entry-config.js.map +1 -0
  36. package/src/commands/build/mcpb/manifest.d.ts +14 -0
  37. package/src/commands/build/mcpb/manifest.js +29 -0
  38. package/src/commands/build/mcpb/manifest.js.map +1 -1
  39. package/src/commands/build/types.d.ts +36 -1
  40. package/src/commands/build/types.js.map +1 -1
  41. package/src/commands/dev/doctor.js +7 -3
  42. package/src/commands/dev/doctor.js.map +1 -1
  43. package/src/commands/package/install.d.ts +1 -1
  44. package/src/commands/package/install.js +10 -8
  45. package/src/commands/package/install.js.map +1 -1
  46. package/src/commands/package/types.d.ts +2 -1
  47. package/src/commands/package/types.js.map +1 -1
  48. package/src/config/frontmcp-config.loader.d.ts +20 -0
  49. package/src/config/frontmcp-config.loader.js +152 -5
  50. package/src/config/frontmcp-config.loader.js.map +1 -1
  51. package/src/config/index.d.ts +1 -1
  52. package/src/config/index.js +2 -1
  53. package/src/config/index.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"runner-script.js","sourceRoot":"","sources":["../../../../../src/commands/build/exec/runner-script.ts"],"names":[],"mappings":";AAAA;;;GAGG;;AAIH,oDA8FC;AA9FD,SAAgB,oBAAoB,CAAC,MAA0B,EAAE,OAAiB,EAAE,OAAiB;IACnG,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;IAEzB,0DAA0D;IAC1D,IAAI,OAAO,EAAE,CAAC;QACZ,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI,MAAM,CAAC;QAC3D,MAAM,OAAO,GAAG,OAAO;YACrB,CAAC,CAAC,KAAK,IAAI,qCAAqC;YAChD,CAAC,CAAC,KAAK,IAAI,wCAAwC,CAAC;QAEtD,OAAO;;;EAGT,OAAO;yCACgC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM;;;yBAGxC,MAAM;;;;;;;;;;;;;;;;;CAiB9B,CAAC;IACA,CAAC;IAED,MAAM,WAAW,GAAG,MAAM,CAAC,WAAW,IAAI,UAAU,CAAC;IACrD,MAAM,YAAY,GAAG,eAAe,CAAC,WAAW,CAAC,CAAC;IAElD,MAAM,MAAM,GAAG,OAAO;QACpB,CAAC,CAAC,kBAAkB,IAAI,gBAAgB;QACxC,CAAC,CAAC,kBAAkB,IAAI,YAAY,CAAC;IAEvC,MAAM,OAAO,GAAG,OAAO;QACrB,CAAC,CAAC,KAAK,IAAI,4BAA4B;QACvC,CAAC,CAAC,KAAK,IAAI,2BAA2B,CAAC;IAEzC,OAAO;;;EAGP,OAAO;yCACgC,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM;;;UAG5D,MAAM;;;;;0BAKU,WAAW;;;;;6BAKR,YAAY;yBAChB,WAAW;;;;;;;uCAOG,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM;;;;;;;;;;;;;;8CActB,IAAI;;;;;;CAMjD,CAAC;AACF,CAAC;AAED,SAAS,eAAe,CAAC,OAAe;IACtC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACrC,OAAO,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AAC7C,CAAC","sourcesContent":["/**\n * Bash runner script generation.\n * The runner checks Node.js, loads .env, and runs the bundle.\n */\n\nimport { FrontmcpExecConfig } from './config';\n\nexport function generateRunnerScript(config: FrontmcpExecConfig, cliMode?: boolean, seaMode?: boolean): string {\n const name = config.name;\n\n // SEA mode: binary is self-contained, no Node.js required\n if (seaMode) {\n const binary = cliMode ? `${name}-cli-bin` : `${name}-bin`;\n const comment = cliMode\n ? `# ${name} — FrontMCP CLI (single executable)`\n : `# ${name} — FrontMCP Server (single executable)`;\n\n return `#!/usr/bin/env bash\nset -euo pipefail\n\n${comment}\n# Generated by frontmcp build --target ${cliMode ? 'cli' : 'node'}\n\nSCRIPT_DIR=\"$(cd \"$(dirname \"\\${BASH_SOURCE[0]}\")\" && pwd)\"\nBINARY=\"\\${SCRIPT_DIR}/${binary}\"\n\nif [ ! -f \"\\${BINARY}\" ]; then\n echo \"Error: Binary not found at \\${BINARY}\"\n exit 1\nfi\n\n# Load .env if present\nENV_FILE=\"\\${SCRIPT_DIR}/.env\"\nif [ -f \"\\${ENV_FILE}\" ]; then\n set -a\n # shellcheck disable=SC1090\n source \"\\${ENV_FILE}\"\n set +a\nfi\n\nexec \"\\${BINARY}\" \"$@\"\n`;\n }\n\n const nodeVersion = config.nodeVersion || '>=22.0.0';\n const minNodeMajor = extractMinMajor(nodeVersion);\n\n const bundle = cliMode\n ? `\\${SCRIPT_DIR}/${name}-cli.bundle.js`\n : `\\${SCRIPT_DIR}/${name}.bundle.js`;\n\n const comment = cliMode\n ? `# ${name} — FrontMCP CLI Executable`\n : `# ${name} — FrontMCP Server Runner`;\n\n return `#!/usr/bin/env bash\nset -euo pipefail\n\n${comment}\n# Generated by frontmcp build --target ${cliMode ? 'cli --js' : 'node'}\n\nSCRIPT_DIR=\"$(cd \"$(dirname \"\\${BASH_SOURCE[0]}\")\" && pwd)\"\nBUNDLE=\"${bundle}\"\n\n# Check Node.js\nif ! command -v node &> /dev/null; then\n echo \"Error: Node.js is required but not installed.\"\n echo \"Install Node.js ${nodeVersion}: https://nodejs.org\"\n exit 1\nfi\n\nNODE_MAJOR=$(node -e \"console.log(process.versions.node.split('.')[0])\")\nif [ \"\\${NODE_MAJOR}\" -lt \"${minNodeMajor}\" ]; then\n echo \"Error: Node.js ${nodeVersion} required, found v$(node -v)\"\n exit 1\nfi\n\n# Check bundle exists\nif [ ! -f \"\\${BUNDLE}\" ]; then\n echo \"Error: Bundle not found at \\${BUNDLE}\"\n echo \"Run 'frontmcp build --target ${cliMode ? 'cli --js' : 'node'}' to create it.\"\n exit 1\nfi\n\n# Load .env if present\nENV_FILE=\"\\${SCRIPT_DIR}/.env\"\nif [ -f \"\\${ENV_FILE}\" ]; then\n set -a\n # shellcheck disable=SC1090\n source \"\\${ENV_FILE}\"\n set +a\nfi\n\n# Enable Node.js compile cache for faster startup on warm runs\nCOMPILE_CACHE_DIR=\"\\${HOME}/.cache/frontmcp/${name}\"\nmkdir -p \"\\${COMPILE_CACHE_DIR}\" 2>/dev/null || true\nexport NODE_COMPILE_CACHE=\"\\${COMPILE_CACHE_DIR}\"\n\n# Run\nexec node \"\\${BUNDLE}\" \"$@\"\n`;\n}\n\nfunction extractMinMajor(version: string): number {\n const match = version.match(/(\\d+)/);\n return match ? parseInt(match[1], 10) : 22;\n}\n"]}
1
+ {"version":3,"file":"runner-script.js","sourceRoot":"","sources":["../../../../../src/commands/build/exec/runner-script.ts"],"names":[],"mappings":";AAAA;;;GAGG;;AAWH,oDAGC;AAED,oDA6IC;AAzJD;;;;;;GAMG;AACH,SAAgB,oBAAoB,CAAC,KAAa;IAChD,0EAA0E;IAC1E,OAAO,KAAK,CAAC,OAAO,CAAC,mBAAmB,EAAE,GAAG,CAAC,CAAC;AACjD,CAAC;AAED,SAAgB,oBAAoB,CAAC,MAA0B,EAAE,OAAiB,EAAE,OAAiB;IACnG,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;IACzB,MAAM,OAAO,GAAG,oBAAoB,CAAC,MAAM,CAAC,OAAO,IAAI,OAAO,CAAC,CAAC;IAEhE,yEAAyE;IACzE,6EAA6E;IAC7E,wEAAwE;IACxE,wEAAwE;IACxE,wBAAwB;IACxB,MAAM,eAAe,GAAG,CAAC,OAAO;QAC9B,CAAC,CAAC;;;;;EAKJ,IAAI,KAAK,OAAO;;;;;IAKd,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;;;;;;;;;;YAUI,IAAI,IAAI,OAAO;;;;0BAID,IAAI;;;;;;;;;;CAU7B;QACG,CAAC,CAAC,EAAE,CAAC;IAEP,0DAA0D;IAC1D,IAAI,OAAO,EAAE,CAAC;QACZ,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI,MAAM,CAAC;QAC3D,MAAM,OAAO,GAAG,OAAO;YACrB,CAAC,CAAC,KAAK,IAAI,qCAAqC;YAChD,CAAC,CAAC,KAAK,IAAI,wCAAwC,CAAC;QAEtD,OAAO;;;EAGT,OAAO;yCACgC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM;;;yBAGxC,MAAM;;;;;;EAM7B,eAAe;;;;;;;;;;;CAWhB,CAAC;IACA,CAAC;IAED,MAAM,WAAW,GAAG,MAAM,CAAC,WAAW,IAAI,UAAU,CAAC;IACrD,MAAM,YAAY,GAAG,eAAe,CAAC,WAAW,CAAC,CAAC;IAElD,MAAM,MAAM,GAAG,OAAO;QACpB,CAAC,CAAC,kBAAkB,IAAI,gBAAgB;QACxC,CAAC,CAAC,kBAAkB,IAAI,YAAY,CAAC;IAEvC,MAAM,OAAO,GAAG,OAAO;QACrB,CAAC,CAAC,KAAK,IAAI,4BAA4B;QACvC,CAAC,CAAC,KAAK,IAAI,2BAA2B,CAAC;IAEzC,OAAO;;;EAGP,OAAO;yCACgC,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM;;;UAG5D,MAAM;EACd,eAAe;;;;0BAIS,WAAW;;;;;6BAKR,YAAY;yBAChB,WAAW;;;;;;;uCAOG,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM;;;;;;;;;;;;;;8CActB,IAAI;;;;;;CAMjD,CAAC;AACF,CAAC;AAED,SAAS,eAAe,CAAC,OAAe;IACtC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACrC,OAAO,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AAC7C,CAAC","sourcesContent":["/**\n * Bash runner script generation.\n * The runner checks Node.js, loads .env, and runs the bundle.\n */\n\nimport { type FrontmcpExecConfig } from './config';\n\n/**\n * Defense-in-depth: scrub anything outside `[a-zA-Z0-9._+-]` from values that\n * the runner / installer scripts interpolate into bash. The user owns\n * `frontmcp.config`, so a malicious value would be self-inflicted, but the\n * generated scripts are committed into repos and downloaded by end-users —\n * so we keep them safe to copy/paste regardless of upstream config hygiene.\n */\nexport function sanitizeShellLiteral(value: string): string {\n // `-` placed last in the character class is literal, so no escape needed.\n return value.replace(/[^A-Za-z0-9._+-]/g, '_');\n}\n\nexport function generateRunnerScript(config: FrontmcpExecConfig, cliMode?: boolean, seaMode?: boolean): string {\n const name = config.name;\n const version = sanitizeShellLiteral(config.version || '0.0.0');\n\n // #377 — `--target node` runner used to silently exec the bundle for any\n // flag, so `./frontegg-bin --help` quietly booted the HTTP server. Intercept\n // help/version here so server-mode runners behave like a normal CLI for\n // those flags. CLI-mode runners pass everything through to the bundle's\n // own commander parser.\n const helpInterceptor = !cliMode\n ? `\n# Intercept standard CLI flags before booting the long-running server.\ncase \"\\${1:-}\" in\n -h|--help)\n cat <<EOF\n${name} v${version} — FrontMCP server\n\nThis binary starts a long-running MCP HTTP server.\n\nUsage:\n ${name} Start the server\n ${name} --help Show this help\n ${name} --version Show version\n ${name} --print-manifest Print the deployment manifest as JSON\n\nConfigure via environment variables, .env, or frontmcp.config.\n\nFor a CLI-style binary that exposes tools/resources/prompts as subcommands,\nbuild with: frontmcp build --target cli\nEOF\n exit 0\n ;;\n --version)\n echo \"${name} ${version}\"\n exit 0\n ;;\n --print-manifest)\n cat \"\\${SCRIPT_DIR}/${name}.manifest.json\"\n exit 0\n ;;\n --*)\n echo \"Error: unsupported flag '\\${1}' on the server runner.\"\n echo \"This binary is a long-running HTTP server; flag-style invocation is reserved.\" >&2\n echo \"Run with no args to start, or build with --target cli for a CLI binary.\" >&2\n exit 2\n ;;\nesac\n`\n : '';\n\n // SEA mode: binary is self-contained, no Node.js required\n if (seaMode) {\n const binary = cliMode ? `${name}-cli-bin` : `${name}-bin`;\n const comment = cliMode\n ? `# ${name} — FrontMCP CLI (single executable)`\n : `# ${name} — FrontMCP Server (single executable)`;\n\n return `#!/usr/bin/env bash\nset -euo pipefail\n\n${comment}\n# Generated by frontmcp build --target ${cliMode ? 'cli' : 'node'}\n\nSCRIPT_DIR=\"$(cd \"$(dirname \"\\${BASH_SOURCE[0]}\")\" && pwd)\"\nBINARY=\"\\${SCRIPT_DIR}/${binary}\"\n\nif [ ! -f \"\\${BINARY}\" ]; then\n echo \"Error: Binary not found at \\${BINARY}\"\n exit 1\nfi\n${helpInterceptor}\n# Load .env if present\nENV_FILE=\"\\${SCRIPT_DIR}/.env\"\nif [ -f \"\\${ENV_FILE}\" ]; then\n set -a\n # shellcheck disable=SC1090\n source \"\\${ENV_FILE}\"\n set +a\nfi\n\nexec \"\\${BINARY}\" \"$@\"\n`;\n }\n\n const nodeVersion = config.nodeVersion || '>=22.0.0';\n const minNodeMajor = extractMinMajor(nodeVersion);\n\n const bundle = cliMode\n ? `\\${SCRIPT_DIR}/${name}-cli.bundle.js`\n : `\\${SCRIPT_DIR}/${name}.bundle.js`;\n\n const comment = cliMode\n ? `# ${name} — FrontMCP CLI Executable`\n : `# ${name} — FrontMCP Server Runner`;\n\n return `#!/usr/bin/env bash\nset -euo pipefail\n\n${comment}\n# Generated by frontmcp build --target ${cliMode ? 'cli --js' : 'node'}\n\nSCRIPT_DIR=\"$(cd \"$(dirname \"\\${BASH_SOURCE[0]}\")\" && pwd)\"\nBUNDLE=\"${bundle}\"\n${helpInterceptor}\n# Check Node.js\nif ! command -v node &> /dev/null; then\n echo \"Error: Node.js is required but not installed.\"\n echo \"Install Node.js ${nodeVersion}: https://nodejs.org\"\n exit 1\nfi\n\nNODE_MAJOR=$(node -e \"console.log(process.versions.node.split('.')[0])\")\nif [ \"\\${NODE_MAJOR}\" -lt \"${minNodeMajor}\" ]; then\n echo \"Error: Node.js ${nodeVersion} required, found v$(node -v)\"\n exit 1\nfi\n\n# Check bundle exists\nif [ ! -f \"\\${BUNDLE}\" ]; then\n echo \"Error: Bundle not found at \\${BUNDLE}\"\n echo \"Run 'frontmcp build --target ${cliMode ? 'cli --js' : 'node'}' to create it.\"\n exit 1\nfi\n\n# Load .env if present\nENV_FILE=\"\\${SCRIPT_DIR}/.env\"\nif [ -f \"\\${ENV_FILE}\" ]; then\n set -a\n # shellcheck disable=SC1090\n source \"\\${ENV_FILE}\"\n set +a\nfi\n\n# Enable Node.js compile cache for faster startup on warm runs\nCOMPILE_CACHE_DIR=\"\\${HOME}/.cache/frontmcp/${name}\"\nmkdir -p \"\\${COMPILE_CACHE_DIR}\" 2>/dev/null || true\nexport NODE_COMPILE_CACHE=\"\\${COMPILE_CACHE_DIR}\"\n\n# Run\nexec node \"\\${BUNDLE}\" \"$@\"\n`;\n}\n\nfunction extractMinMajor(version: string): number {\n const match = version.match(/(\\d+)/);\n return match ? parseInt(match[1], 10) : 22;\n}\n"]}
@@ -16,7 +16,7 @@ function isTsLike(p) {
16
16
  /**
17
17
  * Generate adapter-specific entry point and config files.
18
18
  */
19
- async function generateAdapterFiles(adapter, outDir, entryBasename, cwd) {
19
+ async function generateAdapterFiles(adapter, outDir, entryBasename, cwd, deployment) {
20
20
  const template = adapters_1.ADAPTERS[adapter];
21
21
  // Generate serverless setup file first (if adapter has one)
22
22
  // This file sets FRONTMCP_SERVERLESS=1 before any imports run
@@ -34,6 +34,15 @@ async function generateAdapterFiles(adapter, outDir, entryBasename, cwd) {
34
34
  const entryPath = path.join(outDir, 'index.js');
35
35
  await fs_1.fsp.writeFile(entryPath, entryContent, 'utf8');
36
36
  console.log((0, colors_1.c)('green', ` Generated ${adapter} entry at ${path.relative(cwd, entryPath)}`));
37
+ // ESM adapters (vercel, lambda) emit `import` syntax in the entry. The
38
+ // user's project may be `"type": "commonjs"`, in which case Node and
39
+ // rspack treat the .js file as CJS and parsing fails on `import`.
40
+ // Drop a sibling package.json with `"type": "module"` so the dist
41
+ // directory is always interpreted correctly regardless of the parent.
42
+ if (template.moduleFormat === 'esnext') {
43
+ const pkgPath = path.join(outDir, 'package.json');
44
+ await fs_1.fsp.writeFile(pkgPath, JSON.stringify({ type: 'module' }, null, 2), 'utf8');
45
+ }
37
46
  }
38
47
  // Bundle if adapter requires it (creates single CJS file for serverless)
39
48
  if (template.shouldBundle && template.bundleOutput) {
@@ -48,24 +57,34 @@ async function generateAdapterFiles(adapter, outDir, entryBasename, cwd) {
48
57
  console.log((0, colors_1.c)('green', ` Created deployment output structure`));
49
58
  }
50
59
  }
51
- // Generate config file if adapter has one (skip if already exists)
60
+ // Generate config file if adapter has one. By default we preserve an
61
+ // existing user-edited file. Adapters that mark `alwaysWriteConfig` (e.g.,
62
+ // cloudflare's wrangler.toml) overwrite it on every build so the
63
+ // generated `main = ...` path always matches the actual build output —
64
+ // see #374.
52
65
  if (template.getConfig && template.configFileName) {
53
66
  const configPath = path.join(cwd, template.configFileName);
54
- if (await (0, utils_1.fileExists)(configPath)) {
55
- console.log((0, colors_1.c)('yellow', ` ${template.configFileName} already exists (skipping)`));
56
- }
57
- else {
58
- const configContent = template.getConfig(cwd);
67
+ const exists = await (0, utils_1.fileExists)(configPath);
68
+ const configContent = template.getConfig(cwd, deployment);
69
+ const writeIt = async () => {
59
70
  if (typeof configContent === 'string') {
60
- // Write as plain text (e.g., TOML for wrangler.toml)
61
71
  await fs_1.fsp.writeFile(configPath, configContent, 'utf8');
62
72
  }
63
73
  else {
64
- // Write as JSON
65
74
  await (0, utils_1.writeJSON)(configPath, configContent);
66
75
  }
76
+ };
77
+ if (!exists) {
78
+ await writeIt();
67
79
  console.log((0, colors_1.c)('green', ` Generated ${template.configFileName}`));
68
80
  }
81
+ else if (template.alwaysWriteConfig) {
82
+ await writeIt();
83
+ console.log((0, colors_1.c)('green', ` Updated ${template.configFileName} (build output reference)`));
84
+ }
85
+ else {
86
+ console.log((0, colors_1.c)('yellow', ` ${template.configFileName} already exists (skipping)`));
87
+ }
69
88
  }
70
89
  }
71
90
  /** Map target names to internal adapter names. */
@@ -92,14 +111,17 @@ const TARGET_TO_ADAPTER = {
92
111
  */
93
112
  async function runBuild(opts) {
94
113
  const cwd = process.cwd();
95
- // Try loading frontmcp.config for multi-target support
96
- let config;
97
- try {
98
- config = await (0, config_1.loadFrontMcpConfig)(cwd);
99
- }
100
- catch {
101
- // No config file — fall back to CLI flags only
102
- }
114
+ // Try loading frontmcp.config for multi-target support.
115
+ //
116
+ // #365 — `tryLoadFrontMcpConfig` differentiates between recoverable cases:
117
+ // - no config file present → returns undefined
118
+ // - config exists but doesn't match the new schema → returns undefined
119
+ // (legacy top-level `cli`/`sea`/`esbuild` shape — picked up later by
120
+ // the exec-build's own `loadExecConfig`)
121
+ // …and hard failures:
122
+ // - file exists but can't be parsed (TS syntax error, broken require)
123
+ // → the error propagates, no silent-default regression.
124
+ const config = await (0, config_1.tryLoadFrontMcpConfig)(cwd);
103
125
  // If no -t flag and config has deployments, build all targets from config
104
126
  if (!opts.buildTarget && config && config.deployments.length > 0) {
105
127
  const targets = (0, config_1.getDeploymentTargets)(config);
@@ -128,14 +150,44 @@ async function buildSingleTarget(target, opts, config) {
128
150
  // Merge entry from config if not provided via CLI
129
151
  const entry = opts.entry || config?.entry;
130
152
  const targetOpts = { ...opts, outDir: targetOutDir, entry };
153
+ // #370: forward `build.storage` and per-deployment `cli.outputDefault` from
154
+ // the FrontMcp config into the exec build so the manifest reflects them.
155
+ // The exec build has its own loader (`loadExecConfig`) that doesn't see
156
+ // the deployment-level shape; passing these via opts merges them in
157
+ // `normalizeConfig` before the manifest is generated.
158
+ //
159
+ // Only the cli deployment shape carries a `cli` block; map it down to the
160
+ // narrow exec-config shape (outputDefault / description / authRequired) so
161
+ // the result is a clean object, never `false`.
162
+ const cliDeploymentConfig = deployment?.target === 'cli' ? deployment.cli : undefined;
163
+ const execOverrides = {
164
+ storage: config?.build?.storage,
165
+ cli: cliDeploymentConfig
166
+ ? {
167
+ ...(cliDeploymentConfig.outputDefault ? { outputDefault: cliDeploymentConfig.outputDefault } : {}),
168
+ ...(cliDeploymentConfig.description ? { description: cliDeploymentConfig.description } : {}),
169
+ ...(typeof cliDeploymentConfig.authRequired === 'boolean'
170
+ ? { authRequired: cliDeploymentConfig.authRequired }
171
+ : {}),
172
+ }
173
+ : undefined,
174
+ };
131
175
  switch (target) {
132
176
  case 'cli': {
133
177
  const { buildExec } = await import('./exec/index.js');
134
- return buildExec({ ...targetOpts, cli: true, sea: !opts.js });
178
+ return buildExec({
179
+ ...targetOpts,
180
+ cli: true,
181
+ sea: !opts.js,
182
+ execOverrides,
183
+ });
135
184
  }
136
185
  case 'node': {
137
186
  const { buildExec } = await import('./exec/index.js');
138
- return buildExec(targetOpts);
187
+ return buildExec({
188
+ ...targetOpts,
189
+ execOverrides,
190
+ });
139
191
  }
140
192
  case 'sdk': {
141
193
  const { buildSdk } = await import('./sdk/index.js');
@@ -154,7 +206,7 @@ async function buildSingleTarget(target, opts, config) {
154
206
  case 'cloudflare':
155
207
  case 'distributed': {
156
208
  const adapter = TARGET_TO_ADAPTER[target];
157
- return runAdapterBuild(targetOpts, adapter);
209
+ return runAdapterBuild(targetOpts, adapter, deployment);
158
210
  }
159
211
  default:
160
212
  throw new Error(`Unknown build target: ${target}. Available: cli, node, sdk, browser, cloudflare, vercel, lambda, distributed, mcpb`);
@@ -163,7 +215,7 @@ async function buildSingleTarget(target, opts, config) {
163
215
  /**
164
216
  * Build using a deployment adapter (serverless platforms).
165
217
  */
166
- async function runAdapterBuild(opts, adapter) {
218
+ async function runAdapterBuild(opts, adapter, deployment) {
167
219
  const cwd = process.cwd();
168
220
  const entry = await (0, fs_1.resolveEntry)(cwd, opts.entry);
169
221
  const outDir = path.resolve(cwd, opts.outDir || 'dist');
@@ -176,6 +228,18 @@ async function runAdapterBuild(opts, adapter) {
176
228
  if (adapter === 'cloudflare') {
177
229
  console.log((0, colors_1.c)('yellow', 'Cloudflare Workers adapter is experimental. See docs for limitations.'));
178
230
  }
231
+ // #375 — adapter-level pre-validation: read the entry's @FrontMcp() config
232
+ // metadata and let the adapter reject incompatible features
233
+ // (sqlite/redis on Workers, etc.) before emitting an unrunnable bundle.
234
+ // `loadEntryDecoratorInfo` handles both compiled-JS and raw-TS entries
235
+ // (TS goes through esbuild + Module._compile so we don't need a tsc pass)
236
+ // AND scans the source AST for keys hidden behind env-gated ternaries —
237
+ // round-2 made the env-gated case the headline #375 reproducer.
238
+ if (template.validate) {
239
+ const { loadEntryDecoratorInfo } = await import('./load-entry-config.js');
240
+ const info = await loadEntryDecoratorInfo(entry);
241
+ template.validate(info.decoratorConfig, { keysSeenInSource: info.keysSeenInSource });
242
+ }
179
243
  const moduleFormat = template.moduleFormat;
180
244
  console.log(`${(0, colors_1.c)('cyan', '[build]')} entry: ${path.relative(cwd, entry)}`);
181
245
  console.log(`${(0, colors_1.c)('cyan', '[build]')} outDir: ${path.relative(cwd, outDir)}`);
@@ -204,7 +268,7 @@ async function runAdapterBuild(opts, adapter) {
204
268
  if (adapter !== 'node') {
205
269
  console.log((0, colors_1.c)('cyan', `[build] Generating ${adapter} deployment files...`));
206
270
  const entryBasename = path.basename(entry);
207
- await generateAdapterFiles(adapter, outDir, entryBasename, cwd);
271
+ await generateAdapterFiles(adapter, outDir, entryBasename, cwd, deployment);
208
272
  }
209
273
  console.log((0, colors_1.c)('green', 'Build completed.'));
210
274
  console.log((0, colors_1.c)('gray', `Output placed in ${path.relative(cwd, outDir)}`));
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/commands/build/index.ts"],"names":[],"mappings":";;AAyGA,4BA0BC;;AAnID,mDAA6B;AAE7B,8CAAsC;AACtC,2CAA2E;AAC3E,wCAAoD;AACpD,kDAAgE;AAChE,yCAAsC;AAEtC,uCAAgD;AAChD,yCAAmH;AAEnH,SAAS,QAAQ,CAAC,CAAS;IACzB,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAC5B,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,oBAAoB,CACjC,OAAoB,EACpB,MAAc,EACd,aAAqB,EACrB,GAAW;IAEX,MAAM,QAAQ,GAAG,mBAAQ,CAAC,OAAO,CAAC,CAAC;IAEnC,4DAA4D;IAC5D,8DAA8D;IAC9D,IAAI,QAAQ,CAAC,gBAAgB,EAAE,CAAC;QAC9B,MAAM,YAAY,GAAG,QAAQ,CAAC,gBAAgB,EAAE,CAAC;QACjD,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC;QAC3D,MAAM,QAAG,CAAC,SAAS,CAAC,SAAS,EAAE,YAAY,EAAE,MAAM,CAAC,CAAC;QACrD,OAAO,CAAC,GAAG,CAAC,IAAA,UAAC,EAAC,OAAO,EAAE,mCAAmC,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC;IAC9F,CAAC;IAED,gCAAgC;IAChC,MAAM,cAAc,GAAG,aAAa,CAAC,OAAO,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;IAC/D,MAAM,YAAY,GAAG,QAAQ,CAAC,gBAAgB,CAAC,KAAK,cAAc,EAAE,CAAC,CAAC;IAEtE,iDAAiD;IACjD,IAAI,YAAY,EAAE,CAAC;QACjB,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;QAChD,MAAM,QAAG,CAAC,SAAS,CAAC,SAAS,EAAE,YAAY,EAAE,MAAM,CAAC,CAAC;QACrD,OAAO,CAAC,GAAG,CAAC,IAAA,UAAC,EAAC,OAAO,EAAE,eAAe,OAAO,aAAa,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC;IAC9F,CAAC;IAED,yEAAyE;IACzE,IAAI,QAAQ,CAAC,YAAY,IAAI,QAAQ,CAAC,YAAY,EAAE,CAAC;QACnD,OAAO,CAAC,GAAG,CAAC,IAAA,UAAC,EAAC,MAAM,EAAE,wBAAwB,OAAO,KAAK,CAAC,CAAC,CAAC;QAC7D,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;QAChD,MAAM,IAAA,6BAAmB,EAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,CAAC,YAAY,CAAC,CAAC;QACpE,OAAO,CAAC,GAAG,CAAC,IAAA,UAAC,EAAC,OAAO,EAAE,qBAAqB,QAAQ,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC;QAEtE,4EAA4E;QAC5E,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC;YACxB,OAAO,CAAC,GAAG,CAAC,IAAA,UAAC,EAAC,MAAM,EAAE,oBAAoB,OAAO,0BAA0B,CAAC,CAAC,CAAC;YAC9E,MAAM,QAAQ,CAAC,UAAU,CAAC,MAAM,EAAE,GAAG,EAAE,QAAQ,CAAC,YAAY,CAAC,CAAC;YAC9D,OAAO,CAAC,GAAG,CAAC,IAAA,UAAC,EAAC,OAAO,EAAE,uCAAuC,CAAC,CAAC,CAAC;QACnE,CAAC;IACH,CAAC;IAED,mEAAmE;IACnE,IAAI,QAAQ,CAAC,SAAS,IAAI,QAAQ,CAAC,cAAc,EAAE,CAAC;QAClD,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,cAAc,CAAC,CAAC;QAE3D,IAAI,MAAM,IAAA,kBAAU,EAAC,UAAU,CAAC,EAAE,CAAC;YACjC,OAAO,CAAC,GAAG,CAAC,IAAA,UAAC,EAAC,QAAQ,EAAE,KAAK,QAAQ,CAAC,cAAc,4BAA4B,CAAC,CAAC,CAAC;QACrF,CAAC;aAAM,CAAC;YACN,MAAM,aAAa,GAAG,QAAQ,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;YAE9C,IAAI,OAAO,aAAa,KAAK,QAAQ,EAAE,CAAC;gBACtC,qDAAqD;gBACrD,MAAM,QAAG,CAAC,SAAS,CAAC,UAAU,EAAE,aAAa,EAAE,MAAM,CAAC,CAAC;YACzD,CAAC;iBAAM,CAAC;gBACN,gBAAgB;gBAChB,MAAM,IAAA,iBAAS,EAAC,UAAU,EAAE,aAAa,CAAC,CAAC;YAC7C,CAAC;YACD,OAAO,CAAC,GAAG,CAAC,IAAA,UAAC,EAAC,OAAO,EAAE,eAAe,QAAQ,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC;QACpE,CAAC;IACH,CAAC;AACH,CAAC;AAED,kDAAkD;AAClD,MAAM,iBAAiB,GAAgC;IACrD,QAAQ,EAAE,QAAQ;IAClB,QAAQ,EAAE,QAAQ;IAClB,YAAY,EAAE,YAAY;IAC1B,aAAa,EAAE,aAAa;CAC7B,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACI,KAAK,UAAU,QAAQ,CAAC,IAAgB;IAC7C,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAE1B,uDAAuD;IACvD,IAAI,MAAwC,CAAC;IAC7C,IAAI,CAAC;QACH,MAAM,GAAG,MAAM,IAAA,2BAAkB,EAAC,GAAG,CAAC,CAAC;IACzC,CAAC;IAAC,MAAM,CAAC;QACP,+CAA+C;IACjD,CAAC;IAED,0EAA0E;IAC1E,IAAI,CAAC,IAAI,CAAC,WAAW,IAAI,MAAM,IAAI,MAAM,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACjE,MAAM,OAAO,GAAG,IAAA,6BAAoB,EAAC,MAAM,CAAC,CAAC;QAC7C,OAAO,CAAC,GAAG,CAAC,IAAA,UAAC,EAAC,MAAM,EAAE,oBAAoB,OAAO,CAAC,MAAM,oCAAoC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;QAEnH,KAAK,MAAM,UAAU,IAAI,OAAO,EAAE,CAAC;YACjC,OAAO,CAAC,GAAG,CAAC,IAAA,UAAC,EAAC,MAAM,EAAE,iBAAiB,UAAU,MAAM,CAAC,CAAC,CAAC;YAC1D,MAAM,iBAAiB,CAAC,UAAU,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;QACpD,CAAC;QACD,OAAO;IACT,CAAC;IAED,uDAAuD;IACvD,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,IAAI,MAAM,CAAC;IAC1C,MAAM,iBAAiB,CAAC,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;AAChD,CAAC;AAED;;;GAGG;AACH,KAAK,UAAU,iBAAiB,CAC9B,MAAc,EACd,IAAgB,EAChB,MAA6B;IAE7B,MAAM,UAAU,GAAG,MAAM,CAAC,CAAC,CAAC,IAAA,uBAAc,EAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAEvE,8EAA8E;IAC9E,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,CAAC;IACtE,MAAM,YAAY,GAAG,UAAU,EAAE,MAAM;QACrC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,UAAU,CAAC,MAAM,CAAC;QAChD,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IAElC,kDAAkD;IAClD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,MAAM,EAAE,KAAK,CAAC;IAC1C,MAAM,UAAU,GAAG,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC;IAE5D,QAAQ,MAAM,EAAE,CAAC;QACf,KAAK,KAAK,CAAC,CAAC,CAAC;YACX,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,MAAM,CAAC,iBAAiB,CAAC,CAAC;YACtD,OAAO,SAAS,CAAC,EAAE,GAAG,UAAU,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,EAAE,EAAiD,CAAC,CAAC;QAC/G,CAAC;QACD,KAAK,MAAM,CAAC,CAAC,CAAC;YACZ,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,MAAM,CAAC,iBAAiB,CAAC,CAAC;YACtD,OAAO,SAAS,CAAC,UAAU,CAAC,CAAC;QAC/B,CAAC;QACD,KAAK,KAAK,CAAC,CAAC,CAAC;YACX,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,MAAM,CAAC,gBAAgB,CAAC,CAAC;YACpD,OAAO,QAAQ,CAAC,UAAU,CAAC,CAAC;QAC9B,CAAC;QACD,KAAK,SAAS,CAAC,CAAC,CAAC;YACf,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,MAAM,CAAC,oBAAoB,CAAC,CAAC;YAC5D,OAAO,YAAY,CAAC,UAAU,CAAC,CAAC;QAClC,CAAC;QACD,KAAK,MAAM,CAAC,CAAC,CAAC;YACZ,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,MAAM,CAAC,iBAAiB,CAAC,CAAC;YACtD,OAAO,SAAS,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;QACvC,CAAC;QACD,KAAK,QAAQ,CAAC;QACd,KAAK,QAAQ,CAAC;QACd,KAAK,YAAY,CAAC;QAClB,KAAK,aAAa,CAAC,CAAC,CAAC;YACnB,MAAM,OAAO,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC;YAC1C,OAAO,eAAe,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QAC9C,CAAC;QACD;YACE,MAAM,IAAI,KAAK,CAAC,yBAAyB,MAAM,qFAAqF,CAAC,CAAC;IAC1I,CAAC;AACH,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,eAAe,CAAC,IAAgB,EAAE,OAAoB;IACnE,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAC1B,MAAM,KAAK,GAAG,MAAM,IAAA,iBAAY,EAAC,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;IAClD,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,CAAC;IACxD,MAAM,IAAA,iBAAS,EAAC,MAAM,CAAC,CAAC;IAExB,MAAM,QAAQ,GAAG,mBAAQ,CAAC,OAAO,CAAC,CAAC;IACnC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,mBAAQ,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnD,MAAM,IAAI,KAAK,CAAC,oBAAoB,OAAO,gBAAgB,SAAS,EAAE,CAAC,CAAC;IAC1E,CAAC;IAED,IAAI,OAAO,KAAK,YAAY,EAAE,CAAC;QAC7B,OAAO,CAAC,GAAG,CACT,IAAA,UAAC,EAAC,QAAQ,EAAE,uEAAuE,CAAC,CACrF,CAAC;IACJ,CAAC;IAED,MAAM,YAAY,GAAG,QAAQ,CAAC,YAAY,CAAC;IAE3C,OAAO,CAAC,GAAG,CAAC,GAAG,IAAA,UAAC,EAAC,MAAM,EAAE,SAAS,CAAC,WAAW,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC;IAC3E,OAAO,CAAC,GAAG,CAAC,GAAG,IAAA,UAAC,EAAC,MAAM,EAAE,SAAS,CAAC,YAAY,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;IAC7E,OAAO,CAAC,GAAG,CAAC,GAAG,IAAA,UAAC,EAAC,MAAM,EAAE,SAAS,CAAC,YAAY,OAAO,KAAK,YAAY,GAAG,CAAC,CAAC;IAE5E,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,eAAe,CAAC,CAAC;IACrD,MAAM,WAAW,GAAG,MAAM,IAAA,kBAAU,EAAC,YAAY,CAAC,CAAC;IACnD,MAAM,IAAI,GAAa,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAErC,IAAI,WAAW,EAAE,CAAC;QAChB,OAAO,CAAC,GAAG,CAAC,IAAA,UAAC,EAAC,MAAM,EAAE,kEAAkE,CAAC,CAAC,CAAC;QAC3F,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC;IACvC,CAAC;SAAM,CAAC;QACN,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACjB,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;QAC5C,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;YACrB,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YACvB,OAAO,CAAC,GAAG,CAAC,IAAA,UAAC,EAAC,QAAQ,EAAE,qDAAqD,CAAC,CAAC,CAAC;QAClF,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,0BAA0B,EAAE,yBAAyB,CAAC,CAAC;QACjE,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,oCAAyB,CAAC,MAAM,CAAC,CAAC;IAC1D,CAAC;IAED,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;IACpC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IAC9B,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;IAE5B,MAAM,IAAA,cAAM,EAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAE1B,IAAI,OAAO,KAAK,MAAM,EAAE,CAAC;QACvB,OAAO,CAAC,GAAG,CAAC,IAAA,UAAC,EAAC,MAAM,EAAE,sBAAsB,OAAO,sBAAsB,CAAC,CAAC,CAAC;QAC5E,MAAM,aAAa,GAAG,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QAC3C,MAAM,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,GAAG,CAAC,CAAC;IAClE,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,IAAA,UAAC,EAAC,OAAO,EAAE,kBAAkB,CAAC,CAAC,CAAC;IAC5C,OAAO,CAAC,GAAG,CAAC,IAAA,UAAC,EAAC,MAAM,EAAE,oBAAoB,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC;AAC3E,CAAC","sourcesContent":["import * as path from 'path';\nimport { type ParsedArgs } from '../../core/args';\nimport { c } from '../../core/colors';\nimport { ensureDir, fileExists, runCmd, writeJSON } from '@frontmcp/utils';\nimport { fsp, resolveEntry } from '../../shared/fs';\nimport { REQUIRED_DECORATOR_FIELDS } from '../../core/tsconfig';\nimport { ADAPTERS } from './adapters';\nimport { type AdapterName } from './types';\nimport { bundleForServerless } from './bundler';\nimport { findDeployment, type FrontMcpConfigParsed, getDeploymentTargets, loadFrontMcpConfig } from '../../config';\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/** Map target names to internal adapter names. */\nconst TARGET_TO_ADAPTER: Record<string, AdapterName> = {\n 'vercel': 'vercel',\n 'lambda': 'lambda',\n 'cloudflare': 'cloudflare',\n 'distributed': 'distributed',\n};\n\n/**\n * Build the FrontMCP server for a specific deployment target.\n *\n * @example\n * ```bash\n * frontmcp build --target node # Node.js server bundle\n * frontmcp build --target cli # CLI with SEA binary\n * frontmcp build --target cli --js # CLI without SEA\n * frontmcp build --target sdk # Library (CJS+ESM+types)\n * frontmcp build --target browser # Browser ESM bundle\n * frontmcp build --target vercel # Vercel serverless\n * frontmcp build --target lambda # AWS Lambda\n * frontmcp build --target cloudflare # Cloudflare Workers\n * ```\n */\nexport async function runBuild(opts: ParsedArgs): Promise<void> {\n const cwd = process.cwd();\n\n // Try loading frontmcp.config for multi-target support\n let config: FrontMcpConfigParsed | undefined;\n try {\n config = await loadFrontMcpConfig(cwd);\n } catch {\n // No config file — fall back to CLI flags only\n }\n\n // If no -t flag and config has deployments, build all targets from config\n if (!opts.buildTarget && config && config.deployments.length > 0) {\n const targets = getDeploymentTargets(config);\n console.log(c('cyan', `[build] Building ${targets.length} target(s) from frontmcp.config: ${targets.join(', ')}`));\n\n for (const targetName of targets) {\n console.log(c('cyan', `\\n[build] ═══ ${targetName} ═══`));\n await buildSingleTarget(targetName, opts, config);\n }\n return;\n }\n\n // Single target build (from -t flag or default 'node')\n const target = opts.buildTarget ?? 'node';\n await buildSingleTarget(target, opts, config);\n}\n\n/**\n * Build a single deployment target.\n * Merges per-target config (from frontmcp.config) with CLI opts.\n */\nasync function buildSingleTarget(\n target: string,\n opts: ParsedArgs,\n config?: FrontMcpConfigParsed,\n): Promise<void> {\n const deployment = config ? findDeployment(config, target) : undefined;\n\n // Resolve output directory: deployment.outDir > CLI --out-dir > dist/{target}\n const baseOutDir = path.resolve(process.cwd(), opts.outDir || 'dist');\n const targetOutDir = deployment?.outDir\n ? path.resolve(process.cwd(), deployment.outDir)\n : path.join(baseOutDir, target);\n\n // Merge entry from config if not provided via CLI\n const entry = opts.entry || config?.entry;\n const targetOpts = { ...opts, outDir: targetOutDir, entry };\n\n switch (target) {\n case 'cli': {\n const { buildExec } = await import('./exec/index.js');\n return buildExec({ ...targetOpts, cli: true, sea: !opts.js } as ParsedArgs & { cli: boolean; sea: boolean });\n }\n case 'node': {\n const { buildExec } = await import('./exec/index.js');\n return buildExec(targetOpts);\n }\n case 'sdk': {\n const { buildSdk } = await import('./sdk/index.js');\n return buildSdk(targetOpts);\n }\n case 'browser': {\n const { buildBrowser } = await import('./browser/index.js');\n return buildBrowser(targetOpts);\n }\n case 'mcpb': {\n const { buildMcpb } = await import('./mcpb/index.js');\n return buildMcpb(targetOpts, config);\n }\n case 'vercel':\n case 'lambda':\n case 'cloudflare':\n case 'distributed': {\n const adapter = TARGET_TO_ADAPTER[target];\n return runAdapterBuild(targetOpts, adapter);\n }\n default:\n throw new Error(`Unknown build target: ${target}. Available: cli, node, sdk, browser, cloudflare, vercel, lambda, distributed, mcpb`);\n }\n}\n\n/**\n * Build using a deployment adapter (serverless platforms).\n */\nasync function runAdapterBuild(opts: ParsedArgs, adapter: AdapterName): 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 await ensureDir(outDir);\n\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 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]')} target: ${adapter} (${moduleFormat})`);\n\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 args.push('--module', moduleFormat);\n args.push('--outDir', outDir);\n args.push('--skipLibCheck');\n\n await runCmd('npx', args);\n\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 { type ParsedArgs } from '../../core/args';\nimport { c } from '../../core/colors';\nimport { ensureDir, fileExists, runCmd, writeJSON } from '@frontmcp/utils';\nimport { fsp, resolveEntry } from '../../shared/fs';\nimport { REQUIRED_DECORATOR_FIELDS } from '../../core/tsconfig';\nimport { ADAPTERS } from './adapters';\nimport { type AdapterName } from './types';\nimport { bundleForServerless } from './bundler';\nimport {\n type DeploymentTarget,\n findDeployment,\n type FrontMcpConfigParsed,\n getDeploymentTargets,\n tryLoadFrontMcpConfig,\n} from '../../config';\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 deployment?: DeploymentTarget,\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 // ESM adapters (vercel, lambda) emit `import` syntax in the entry. The\n // user's project may be `\"type\": \"commonjs\"`, in which case Node and\n // rspack treat the .js file as CJS and parsing fails on `import`.\n // Drop a sibling package.json with `\"type\": \"module\"` so the dist\n // directory is always interpreted correctly regardless of the parent.\n if (template.moduleFormat === 'esnext') {\n const pkgPath = path.join(outDir, 'package.json');\n await fsp.writeFile(pkgPath, JSON.stringify({ type: 'module' }, null, 2), 'utf8');\n }\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. By default we preserve an\n // existing user-edited file. Adapters that mark `alwaysWriteConfig` (e.g.,\n // cloudflare's wrangler.toml) overwrite it on every build so the\n // generated `main = ...` path always matches the actual build output —\n // see #374.\n if (template.getConfig && template.configFileName) {\n const configPath = path.join(cwd, template.configFileName);\n const exists = await fileExists(configPath);\n\n const configContent = template.getConfig(cwd, deployment);\n const writeIt = async (): Promise<void> => {\n if (typeof configContent === 'string') {\n await fsp.writeFile(configPath, configContent, 'utf8');\n } else {\n await writeJSON(configPath, configContent);\n }\n };\n\n if (!exists) {\n await writeIt();\n console.log(c('green', ` Generated ${template.configFileName}`));\n } else if (template.alwaysWriteConfig) {\n await writeIt();\n console.log(c('green', ` Updated ${template.configFileName} (build output reference)`));\n } else {\n console.log(c('yellow', ` ${template.configFileName} already exists (skipping)`));\n }\n }\n}\n\n/** Map target names to internal adapter names. */\nconst TARGET_TO_ADAPTER: Record<string, AdapterName> = {\n 'vercel': 'vercel',\n 'lambda': 'lambda',\n 'cloudflare': 'cloudflare',\n 'distributed': 'distributed',\n};\n\n/**\n * Build the FrontMCP server for a specific deployment target.\n *\n * @example\n * ```bash\n * frontmcp build --target node # Node.js server bundle\n * frontmcp build --target cli # CLI with SEA binary\n * frontmcp build --target cli --js # CLI without SEA\n * frontmcp build --target sdk # Library (CJS+ESM+types)\n * frontmcp build --target browser # Browser ESM bundle\n * frontmcp build --target vercel # Vercel serverless\n * frontmcp build --target lambda # AWS Lambda\n * frontmcp build --target cloudflare # Cloudflare Workers\n * ```\n */\nexport async function runBuild(opts: ParsedArgs): Promise<void> {\n const cwd = process.cwd();\n\n // Try loading frontmcp.config for multi-target support.\n //\n // #365 — `tryLoadFrontMcpConfig` differentiates between recoverable cases:\n // - no config file present → returns undefined\n // - config exists but doesn't match the new schema → returns undefined\n // (legacy top-level `cli`/`sea`/`esbuild` shape — picked up later by\n // the exec-build's own `loadExecConfig`)\n // …and hard failures:\n // - file exists but can't be parsed (TS syntax error, broken require)\n // → the error propagates, no silent-default regression.\n const config: FrontMcpConfigParsed | undefined = await tryLoadFrontMcpConfig(cwd);\n\n // If no -t flag and config has deployments, build all targets from config\n if (!opts.buildTarget && config && config.deployments.length > 0) {\n const targets = getDeploymentTargets(config);\n console.log(c('cyan', `[build] Building ${targets.length} target(s) from frontmcp.config: ${targets.join(', ')}`));\n\n for (const targetName of targets) {\n console.log(c('cyan', `\\n[build] ═══ ${targetName} ═══`));\n await buildSingleTarget(targetName, opts, config);\n }\n return;\n }\n\n // Single target build (from -t flag or default 'node')\n const target = opts.buildTarget ?? 'node';\n await buildSingleTarget(target, opts, config);\n}\n\n/**\n * Build a single deployment target.\n * Merges per-target config (from frontmcp.config) with CLI opts.\n */\nasync function buildSingleTarget(\n target: string,\n opts: ParsedArgs,\n config?: FrontMcpConfigParsed,\n): Promise<void> {\n const deployment:DeploymentTarget | undefined = config ? findDeployment(config, target) : undefined;\n\n // Resolve output directory: deployment.outDir > CLI --out-dir > dist/{target}\n const baseOutDir = path.resolve(process.cwd(), opts.outDir || 'dist');\n const targetOutDir = deployment?.outDir\n ? path.resolve(process.cwd(), deployment.outDir)\n : path.join(baseOutDir, target);\n\n // Merge entry from config if not provided via CLI\n const entry = opts.entry || config?.entry;\n const targetOpts = { ...opts, outDir: targetOutDir, entry };\n\n // #370: forward `build.storage` and per-deployment `cli.outputDefault` from\n // the FrontMcp config into the exec build so the manifest reflects them.\n // The exec build has its own loader (`loadExecConfig`) that doesn't see\n // the deployment-level shape; passing these via opts merges them in\n // `normalizeConfig` before the manifest is generated.\n //\n // Only the cli deployment shape carries a `cli` block; map it down to the\n // narrow exec-config shape (outputDefault / description / authRequired) so\n // the result is a clean object, never `false`.\n const cliDeploymentConfig = deployment?.target === 'cli' ? deployment.cli : undefined;\n const execOverrides: {\n storage?: { type: 'sqlite' | 'redis' | 'none'; required?: boolean };\n cli?: { outputDefault?: 'text' | 'json'; description?: string; authRequired?: boolean };\n } = {\n storage: config?.build?.storage,\n cli: cliDeploymentConfig\n ? {\n ...(cliDeploymentConfig.outputDefault ? { outputDefault: cliDeploymentConfig.outputDefault } : {}),\n ...(cliDeploymentConfig.description ? { description: cliDeploymentConfig.description } : {}),\n ...(typeof cliDeploymentConfig.authRequired === 'boolean'\n ? { authRequired: cliDeploymentConfig.authRequired }\n : {}),\n }\n : undefined,\n };\n\n switch (target) {\n case 'cli': {\n const { buildExec } = await import('./exec/index.js');\n return buildExec({\n ...targetOpts,\n cli: true,\n sea: !opts.js,\n execOverrides,\n } as ParsedArgs & { cli: boolean; sea: boolean; execOverrides?: typeof execOverrides });\n }\n case 'node': {\n const { buildExec } = await import('./exec/index.js');\n return buildExec({\n ...targetOpts,\n execOverrides,\n } as ParsedArgs & { execOverrides?: typeof execOverrides });\n }\n case 'sdk': {\n const { buildSdk } = await import('./sdk/index.js');\n return buildSdk(targetOpts);\n }\n case 'browser': {\n const { buildBrowser } = await import('./browser/index.js');\n return buildBrowser(targetOpts);\n }\n case 'mcpb': {\n const { buildMcpb } = await import('./mcpb/index.js');\n return buildMcpb(targetOpts, config);\n }\n case 'vercel':\n case 'lambda':\n case 'cloudflare':\n case 'distributed': {\n const adapter = TARGET_TO_ADAPTER[target];\n return runAdapterBuild(targetOpts, adapter, deployment);\n }\n default:\n throw new Error(`Unknown build target: ${target}. Available: cli, node, sdk, browser, cloudflare, vercel, lambda, distributed, mcpb`);\n }\n}\n\n/**\n * Build using a deployment adapter (serverless platforms).\n */\nasync function runAdapterBuild(\n opts: ParsedArgs,\n adapter: AdapterName,\n deployment?: DeploymentTarget,\n): 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 await ensureDir(outDir);\n\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 if (adapter === 'cloudflare') {\n console.log(\n c('yellow', 'Cloudflare Workers adapter is experimental. See docs for limitations.'),\n );\n }\n\n // #375 — adapter-level pre-validation: read the entry's @FrontMcp() config\n // metadata and let the adapter reject incompatible features\n // (sqlite/redis on Workers, etc.) before emitting an unrunnable bundle.\n // `loadEntryDecoratorInfo` handles both compiled-JS and raw-TS entries\n // (TS goes through esbuild + Module._compile so we don't need a tsc pass)\n // AND scans the source AST for keys hidden behind env-gated ternaries —\n // round-2 made the env-gated case the headline #375 reproducer.\n if (template.validate) {\n const { loadEntryDecoratorInfo } = await import('./load-entry-config.js');\n const info = await loadEntryDecoratorInfo(entry);\n template.validate(info.decoratorConfig, { keysSeenInSource: info.keysSeenInSource });\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]')} target: ${adapter} (${moduleFormat})`);\n\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 args.push('--module', moduleFormat);\n args.push('--outDir', outDir);\n args.push('--skipLibCheck');\n\n await runCmd('npx', args);\n\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, deployment);\n }\n\n console.log(c('green', 'Build completed.'));\n console.log(c('gray', `Output placed in ${path.relative(cwd, outDir)}`));\n}\n"]}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Result returned alongside the runtime-evaluated decorator config. The
3
+ * `keysSeenInSource` field carries top-level identifiers found in the
4
+ * `@FrontMcp({...})` arg expression even when those keys' values are
5
+ * conditional or env-gated — a `validate` hook can use it to fail loud
6
+ * on `sqlite: process.env.X ? {...} : undefined` patterns that the
7
+ * runtime config object alone can't catch (#375 round-2).
8
+ */
9
+ export interface EntryDecoratorInfo {
10
+ /** Best-effort runtime config object (when the decorator was evaluated). */
11
+ decoratorConfig: Record<string, unknown> | undefined;
12
+ /**
13
+ * Top-level property names that appear in `@FrontMcp({...})` argument
14
+ * expressions in the entry source — regardless of whether they're literals,
15
+ * ternaries, function calls, or env-gated branches. Used by adapter
16
+ * validators to reject runtime-incompatible options before bundling.
17
+ */
18
+ keysSeenInSource: string[];
19
+ }
20
+ /**
21
+ * Load the entry, extract its `@FrontMcp` decorator config, and return it.
22
+ * Falls back to esbuild for TS entries.
23
+ */
24
+ export declare function loadEntryDecoratorConfig(entry: string): Promise<Record<string, unknown> | undefined>;
25
+ /**
26
+ * Round-2 (#375): same as `loadEntryDecoratorConfig` but also returns the
27
+ * set of top-level property names found in any `@FrontMcp({...})` argument
28
+ * expression in the entry source. This catches conditional-config patterns
29
+ * like `sqlite: process.env.X ? {...} : undefined` where the runtime config
30
+ * object's `sqlite` key may evaluate to `undefined` at decorator-construction
31
+ * time but the bundled output still ships a Node-only branch.
32
+ */
33
+ export declare function loadEntryDecoratorInfo(entry: string): Promise<EntryDecoratorInfo>;
@@ -0,0 +1,206 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.loadEntryDecoratorConfig = loadEntryDecoratorConfig;
4
+ exports.loadEntryDecoratorInfo = loadEntryDecoratorInfo;
5
+ const tslib_1 = require("tslib");
6
+ /**
7
+ * Best-effort load of a user's `@FrontMcp()`-decorated entry to read its
8
+ * decorator config at build time.
9
+ *
10
+ * Why it exists: `runAdapterBuild` and the adapter `validate` hook need
11
+ * `@FrontMcp({ http, sqlite, redis })` *before* TypeScript compilation runs,
12
+ * but the entry itself is usually `./src/main.ts` — `require()` can't load it
13
+ * without a TS hook. Falling back to esbuild + `Module._compile` lets us
14
+ * read the metadata regardless of whether the entry is `.ts` or `.js`.
15
+ *
16
+ * Returns `undefined` when the entry can't be loaded or doesn't carry a
17
+ * decorator config (plain config object, missing decorator, etc.). Callers
18
+ * should treat that as "no metadata available" and not as a build failure.
19
+ */
20
+ const fs = tslib_1.__importStar(require("fs"));
21
+ const path = tslib_1.__importStar(require("path"));
22
+ const FRONTMCP_CONFIG_METADATA_KEY = '__frontmcp:config';
23
+ function readDecoratorMetadata(target) {
24
+ if (typeof target !== 'function') {
25
+ if (target && typeof target === 'object') {
26
+ // Plain config object — return as-is so the validate() can inspect
27
+ // top-level fields like `sqlite`, `redis`, `http` directly.
28
+ return target;
29
+ }
30
+ return undefined;
31
+ }
32
+ const reflect = globalThis.Reflect;
33
+ if (!reflect?.getMetadata)
34
+ return undefined;
35
+ const config = reflect.getMetadata(FRONTMCP_CONFIG_METADATA_KEY, target);
36
+ return (config ?? undefined);
37
+ }
38
+ /**
39
+ * Load the entry, extract its `@FrontMcp` decorator config, and return it.
40
+ * Falls back to esbuild for TS entries.
41
+ */
42
+ async function loadEntryDecoratorConfig(entry) {
43
+ const info = await loadEntryDecoratorInfo(entry);
44
+ return info.decoratorConfig;
45
+ }
46
+ /**
47
+ * Round-2 (#375): same as `loadEntryDecoratorConfig` but also returns the
48
+ * set of top-level property names found in any `@FrontMcp({...})` argument
49
+ * expression in the entry source. This catches conditional-config patterns
50
+ * like `sqlite: process.env.X ? {...} : undefined` where the runtime config
51
+ * object's `sqlite` key may evaluate to `undefined` at decorator-construction
52
+ * time but the bundled output still ships a Node-only branch.
53
+ */
54
+ async function loadEntryDecoratorInfo(entry) {
55
+ const prev = process.env['FRONTMCP_SCHEMA_EXTRACT'];
56
+ process.env['FRONTMCP_SCHEMA_EXTRACT'] = '1';
57
+ let decoratorConfig;
58
+ try {
59
+ // Path 1: plain require() — works for compiled JS entries.
60
+ try {
61
+ const mod = require(entry);
62
+ const target = mod.default ?? mod;
63
+ decoratorConfig = readDecoratorMetadata(target);
64
+ }
65
+ catch {
66
+ // require failed (e.g., .ts entry without a hook); fall through to esbuild.
67
+ }
68
+ // Path 2: esbuild transpile + Module._compile, only if the entry is .ts/.tsx.
69
+ if (!decoratorConfig && /\.tsx?$/i.test(entry)) {
70
+ try {
71
+ decoratorConfig = await loadTsEntryViaEsbuild(entry);
72
+ }
73
+ catch {
74
+ // Anything throws (esbuild parse error, runtime error in the entry, etc.) —
75
+ // return undefined so the caller's validate() falls back to a no-op
76
+ // and the regular tsc compile produces the real error message.
77
+ }
78
+ }
79
+ }
80
+ finally {
81
+ if (prev === undefined)
82
+ delete process.env['FRONTMCP_SCHEMA_EXTRACT'];
83
+ else
84
+ process.env['FRONTMCP_SCHEMA_EXTRACT'] = prev;
85
+ }
86
+ // Source-level scan runs unconditionally (in addition to the runtime
87
+ // load) because the runtime path can't see env-gated branches.
88
+ const keysSeenInSource = scanFrontMcpDecoratorKeys(entry);
89
+ return { decoratorConfig, keysSeenInSource };
90
+ }
91
+ /**
92
+ * Read the entry source and collect property names that appear at the top
93
+ * level of any `@FrontMcp({...})` argument expression — regardless of whether
94
+ * the value is a literal, ternary, env-gated, or function call.
95
+ *
96
+ * Approach: locate `@FrontMcp(`, find the matching `)`, walk that slice
97
+ * tracking brace/paren depth, and at depth==1 inside a `{ ... }` collect
98
+ * `<key>:` identifiers. Comments and strings are stripped first so a literal
99
+ * `// sqlite: { ... }` or a `'sqlite:'` string don't false-positive. Failures
100
+ * (no decorator found, syntax we can't parse) return an empty list — never
101
+ * throws, since this is a best-effort safety net layered on top of runtime
102
+ * validation.
103
+ */
104
+ function scanFrontMcpDecoratorKeys(entry) {
105
+ let source;
106
+ try {
107
+ source = fs.readFileSync(entry, 'utf-8');
108
+ }
109
+ catch {
110
+ return [];
111
+ }
112
+ // Strip block comments, line comments, and string literals so we don't
113
+ // false-match on them.
114
+ const stripped = source
115
+ .replace(/\/\*[\s\S]*?\*\//g, '')
116
+ .replace(/\/\/[^\n]*/g, '')
117
+ .replace(/"(?:\\.|[^"\\])*"/g, '""')
118
+ .replace(/'(?:\\.|[^'\\])*'/g, "''")
119
+ .replace(/`(?:\\.|[^`\\])*`/g, '``');
120
+ const decoratorIdx = stripped.indexOf('@FrontMcp(');
121
+ if (decoratorIdx === -1)
122
+ return [];
123
+ // Find the argument body that starts at the first `{` after `@FrontMcp(`.
124
+ let i = decoratorIdx + '@FrontMcp('.length;
125
+ while (i < stripped.length && stripped[i] !== '{') {
126
+ if (stripped[i] === ')')
127
+ return []; // empty/no-object arg
128
+ i++;
129
+ }
130
+ if (stripped[i] !== '{')
131
+ return [];
132
+ // Walk the object literal, collecting top-level keys. Depth-1 = the
133
+ // outer `{ ... }`; deeper braces (nested object literals, ternary
134
+ // branches, function bodies) are skipped.
135
+ const keys = new Set();
136
+ let depth = 0;
137
+ let parenDepth = 0;
138
+ // Detect property keys: an identifier (letters/_/$) followed by `:` at depth==1.
139
+ const keyRe = /[A-Za-z_$][A-Za-z0-9_$]*/y;
140
+ while (i < stripped.length) {
141
+ const ch = stripped[i];
142
+ if (ch === '{') {
143
+ depth++;
144
+ i++;
145
+ continue;
146
+ }
147
+ if (ch === '}') {
148
+ depth--;
149
+ i++;
150
+ if (depth === 0 && parenDepth === 0)
151
+ break;
152
+ continue;
153
+ }
154
+ if (ch === '(') {
155
+ parenDepth++;
156
+ i++;
157
+ continue;
158
+ }
159
+ if (ch === ')') {
160
+ parenDepth--;
161
+ i++;
162
+ if (depth === 0 && parenDepth < 0)
163
+ break;
164
+ continue;
165
+ }
166
+ if (depth === 1 && parenDepth === 0 && /[A-Za-z_$]/.test(ch)) {
167
+ keyRe.lastIndex = i;
168
+ const m = keyRe.exec(stripped);
169
+ if (m) {
170
+ const after = m.index + m[0].length;
171
+ // Skip whitespace, then expect ':'. (`?:` for optional spacing.)
172
+ let j = after;
173
+ while (j < stripped.length && /\s/.test(stripped[j]))
174
+ j++;
175
+ if (stripped[j] === ':') {
176
+ keys.add(m[0]);
177
+ }
178
+ i = after;
179
+ continue;
180
+ }
181
+ }
182
+ i++;
183
+ }
184
+ return Array.from(keys);
185
+ }
186
+ async function loadTsEntryViaEsbuild(entryPath) {
187
+ const esbuild = require('esbuild');
188
+ const source = fs.readFileSync(entryPath, 'utf-8');
189
+ const transformed = esbuild.transformSync(source, {
190
+ loader: 'ts',
191
+ format: 'cjs',
192
+ target: 'es2022',
193
+ sourcefile: entryPath,
194
+ });
195
+ const Module = require('module');
196
+ const m = new Module(entryPath, module);
197
+ m.filename = entryPath;
198
+ // Resolve user `import { ... } from '@frontmcp/sdk'` against the user's
199
+ // node_modules, not the CLI's. Same trick as the config loader.
200
+ m.paths = Module._nodeModulePaths(path.dirname(entryPath));
201
+ m._compile(transformed.code, entryPath);
202
+ const exported = m.exports;
203
+ const target = exported.default ?? exported;
204
+ return readDecoratorMetadata(target);
205
+ }
206
+ //# sourceMappingURL=load-entry-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load-entry-config.js","sourceRoot":"","sources":["../../../../src/commands/build/load-entry-config.ts"],"names":[],"mappings":";;AA8DA,4DAGC;AAUD,wDAiCC;;AA5GD;;;;;;;;;;;;;GAaG;AACH,+CAAyB;AACzB,mDAA6B;AAE7B,MAAM,4BAA4B,GAAG,mBAAmB,CAAC;AA0BzD,SAAS,qBAAqB,CAAC,MAAe;IAC5C,IAAI,OAAO,MAAM,KAAK,UAAU,EAAE,CAAC;QACjC,IAAI,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;YACzC,mEAAmE;YACnE,4DAA4D;YAC5D,OAAO,MAAiC,CAAC;QAC3C,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,OAAO,GAAI,UAAwC,CAAC,OAAO,CAAC;IAClE,IAAI,CAAC,OAAO,EAAE,WAAW;QAAE,OAAO,SAAS,CAAC;IAC5C,MAAM,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,4BAA4B,EAAE,MAAM,CAAC,CAAC;IACzE,OAAO,CAAC,MAAM,IAAI,SAAS,CAAwC,CAAC;AACtE,CAAC;AAED;;;GAGG;AACI,KAAK,UAAU,wBAAwB,CAAC,KAAa;IAC1D,MAAM,IAAI,GAAG,MAAM,sBAAsB,CAAC,KAAK,CAAC,CAAC;IACjD,OAAO,IAAI,CAAC,eAAe,CAAC;AAC9B,CAAC;AAED;;;;;;;GAOG;AACI,KAAK,UAAU,sBAAsB,CAAC,KAAa;IACxD,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC;IACpD,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,GAAG,GAAG,CAAC;IAC7C,IAAI,eAAoD,CAAC;IACzD,IAAI,CAAC;QACH,2DAA2D;QAC3D,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAoD,CAAC;YAC9E,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,IAAI,GAAG,CAAC;YAClC,eAAe,GAAG,qBAAqB,CAAC,MAAM,CAAC,CAAC;QAClD,CAAC;QAAC,MAAM,CAAC;YACP,4EAA4E;QAC9E,CAAC;QAED,8EAA8E;QAC9E,IAAI,CAAC,eAAe,IAAI,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YAC/C,IAAI,CAAC;gBACH,eAAe,GAAG,MAAM,qBAAqB,CAAC,KAAK,CAAC,CAAC;YACvD,CAAC;YAAC,MAAM,CAAC;gBACP,4EAA4E;gBAC5E,oEAAoE;gBACpE,+DAA+D;YACjE,CAAC;QACH,CAAC;IACH,CAAC;YAAS,CAAC;QACT,IAAI,IAAI,KAAK,SAAS;YAAE,OAAO,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC;;YACjE,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,GAAG,IAAI,CAAC;IACrD,CAAC;IAED,qEAAqE;IACrE,+DAA+D;IAC/D,MAAM,gBAAgB,GAAG,yBAAyB,CAAC,KAAK,CAAC,CAAC;IAC1D,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,CAAC;AAC/C,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,SAAS,yBAAyB,CAAC,KAAa;IAC9C,IAAI,MAAc,CAAC;IACnB,IAAI,CAAC;QACH,MAAM,GAAG,EAAE,CAAC,YAAY,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IAC3C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,uEAAuE;IACvE,uBAAuB;IACvB,MAAM,QAAQ,GAAG,MAAM;SACpB,OAAO,CAAC,mBAAmB,EAAE,EAAE,CAAC;SAChC,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC;SAC1B,OAAO,CAAC,oBAAoB,EAAE,IAAI,CAAC;SACnC,OAAO,CAAC,oBAAoB,EAAE,IAAI,CAAC;SACnC,OAAO,CAAC,oBAAoB,EAAE,IAAI,CAAC,CAAC;IAEvC,MAAM,YAAY,GAAG,QAAQ,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;IACpD,IAAI,YAAY,KAAK,CAAC,CAAC;QAAE,OAAO,EAAE,CAAC;IAEnC,0EAA0E;IAC1E,IAAI,CAAC,GAAG,YAAY,GAAG,YAAY,CAAC,MAAM,CAAC;IAC3C,OAAO,CAAC,GAAG,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;QAClD,IAAI,QAAQ,CAAC,CAAC,CAAC,KAAK,GAAG;YAAE,OAAO,EAAE,CAAC,CAAC,sBAAsB;QAC1D,CAAC,EAAE,CAAC;IACN,CAAC;IACD,IAAI,QAAQ,CAAC,CAAC,CAAC,KAAK,GAAG;QAAE,OAAO,EAAE,CAAC;IAEnC,oEAAoE;IACpE,kEAAkE;IAClE,0CAA0C;IAC1C,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,iFAAiF;IACjF,MAAM,KAAK,GAAG,2BAA2B,CAAC;IAC1C,OAAO,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC;QAC3B,MAAM,EAAE,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;QACvB,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;YACf,KAAK,EAAE,CAAC;YACR,CAAC,EAAE,CAAC;YACJ,SAAS;QACX,CAAC;QACD,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;YACf,KAAK,EAAE,CAAC;YACR,CAAC,EAAE,CAAC;YACJ,IAAI,KAAK,KAAK,CAAC,IAAI,UAAU,KAAK,CAAC;gBAAE,MAAM;YAC3C,SAAS;QACX,CAAC;QACD,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;YACf,UAAU,EAAE,CAAC;YACb,CAAC,EAAE,CAAC;YACJ,SAAS;QACX,CAAC;QACD,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;YACf,UAAU,EAAE,CAAC;YACb,CAAC,EAAE,CAAC;YACJ,IAAI,KAAK,KAAK,CAAC,IAAI,UAAU,GAAG,CAAC;gBAAE,MAAM;YACzC,SAAS;QACX,CAAC;QACD,IAAI,KAAK,KAAK,CAAC,IAAI,UAAU,KAAK,CAAC,IAAI,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;YAC7D,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC;YACpB,MAAM,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAC/B,IAAI,CAAC,EAAE,CAAC;gBACN,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;gBACpC,iEAAiE;gBACjE,IAAI,CAAC,GAAG,KAAK,CAAC;gBACd,OAAO,CAAC,GAAG,QAAQ,CAAC,MAAM,IAAI,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;oBAAE,CAAC,EAAE,CAAC;gBAC1D,IAAI,QAAQ,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;oBACxB,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;gBACjB,CAAC;gBACD,CAAC,GAAG,KAAK,CAAC;gBACV,SAAS;YACX,CAAC;QACH,CAAC;QACD,CAAC,EAAE,CAAC;IACN,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,KAAK,UAAU,qBAAqB,CAAC,SAAiB;IACpD,MAAM,OAAO,GAAG,OAAO,CAAC,SAAS,CAA6B,CAAC;IAC/D,MAAM,MAAM,GAAG,EAAE,CAAC,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IACnD,MAAM,WAAW,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,EAAE;QAChD,MAAM,EAAE,IAAI;QACZ,MAAM,EAAE,KAAK;QACb,MAAM,EAAE,QAAQ;QAChB,UAAU,EAAE,SAAS;KACtB,CAAC,CAAC;IAEH,MAAM,MAAM,GAAG,OAAO,CAAC,QAAQ,CAA4B,CAAC;IAC5D,MAAM,CAAC,GAAG,IAAI,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IACxC,CAAC,CAAC,QAAQ,GAAG,SAAS,CAAC;IACvB,wEAAwE;IACxE,gEAAgE;IAChE,CAAC,CAAC,KAAK,GAAI,MAA+D,CAAC,gBAAgB,CACzF,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CACxB,CAAC;IAED,CAAS,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;IAEjD,MAAM,QAAQ,GAAI,CAAS,CAAC,OAA0D,CAAC;IACvF,MAAM,MAAM,GAAG,QAAQ,CAAC,OAAO,IAAI,QAAQ,CAAC;IAC5C,OAAO,qBAAqB,CAAC,MAAM,CAAC,CAAC;AACvC,CAAC","sourcesContent":["/**\n * Best-effort load of a user's `@FrontMcp()`-decorated entry to read its\n * decorator config at build time.\n *\n * Why it exists: `runAdapterBuild` and the adapter `validate` hook need\n * `@FrontMcp({ http, sqlite, redis })` *before* TypeScript compilation runs,\n * but the entry itself is usually `./src/main.ts` — `require()` can't load it\n * without a TS hook. Falling back to esbuild + `Module._compile` lets us\n * read the metadata regardless of whether the entry is `.ts` or `.js`.\n *\n * Returns `undefined` when the entry can't be loaded or doesn't carry a\n * decorator config (plain config object, missing decorator, etc.). Callers\n * should treat that as \"no metadata available\" and not as a build failure.\n */\nimport * as fs from 'fs';\nimport * as path from 'path';\n\nconst FRONTMCP_CONFIG_METADATA_KEY = '__frontmcp:config';\n\n/**\n * Result returned alongside the runtime-evaluated decorator config. The\n * `keysSeenInSource` field carries top-level identifiers found in the\n * `@FrontMcp({...})` arg expression even when those keys' values are\n * conditional or env-gated — a `validate` hook can use it to fail loud\n * on `sqlite: process.env.X ? {...} : undefined` patterns that the\n * runtime config object alone can't catch (#375 round-2).\n */\nexport interface EntryDecoratorInfo {\n /** Best-effort runtime config object (when the decorator was evaluated). */\n decoratorConfig: Record<string, unknown> | undefined;\n /**\n * Top-level property names that appear in `@FrontMcp({...})` argument\n * expressions in the entry source — regardless of whether they're literals,\n * ternaries, function calls, or env-gated branches. Used by adapter\n * validators to reject runtime-incompatible options before bundling.\n */\n keysSeenInSource: string[];\n}\n\ninterface ReflectLike {\n getMetadata?: (key: string, target: unknown) => unknown;\n}\n\nfunction readDecoratorMetadata(target: unknown): Record<string, unknown> | undefined {\n if (typeof target !== 'function') {\n if (target && typeof target === 'object') {\n // Plain config object — return as-is so the validate() can inspect\n // top-level fields like `sqlite`, `redis`, `http` directly.\n return target as Record<string, unknown>;\n }\n return undefined;\n }\n const reflect = (globalThis as { Reflect?: ReflectLike }).Reflect;\n if (!reflect?.getMetadata) return undefined;\n const config = reflect.getMetadata(FRONTMCP_CONFIG_METADATA_KEY, target);\n return (config ?? undefined) as Record<string, unknown> | undefined;\n}\n\n/**\n * Load the entry, extract its `@FrontMcp` decorator config, and return it.\n * Falls back to esbuild for TS entries.\n */\nexport async function loadEntryDecoratorConfig(entry: string): Promise<Record<string, unknown> | undefined> {\n const info = await loadEntryDecoratorInfo(entry);\n return info.decoratorConfig;\n}\n\n/**\n * Round-2 (#375): same as `loadEntryDecoratorConfig` but also returns the\n * set of top-level property names found in any `@FrontMcp({...})` argument\n * expression in the entry source. This catches conditional-config patterns\n * like `sqlite: process.env.X ? {...} : undefined` where the runtime config\n * object's `sqlite` key may evaluate to `undefined` at decorator-construction\n * time but the bundled output still ships a Node-only branch.\n */\nexport async function loadEntryDecoratorInfo(entry: string): Promise<EntryDecoratorInfo> {\n const prev = process.env['FRONTMCP_SCHEMA_EXTRACT'];\n process.env['FRONTMCP_SCHEMA_EXTRACT'] = '1';\n let decoratorConfig: Record<string, unknown> | undefined;\n try {\n // Path 1: plain require() — works for compiled JS entries.\n try {\n const mod = require(entry) as { default?: unknown } & Record<string, unknown>;\n const target = mod.default ?? mod;\n decoratorConfig = readDecoratorMetadata(target);\n } catch {\n // require failed (e.g., .ts entry without a hook); fall through to esbuild.\n }\n\n // Path 2: esbuild transpile + Module._compile, only if the entry is .ts/.tsx.\n if (!decoratorConfig && /\\.tsx?$/i.test(entry)) {\n try {\n decoratorConfig = await loadTsEntryViaEsbuild(entry);\n } catch {\n // Anything throws (esbuild parse error, runtime error in the entry, etc.) —\n // return undefined so the caller's validate() falls back to a no-op\n // and the regular tsc compile produces the real error message.\n }\n }\n } finally {\n if (prev === undefined) delete process.env['FRONTMCP_SCHEMA_EXTRACT'];\n else process.env['FRONTMCP_SCHEMA_EXTRACT'] = prev;\n }\n\n // Source-level scan runs unconditionally (in addition to the runtime\n // load) because the runtime path can't see env-gated branches.\n const keysSeenInSource = scanFrontMcpDecoratorKeys(entry);\n return { decoratorConfig, keysSeenInSource };\n}\n\n/**\n * Read the entry source and collect property names that appear at the top\n * level of any `@FrontMcp({...})` argument expression — regardless of whether\n * the value is a literal, ternary, env-gated, or function call.\n *\n * Approach: locate `@FrontMcp(`, find the matching `)`, walk that slice\n * tracking brace/paren depth, and at depth==1 inside a `{ ... }` collect\n * `<key>:` identifiers. Comments and strings are stripped first so a literal\n * `// sqlite: { ... }` or a `'sqlite:'` string don't false-positive. Failures\n * (no decorator found, syntax we can't parse) return an empty list — never\n * throws, since this is a best-effort safety net layered on top of runtime\n * validation.\n */\nfunction scanFrontMcpDecoratorKeys(entry: string): string[] {\n let source: string;\n try {\n source = fs.readFileSync(entry, 'utf-8');\n } catch {\n return [];\n }\n // Strip block comments, line comments, and string literals so we don't\n // false-match on them.\n const stripped = source\n .replace(/\\/\\*[\\s\\S]*?\\*\\//g, '')\n .replace(/\\/\\/[^\\n]*/g, '')\n .replace(/\"(?:\\\\.|[^\"\\\\])*\"/g, '\"\"')\n .replace(/'(?:\\\\.|[^'\\\\])*'/g, \"''\")\n .replace(/`(?:\\\\.|[^`\\\\])*`/g, '``');\n\n const decoratorIdx = stripped.indexOf('@FrontMcp(');\n if (decoratorIdx === -1) return [];\n\n // Find the argument body that starts at the first `{` after `@FrontMcp(`.\n let i = decoratorIdx + '@FrontMcp('.length;\n while (i < stripped.length && stripped[i] !== '{') {\n if (stripped[i] === ')') return []; // empty/no-object arg\n i++;\n }\n if (stripped[i] !== '{') return [];\n\n // Walk the object literal, collecting top-level keys. Depth-1 = the\n // outer `{ ... }`; deeper braces (nested object literals, ternary\n // branches, function bodies) are skipped.\n const keys = new Set<string>();\n let depth = 0;\n let parenDepth = 0;\n // Detect property keys: an identifier (letters/_/$) followed by `:` at depth==1.\n const keyRe = /[A-Za-z_$][A-Za-z0-9_$]*/y;\n while (i < stripped.length) {\n const ch = stripped[i];\n if (ch === '{') {\n depth++;\n i++;\n continue;\n }\n if (ch === '}') {\n depth--;\n i++;\n if (depth === 0 && parenDepth === 0) break;\n continue;\n }\n if (ch === '(') {\n parenDepth++;\n i++;\n continue;\n }\n if (ch === ')') {\n parenDepth--;\n i++;\n if (depth === 0 && parenDepth < 0) break;\n continue;\n }\n if (depth === 1 && parenDepth === 0 && /[A-Za-z_$]/.test(ch)) {\n keyRe.lastIndex = i;\n const m = keyRe.exec(stripped);\n if (m) {\n const after = m.index + m[0].length;\n // Skip whitespace, then expect ':'. (`?:` for optional spacing.)\n let j = after;\n while (j < stripped.length && /\\s/.test(stripped[j])) j++;\n if (stripped[j] === ':') {\n keys.add(m[0]);\n }\n i = after;\n continue;\n }\n }\n i++;\n }\n return Array.from(keys);\n}\n\nasync function loadTsEntryViaEsbuild(entryPath: string): Promise<Record<string, unknown> | undefined> {\n const esbuild = require('esbuild') as typeof import('esbuild');\n const source = fs.readFileSync(entryPath, 'utf-8');\n const transformed = esbuild.transformSync(source, {\n loader: 'ts',\n format: 'cjs',\n target: 'es2022',\n sourcefile: entryPath,\n });\n\n const Module = require('module') as typeof import('module');\n const m = new Module(entryPath, module);\n m.filename = entryPath;\n // Resolve user `import { ... } from '@frontmcp/sdk'` against the user's\n // node_modules, not the CLI's. Same trick as the config loader.\n m.paths = (Module as unknown as { _nodeModulePaths(p: string): string[] })._nodeModulePaths(\n path.dirname(entryPath),\n );\n \n (m as any)._compile(transformed.code, entryPath);\n \n const exported = (m as any).exports as { default?: unknown } & Record<string, unknown>;\n const target = exported.default ?? exported;\n return readDecoratorMetadata(target);\n}\n"]}
@@ -45,6 +45,13 @@ export interface McpbManifest {
45
45
  description: string;
46
46
  }>;
47
47
  tools_generated?: boolean;
48
+ resources?: Array<{
49
+ name?: string;
50
+ uri: string;
51
+ description?: string;
52
+ mimeType?: string;
53
+ }>;
54
+ resources_generated?: boolean;
48
55
  prompts?: Array<{
49
56
  name: string;
50
57
  description?: string;
@@ -103,6 +110,13 @@ export declare const mcpbManifestSchema: import("@frontmcp/lazy-zod").ZodObject<
103
110
  description: import("@frontmcp/lazy-zod").ZodString;
104
111
  }, import("zod/v4/core").$strict>>>;
105
112
  tools_generated: import("@frontmcp/lazy-zod").ZodOptional<import("@frontmcp/lazy-zod").ZodBoolean>;
113
+ resources: import("@frontmcp/lazy-zod").ZodOptional<import("@frontmcp/lazy-zod").ZodArray<import("@frontmcp/lazy-zod").ZodObject<{
114
+ name: import("@frontmcp/lazy-zod").ZodOptional<import("@frontmcp/lazy-zod").ZodString>;
115
+ uri: import("@frontmcp/lazy-zod").ZodString;
116
+ description: import("@frontmcp/lazy-zod").ZodOptional<import("@frontmcp/lazy-zod").ZodString>;
117
+ mimeType: import("@frontmcp/lazy-zod").ZodOptional<import("@frontmcp/lazy-zod").ZodString>;
118
+ }, import("zod/v4/core").$strict>>>;
119
+ resources_generated: import("@frontmcp/lazy-zod").ZodOptional<import("@frontmcp/lazy-zod").ZodBoolean>;
106
120
  prompts: import("@frontmcp/lazy-zod").ZodOptional<import("@frontmcp/lazy-zod").ZodArray<import("@frontmcp/lazy-zod").ZodObject<{
107
121
  name: import("@frontmcp/lazy-zod").ZodString;
108
122
  description: import("@frontmcp/lazy-zod").ZodOptional<import("@frontmcp/lazy-zod").ZodString>;