frontmcp 1.1.1 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,4 @@
1
+ import type { DeploymentTarget } from '../../config/frontmcp-config.types';
1
2
  /**
2
3
  * Configuration for a deployment adapter.
3
4
  * Each adapter defines how to compile and package the FrontMCP server
@@ -31,9 +32,14 @@ export type AdapterTemplate = {
31
32
  /**
32
33
  * Generate the deployment platform config file content.
33
34
  * @param cwd - Current working directory (for detecting package manager, etc.)
35
+ * @param deployment - Resolved `frontmcp.config.deployments[]` entry for this
36
+ * target, when one was found. Adapters that opt in (e.g., cloudflare)
37
+ * merge platform-specific fields (`wrangler.name`, `compatibilityDate`)
38
+ * into their generated config so values declared in `frontmcp.config.js`
39
+ * actually reach the platform — see #374.
34
40
  * @returns Object (for JSON) or string (for TOML/YAML)
35
41
  */
36
- getConfig?: (cwd: string) => object | string;
42
+ getConfig?: (cwd: string, deployment?: DeploymentTarget) => object | string;
37
43
  /** Name of the config file (e.g., 'vercel.json', 'wrangler.toml') */
38
44
  configFileName?: string;
39
45
  /**
@@ -52,10 +58,20 @@ export type AdapterTemplate = {
52
58
  *
53
59
  * @param decoratorConfig - Best-effort `__frontmcp:config` metadata
54
60
  * extracted from the entry's @FrontMcp() decorator. May be undefined
55
- * when the entry exports a plain config object.
61
+ * when the entry exports a plain config object or the decorator's
62
+ * value evaluates to undefined at module-load time (e.g., env-gated
63
+ * ternaries that resolve to undefined when the env isn't set).
64
+ * @param info - Round-2 (#375): structural metadata about the entry source.
65
+ * `info.keysSeenInSource` lists the top-level property names that appear
66
+ * in `@FrontMcp({...})` arg expressions even when their values are
67
+ * conditional. Adapters can use this to reject incompatible options
68
+ * (e.g., `sqlite: process.env.X ? {...} : undefined`) that the runtime
69
+ * config alone can't catch.
56
70
  * @throws to abort the build with a user-facing message.
57
71
  */
58
- validate?: (decoratorConfig: Record<string, unknown> | undefined) => void;
72
+ validate?: (decoratorConfig: Record<string, unknown> | undefined, info?: {
73
+ keysSeenInSource: string[];
74
+ }) => void;
59
75
  /**
60
76
  * Whether `getConfig()` output should overwrite an existing config file
61
77
  * (e.g., wrangler.toml) on every build. When false, an existing file is
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/commands/build/types.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * Configuration for a deployment adapter.\n * Each adapter defines how to compile and package the FrontMCP server\n * for a specific deployment target.\n */\nexport type AdapterTemplate = {\n /** Module format for TypeScript compilation */\n moduleFormat: 'commonjs' | 'esnext';\n\n /**\n * Generate the entry point file content.\n * @param mainModulePath - Relative path to the compiled main module (e.g., './main.js')\n * @returns The content for index.js, or empty string if no wrapper needed\n */\n getEntryTemplate: (mainModulePath: string) => string;\n\n /**\n * Generate the serverless setup file content.\n * This file is imported first to set environment variables before decorators run.\n * @returns The content for serverless-setup.js, or undefined if not needed\n */\n getSetupTemplate?: () => string;\n\n /**\n * Whether to bundle the output with rspack.\n * Recommended for serverless deployments to avoid ESM/CJS issues.\n */\n shouldBundle?: boolean;\n\n /**\n * Output filename for the bundled file (e.g., 'handler.cjs').\n * Only used when shouldBundle is true.\n */\n bundleOutput?: string;\n\n /**\n * Generate the deployment platform config file content.\n * @param cwd - Current working directory (for detecting package manager, etc.)\n * @returns Object (for JSON) or string (for TOML/YAML)\n */\n getConfig?: (cwd: string) => object | string;\n\n /** Name of the config file (e.g., 'vercel.json', 'wrangler.toml') */\n configFileName?: string;\n\n /**\n * Post-bundle hook for creating deployment-specific output structure.\n * Called after bundling is complete.\n * @param outDir - The output directory (e.g., 'dist')\n * @param cwd - Current working directory\n * @param bundleOutput - Name of the bundled file (e.g., 'handler.cjs')\n */\n postBundle?: (outDir: string, cwd: string, bundleOutput: string) => Promise<void>;\n\n /**\n * Pre-build validation hook. Runs after schema/decorator extraction but\n * before TypeScript compilation. Allows the adapter to fail loudly when\n * the user's config references runtime features that won't work on the\n * target platform (e.g., sqlite on Cloudflare Workers).\n *\n * @param decoratorConfig - Best-effort `__frontmcp:config` metadata\n * extracted from the entry's @FrontMcp() decorator. May be undefined\n * when the entry exports a plain config object.\n * @throws to abort the build with a user-facing message.\n */\n validate?: (decoratorConfig: Record<string, unknown> | undefined) => void;\n\n /**\n * Whether `getConfig()` output should overwrite an existing config file\n * (e.g., wrangler.toml) on every build. When false, an existing file is\n * left untouched but its contents are diffed against the build output and\n * the build fails on mismatch (#374). Default: false (preserve existing).\n */\n alwaysWriteConfig?: boolean;\n};\n\nexport type AdapterName = 'node' | 'vercel' | 'lambda' | 'cloudflare' | 'distributed';\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/commands/build/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { DeploymentTarget } from '../../config/frontmcp-config.types';\n\n/**\n * Configuration for a deployment adapter.\n * Each adapter defines how to compile and package the FrontMCP server\n * for a specific deployment target.\n */\nexport type AdapterTemplate = {\n /** Module format for TypeScript compilation */\n moduleFormat: 'commonjs' | 'esnext';\n\n /**\n * Generate the entry point file content.\n * @param mainModulePath - Relative path to the compiled main module (e.g., './main.js')\n * @returns The content for index.js, or empty string if no wrapper needed\n */\n getEntryTemplate: (mainModulePath: string) => string;\n\n /**\n * Generate the serverless setup file content.\n * This file is imported first to set environment variables before decorators run.\n * @returns The content for serverless-setup.js, or undefined if not needed\n */\n getSetupTemplate?: () => string;\n\n /**\n * Whether to bundle the output with rspack.\n * Recommended for serverless deployments to avoid ESM/CJS issues.\n */\n shouldBundle?: boolean;\n\n /**\n * Output filename for the bundled file (e.g., 'handler.cjs').\n * Only used when shouldBundle is true.\n */\n bundleOutput?: string;\n\n /**\n * Generate the deployment platform config file content.\n * @param cwd - Current working directory (for detecting package manager, etc.)\n * @param deployment - Resolved `frontmcp.config.deployments[]` entry for this\n * target, when one was found. Adapters that opt in (e.g., cloudflare)\n * merge platform-specific fields (`wrangler.name`, `compatibilityDate`)\n * into their generated config so values declared in `frontmcp.config.js`\n * actually reach the platform — see #374.\n * @returns Object (for JSON) or string (for TOML/YAML)\n */\n getConfig?: (cwd: string, deployment?: DeploymentTarget) => object | string;\n\n /** Name of the config file (e.g., 'vercel.json', 'wrangler.toml') */\n configFileName?: string;\n\n /**\n * Post-bundle hook for creating deployment-specific output structure.\n * Called after bundling is complete.\n * @param outDir - The output directory (e.g., 'dist')\n * @param cwd - Current working directory\n * @param bundleOutput - Name of the bundled file (e.g., 'handler.cjs')\n */\n postBundle?: (outDir: string, cwd: string, bundleOutput: string) => Promise<void>;\n\n /**\n * Pre-build validation hook. Runs after schema/decorator extraction but\n * before TypeScript compilation. Allows the adapter to fail loudly when\n * the user's config references runtime features that won't work on the\n * target platform (e.g., sqlite on Cloudflare Workers).\n *\n * @param decoratorConfig - Best-effort `__frontmcp:config` metadata\n * extracted from the entry's @FrontMcp() decorator. May be undefined\n * when the entry exports a plain config object or the decorator's\n * value evaluates to undefined at module-load time (e.g., env-gated\n * ternaries that resolve to undefined when the env isn't set).\n * @param info - Round-2 (#375): structural metadata about the entry source.\n * `info.keysSeenInSource` lists the top-level property names that appear\n * in `@FrontMcp({...})` arg expressions even when their values are\n * conditional. Adapters can use this to reject incompatible options\n * (e.g., `sqlite: process.env.X ? {...} : undefined`) that the runtime\n * config alone can't catch.\n * @throws to abort the build with a user-facing message.\n */\n validate?: (\n decoratorConfig: Record<string, unknown> | undefined,\n info?: { keysSeenInSource: string[] },\n ) => void;\n\n /**\n * Whether `getConfig()` output should overwrite an existing config file\n * (e.g., wrangler.toml) on every build. When false, an existing file is\n * left untouched but its contents are diffed against the build output and\n * the build fails on mismatch (#374). Default: false (preserve existing).\n */\n alwaysWriteConfig?: boolean;\n};\n\nexport type AdapterName = 'node' | 'vercel' | 'lambda' | 'cloudflare' | 'distributed';\n"]}
@@ -102,35 +102,57 @@ async function loadRawConfig(cwd) {
102
102
  return JSON.parse(content);
103
103
  }
104
104
  if (filename.endsWith('.ts')) {
105
- // #365 — When the project is `"type": "commonjs"` (the default),
106
- // `require()` can't load .ts (no transpiler) and `await import()`
107
- // fails with "Make sure to set 'type': 'module'". The previous loader
108
- // silently fell through to defaults. Now we transpile the .ts file
109
- // with esbuild (already a dependency) and eval the resulting CJS.
110
- // Any failure throws no silent fallback.
111
- try {
112
- const mod = require(configPath);
113
- return mod.default ?? mod;
114
- }
115
- catch (requireErr) {
116
- try {
117
- const mod = await import(configPath);
118
- return mod.default ?? mod;
119
- }
120
- catch {
121
- // Both runtime loaders rejected — fall through to esbuild transpile.
122
- }
105
+ // #365 — Loading `.ts` under `"type": "commonjs"` (the default) is a
106
+ // minefield across Node versions:
107
+ // - Node 20: `require()` throws on TS syntax, `await import()` errors
108
+ // with "Make sure to set type: module".
109
+ // - Node 22+: `require(esm)` may succeed but return partial data, OR
110
+ // emit a warning on `await import()` even when the load succeeds.
111
+ // - Node 24: type-stripping may swallow `import { x } from ...`
112
+ // statements, returning `{}` instead of the user's exports — the
113
+ // 1.1.2-beta.1 silent-defaults regression.
114
+ // Round 3: under CJS, ALWAYS transpile via esbuild. It's the only path
115
+ // that produces a deterministic, fully-typed result. ESM projects can
116
+ // still use Node's runtime loaders since they're well-behaved there.
117
+ const isCjsProject = await isCommonJsProject(cwd);
118
+ if (isCjsProject) {
123
119
  try {
124
120
  return await loadTsConfigViaEsbuild(configPath);
125
121
  }
126
122
  catch (esbuildErr) {
127
- throw new Error(`Failed to load ${filename}.\n` +
128
- ` require() error: ${requireErr.message}\n` +
129
- ` esbuild error: ${esbuildErr.message}\n` +
123
+ throw new Error(`Failed to load ${filename} via esbuild.\n` +
124
+ ` ${esbuildErr.message}\n` +
130
125
  `Hint: ensure the file exports a default config (e.g., ` +
131
126
  `\`export default defineConfig({...})\`) and that all imports resolve.`);
132
127
  }
133
128
  }
129
+ // ESM project ("type": "module"): try Node's loaders first (faster,
130
+ // no transpile cost), fall back to esbuild on failure.
131
+ let requireErr;
132
+ try {
133
+ const mod = require(configPath);
134
+ return mod.default ?? mod;
135
+ }
136
+ catch (e) {
137
+ requireErr = e;
138
+ }
139
+ try {
140
+ const mod = await import(configPath);
141
+ return mod.default ?? mod;
142
+ }
143
+ catch {
144
+ // Fall through to esbuild.
145
+ }
146
+ try {
147
+ return await loadTsConfigViaEsbuild(configPath);
148
+ }
149
+ catch (esbuildErr) {
150
+ throw new Error(`Failed to load ${filename}.\n` +
151
+ ` require() error: ${requireErr?.message ?? '(skipped)'}\n` +
152
+ ` esbuild error: ${esbuildErr.message}\n` +
153
+ `Hint: ensure the file exports a default config (e.g., ` +
154
+ `\`export default defineConfig({...})\`) and that all imports resolve.`);
155
+ }
134
156
  }
135
157
  // JS/MJS/CJS
136
158
  if (filename.endsWith('.mjs')) {
@@ -143,6 +165,29 @@ async function loadRawConfig(cwd) {
143
165
  // Fallback: derive from package.json
144
166
  return deriveFromPackageJson(cwd);
145
167
  }
168
+ /**
169
+ * Read the host project's `package.json.type` to decide whether `await import()`
170
+ * of a `.ts` file is worth attempting. Returns true when the project is
171
+ * declared `"type": "commonjs"` or omits the field entirely (Node's default).
172
+ *
173
+ * Read errors (no package.json, malformed JSON) are treated as "CJS" — that's
174
+ * the safer default for the loader because it routes us through esbuild
175
+ * transpilation rather than relying on Node's experimental TS handling.
176
+ *
177
+ * Routed through `@frontmcp/utils` per repo convention so this module
178
+ * doesn't reach into `node:fs` for an ad-hoc package.json read.
179
+ */
180
+ async function isCommonJsProject(cwd) {
181
+ try {
182
+ const pkgPath = path.join(cwd, 'package.json');
183
+ const contents = await (0, utils_1.readFile)(pkgPath);
184
+ const pkg = JSON.parse(contents);
185
+ return pkg.type !== 'module';
186
+ }
187
+ catch {
188
+ return true;
189
+ }
190
+ }
146
191
  /**
147
192
  * Transpile a TypeScript config file with esbuild (CJS target) and eval the
148
193
  * result via Module-via-vm. Used as a last-resort path when neither `require()`
@@ -186,10 +231,6 @@ async function loadTsConfigViaEsbuild(configPath) {
186
231
  const exported = m.exports;
187
232
  return exported?.default ?? exported;
188
233
  }
189
- // Surface the @frontmcp/utils import even though esbuild reads the entry
190
- // itself — keeps the file's filesystem boundary going through @frontmcp/utils
191
- // for any future expansion.
192
- void utils_1.readFile;
193
234
  /**
194
235
  * Derive minimal config from package.json.
195
236
  */
@@ -1 +1 @@
1
- {"version":3,"file":"frontmcp-config.loader.js","sourceRoot":"","sources":["../../../src/config/frontmcp-config.loader.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * FrontMCP Config Loader\n *\n * Loads `frontmcp.config.(json|js|ts|mjs|cjs)` from a directory.\n * Falls back to deriving minimal config from package.json.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\n\nimport { readFile } from '@frontmcp/utils';\n\nimport { frontmcpConfigSchema, type FrontMcpConfigParsed } from './frontmcp-config.schema';\nimport type { DeploymentTarget, FrontMcpConfig } from './frontmcp-config.types';\n\nconst CONFIG_FILENAMES = [\n 'frontmcp.config.ts',\n 'frontmcp.config.js',\n 'frontmcp.config.json',\n 'frontmcp.config.mjs',\n 'frontmcp.config.cjs',\n];\n\n/**\n * Load and validate a frontmcp.config file from the given directory.\n *\n * Resolution order:\n * 1. frontmcp.config.ts\n * 2. frontmcp.config.js\n * 3. frontmcp.config.json\n * 4. frontmcp.config.mjs\n * 5. frontmcp.config.cjs\n * 6. Derive from package.json (minimal config with 'node' target)\n */\nexport async function loadFrontMcpConfig(cwd: string): Promise<FrontMcpConfigParsed> {\n const raw = await loadRawConfig(cwd);\n return validateConfig(raw);\n}\n\n/**\n * Variant that load-errors propagate (parse failures in `frontmcp.config.ts`,\n * missing dependencies, etc.) but schema-validation errors return `undefined`.\n *\n * Used by `runBuild` to support both shapes: the new top-level\n * `frontmcpConfigSchema` (with `deployments`) and the older exec-only shape\n * (top-level `cli`, `sea`, `esbuild`) that `loadExecConfig` consumes\n * directly. A user with the old shape should still get a successful build\n * — the exec-loader picks the file up from disk by itself.\n *\n * Returns `undefined` when:\n * - no config file is present (caller falls back to CLI flags), or\n * - the file loads but doesn't match the new schema (legacy shape).\n *\n * Throws when:\n * - the file exists but can't be parsed (TS syntax error, ESM/CJS mismatch\n * that even esbuild can't recover from, etc.) — i.e., #365 silent-default\n * regressions.\n */\nexport async function tryLoadFrontMcpConfig(cwd: string): Promise<FrontMcpConfigParsed | undefined> {\n let raw: unknown;\n try {\n raw = await loadRawConfig(cwd);\n } catch (err) {\n // Distinguish \"no config and no package.json\" from real load failures.\n // `deriveFromPackageJson` throws this exact message — treat it as \"no config\".\n if ((err as Error).message?.startsWith('No frontmcp.config found')) {\n return undefined;\n }\n throw err;\n }\n const result = frontmcpConfigSchema.safeParse(raw);\n if (!result.success) {\n // Distinguish two failure shapes:\n // 1. Legacy exec-only config — top-level `cli` / `sea` / `esbuild`,\n // no `deployments` key. Return undefined so `loadExecConfig` picks\n // it up directly. Pre-v1.1 fixtures live in this branch.\n // 2. Anything else — looks like the user attempted a v1.1 config and\n // got it wrong (typo'd `deployments`, invalid `target`, etc.).\n // Throw so we don't silently fall back to defaults — that was the\n // silent-corruption mode #365 was trying to eliminate.\n const obj = typeof raw === 'object' && raw !== null ? (raw as Record<string, unknown>) : undefined;\n const isLegacyExecOnlyShape =\n !!obj && !('deployments' in obj) && ('cli' in obj || 'sea' in obj || 'esbuild' in obj);\n if (isLegacyExecOnlyShape) return undefined;\n const issues = result.error.issues.map((i) => ` - ${i.path.join('.')}: ${i.message}`).join('\\n');\n throw new Error(`Invalid frontmcp.config:\\n${issues}`);\n }\n return result.data;\n}\n\n/**\n * Load raw config without validation.\n */\nasync function loadRawConfig(cwd: string): Promise<unknown> {\n for (const filename of CONFIG_FILENAMES) {\n const configPath = path.join(cwd, filename);\n if (!fs.existsSync(configPath)) continue;\n\n if (filename.endsWith('.json')) {\n const content = fs.readFileSync(configPath, 'utf-8');\n return JSON.parse(content);\n }\n\n if (filename.endsWith('.ts')) {\n // #365 — When the project is `\"type\": \"commonjs\"` (the default),\n // `require()` can't load .ts (no transpiler) and `await import()`\n // fails with \"Make sure to set 'type': 'module'\". The previous loader\n // silently fell through to defaults. Now we transpile the .ts file\n // with esbuild (already a dependency) and eval the resulting CJS.\n // Any failure throws — no silent fallback.\n try {\n const mod = require(configPath);\n return mod.default ?? mod;\n } catch (requireErr) {\n try {\n const mod = await import(configPath);\n return mod.default ?? mod;\n } catch {\n // Both runtime loaders rejected — fall through to esbuild transpile.\n }\n try {\n return await loadTsConfigViaEsbuild(configPath);\n } catch (esbuildErr) {\n throw new Error(\n `Failed to load ${filename}.\\n` +\n ` require() error: ${(requireErr as Error).message}\\n` +\n ` esbuild error: ${(esbuildErr as Error).message}\\n` +\n `Hint: ensure the file exports a default config (e.g., ` +\n `\\`export default defineConfig({...})\\`) and that all imports resolve.`,\n );\n }\n }\n }\n\n // JS/MJS/CJS\n if (filename.endsWith('.mjs')) {\n const mod = await import(configPath);\n return mod.default ?? mod;\n }\n\n const mod = require(configPath);\n return mod.default ?? mod;\n }\n\n // Fallback: derive from package.json\n return deriveFromPackageJson(cwd);\n}\n\n/**\n * Transpile a TypeScript config file with esbuild (CJS target) and eval the\n * result via Module-via-vm. Used as a last-resort path when neither `require()`\n * (no ts-node hook) nor `await import()` (project is `\"type\": \"commonjs\"`)\n * can load the file directly.\n *\n * Uses `esbuild.build({ bundle: true, packages: 'external' })` rather than\n * `transformSync` so a config that imports a sibling helper TS file\n * (`import { foo } from './helpers'`) gets the helper inlined into the\n * compiled output. Without bundling, the resulting CJS would emit\n * `require('./helpers')` which Node can't resolve under `\"type\": \"commonjs\"`.\n *\n * `packages: 'external'` keeps node_modules dependencies as runtime\n * `require()` calls so `import { defineConfig } from 'frontmcp'` still\n * resolves against the project's installed copy of the SDK.\n */\nasync function loadTsConfigViaEsbuild(configPath: string): Promise<unknown> {\n const esbuild = require('esbuild') as typeof import('esbuild');\n const built = await esbuild.build({\n entryPoints: [configPath],\n bundle: true,\n write: false,\n platform: 'node',\n format: 'cjs',\n target: 'es2022',\n packages: 'external',\n sourcemap: 'inline',\n logLevel: 'silent',\n });\n if (!built.outputFiles || built.outputFiles.length === 0) {\n throw new Error('esbuild produced no output for ' + configPath);\n }\n const code = built.outputFiles[0].text;\n\n const Module = require('module') as typeof import('module');\n const m = new Module(configPath, module);\n // Make the loaded module's `require` resolve relative to the config dir\n // so user `import { defineConfig } from 'frontmcp'` keeps working.\n m.filename = configPath;\n m.paths = (Module as unknown as { _nodeModulePaths(p: string): string[] })._nodeModulePaths(path.dirname(configPath));\n\n (m as any)._compile(code, configPath);\n\n const exported = (m as any).exports as { default?: unknown };\n return exported?.default ?? exported;\n}\n\n// Surface the @frontmcp/utils import even though esbuild reads the entry\n// itself — keeps the file's filesystem boundary going through @frontmcp/utils\n// for any future expansion.\nvoid readFile;\n\n/**\n * Derive minimal config from package.json.\n */\nfunction deriveFromPackageJson(cwd: string): FrontMcpConfig {\n const pkgPath = path.join(cwd, 'package.json');\n if (!fs.existsSync(pkgPath)) {\n throw new Error(\n 'No frontmcp.config found and no package.json. Create a frontmcp.config.ts to configure build targets.',\n );\n }\n\n const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));\n return {\n name: pkg.name?.replace(/^@[^/]+\\//, '') || path.basename(cwd),\n version: pkg.version || '1.0.0',\n entry: pkg.main,\n deployments: [{ target: 'node' }],\n };\n}\n\n/**\n * Validate raw config against the Zod schema.\n */\nexport function validateConfig(raw: unknown): FrontMcpConfigParsed {\n const result = frontmcpConfigSchema.safeParse(raw);\n if (!result.success) {\n const issues = result.error.issues.map((i) => ` - ${i.path.join('.')}: ${i.message}`).join('\\n');\n throw new Error(`Invalid frontmcp.config:\\n${issues}`);\n }\n return result.data;\n}\n\n/**\n * Find a deployment target by type.\n */\nexport function findDeployment(config: FrontMcpConfigParsed, target: string): DeploymentTarget | undefined {\n return config.deployments.find((d) => d.target === target);\n}\n\n/**\n * Get all deployment target types from the config.\n */\nexport function getDeploymentTargets(config: FrontMcpConfigParsed): string[] {\n return config.deployments.map((d) => d.target);\n}\n"]}
1
+ {"version":3,"file":"frontmcp-config.loader.js","sourceRoot":"","sources":["../../../src/config/frontmcp-config.loader.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * FrontMCP Config Loader\n *\n * Loads `frontmcp.config.(json|js|ts|mjs|cjs)` from a directory.\n * Falls back to deriving minimal config from package.json.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\n\nimport { readFile } from '@frontmcp/utils';\n\nimport { frontmcpConfigSchema, type FrontMcpConfigParsed } from './frontmcp-config.schema';\nimport type { DeploymentTarget, FrontMcpConfig } from './frontmcp-config.types';\n\nconst CONFIG_FILENAMES = [\n 'frontmcp.config.ts',\n 'frontmcp.config.js',\n 'frontmcp.config.json',\n 'frontmcp.config.mjs',\n 'frontmcp.config.cjs',\n];\n\n/**\n * Load and validate a frontmcp.config file from the given directory.\n *\n * Resolution order:\n * 1. frontmcp.config.ts\n * 2. frontmcp.config.js\n * 3. frontmcp.config.json\n * 4. frontmcp.config.mjs\n * 5. frontmcp.config.cjs\n * 6. Derive from package.json (minimal config with 'node' target)\n */\nexport async function loadFrontMcpConfig(cwd: string): Promise<FrontMcpConfigParsed> {\n const raw = await loadRawConfig(cwd);\n return validateConfig(raw);\n}\n\n/**\n * Variant that load-errors propagate (parse failures in `frontmcp.config.ts`,\n * missing dependencies, etc.) but schema-validation errors return `undefined`.\n *\n * Used by `runBuild` to support both shapes: the new top-level\n * `frontmcpConfigSchema` (with `deployments`) and the older exec-only shape\n * (top-level `cli`, `sea`, `esbuild`) that `loadExecConfig` consumes\n * directly. A user with the old shape should still get a successful build\n * — the exec-loader picks the file up from disk by itself.\n *\n * Returns `undefined` when:\n * - no config file is present (caller falls back to CLI flags), or\n * - the file loads but doesn't match the new schema (legacy shape).\n *\n * Throws when:\n * - the file exists but can't be parsed (TS syntax error, ESM/CJS mismatch\n * that even esbuild can't recover from, etc.) — i.e., #365 silent-default\n * regressions.\n */\nexport async function tryLoadFrontMcpConfig(cwd: string): Promise<FrontMcpConfigParsed | undefined> {\n let raw: unknown;\n try {\n raw = await loadRawConfig(cwd);\n } catch (err) {\n // Distinguish \"no config and no package.json\" from real load failures.\n // `deriveFromPackageJson` throws this exact message — treat it as \"no config\".\n if ((err as Error).message?.startsWith('No frontmcp.config found')) {\n return undefined;\n }\n throw err;\n }\n const result = frontmcpConfigSchema.safeParse(raw);\n if (!result.success) {\n // Distinguish two failure shapes:\n // 1. Legacy exec-only config — top-level `cli` / `sea` / `esbuild`,\n // no `deployments` key. Return undefined so `loadExecConfig` picks\n // it up directly. Pre-v1.1 fixtures live in this branch.\n // 2. Anything else — looks like the user attempted a v1.1 config and\n // got it wrong (typo'd `deployments`, invalid `target`, etc.).\n // Throw so we don't silently fall back to defaults — that was the\n // silent-corruption mode #365 was trying to eliminate.\n const obj = typeof raw === 'object' && raw !== null ? (raw as Record<string, unknown>) : undefined;\n const isLegacyExecOnlyShape =\n !!obj && !('deployments' in obj) && ('cli' in obj || 'sea' in obj || 'esbuild' in obj);\n if (isLegacyExecOnlyShape) return undefined;\n const issues = result.error.issues.map((i) => ` - ${i.path.join('.')}: ${i.message}`).join('\\n');\n throw new Error(`Invalid frontmcp.config:\\n${issues}`);\n }\n return result.data;\n}\n\n/**\n * Load raw config without validation.\n */\nasync function loadRawConfig(cwd: string): Promise<unknown> {\n for (const filename of CONFIG_FILENAMES) {\n const configPath = path.join(cwd, filename);\n if (!fs.existsSync(configPath)) continue;\n\n if (filename.endsWith('.json')) {\n const content = fs.readFileSync(configPath, 'utf-8');\n return JSON.parse(content);\n }\n\n if (filename.endsWith('.ts')) {\n // #365 — Loading `.ts` under `\"type\": \"commonjs\"` (the default) is a\n // minefield across Node versions:\n // - Node 20: `require()` throws on TS syntax, `await import()` errors\n // with \"Make sure to set type: module\".\n // - Node 22+: `require(esm)` may succeed but return partial data, OR\n // emit a warning on `await import()` even when the load succeeds.\n // - Node 24: type-stripping may swallow `import { x } from ...`\n // statements, returning `{}` instead of the user's exports — the\n // 1.1.2-beta.1 silent-defaults regression.\n // Round 3: under CJS, ALWAYS transpile via esbuild. It's the only path\n // that produces a deterministic, fully-typed result. ESM projects can\n // still use Node's runtime loaders since they're well-behaved there.\n const isCjsProject = await isCommonJsProject(cwd);\n if (isCjsProject) {\n try {\n return await loadTsConfigViaEsbuild(configPath);\n } catch (esbuildErr) {\n throw new Error(\n `Failed to load ${filename} via esbuild.\\n` +\n ` ${(esbuildErr as Error).message}\\n` +\n `Hint: ensure the file exports a default config (e.g., ` +\n `\\`export default defineConfig({...})\\`) and that all imports resolve.`,\n );\n }\n }\n // ESM project (\"type\": \"module\"): try Node's loaders first (faster,\n // no transpile cost), fall back to esbuild on failure.\n let requireErr: Error | undefined;\n try {\n const mod = require(configPath);\n return mod.default ?? mod;\n } catch (e) {\n requireErr = e as Error;\n }\n try {\n const mod = await import(configPath);\n return mod.default ?? mod;\n } catch {\n // Fall through to esbuild.\n }\n try {\n return await loadTsConfigViaEsbuild(configPath);\n } catch (esbuildErr) {\n throw new Error(\n `Failed to load ${filename}.\\n` +\n ` require() error: ${requireErr?.message ?? '(skipped)'}\\n` +\n ` esbuild error: ${(esbuildErr as Error).message}\\n` +\n `Hint: ensure the file exports a default config (e.g., ` +\n `\\`export default defineConfig({...})\\`) and that all imports resolve.`,\n );\n }\n }\n\n // JS/MJS/CJS\n if (filename.endsWith('.mjs')) {\n const mod = await import(configPath);\n return mod.default ?? mod;\n }\n\n const mod = require(configPath);\n return mod.default ?? mod;\n }\n\n // Fallback: derive from package.json\n return deriveFromPackageJson(cwd);\n}\n\n/**\n * Read the host project's `package.json.type` to decide whether `await import()`\n * of a `.ts` file is worth attempting. Returns true when the project is\n * declared `\"type\": \"commonjs\"` or omits the field entirely (Node's default).\n *\n * Read errors (no package.json, malformed JSON) are treated as \"CJS\" — that's\n * the safer default for the loader because it routes us through esbuild\n * transpilation rather than relying on Node's experimental TS handling.\n *\n * Routed through `@frontmcp/utils` per repo convention so this module\n * doesn't reach into `node:fs` for an ad-hoc package.json read.\n */\nasync function isCommonJsProject(cwd: string): Promise<boolean> {\n try {\n const pkgPath = path.join(cwd, 'package.json');\n const contents = await readFile(pkgPath);\n const pkg = JSON.parse(contents) as { type?: string };\n return pkg.type !== 'module';\n } catch {\n return true;\n }\n}\n\n/**\n * Transpile a TypeScript config file with esbuild (CJS target) and eval the\n * result via Module-via-vm. Used as a last-resort path when neither `require()`\n * (no ts-node hook) nor `await import()` (project is `\"type\": \"commonjs\"`)\n * can load the file directly.\n *\n * Uses `esbuild.build({ bundle: true, packages: 'external' })` rather than\n * `transformSync` so a config that imports a sibling helper TS file\n * (`import { foo } from './helpers'`) gets the helper inlined into the\n * compiled output. Without bundling, the resulting CJS would emit\n * `require('./helpers')` which Node can't resolve under `\"type\": \"commonjs\"`.\n *\n * `packages: 'external'` keeps node_modules dependencies as runtime\n * `require()` calls so `import { defineConfig } from 'frontmcp'` still\n * resolves against the project's installed copy of the SDK.\n */\nasync function loadTsConfigViaEsbuild(configPath: string): Promise<unknown> {\n const esbuild = require('esbuild') as typeof import('esbuild');\n const built = await esbuild.build({\n entryPoints: [configPath],\n bundle: true,\n write: false,\n platform: 'node',\n format: 'cjs',\n target: 'es2022',\n packages: 'external',\n sourcemap: 'inline',\n logLevel: 'silent',\n });\n if (!built.outputFiles || built.outputFiles.length === 0) {\n throw new Error('esbuild produced no output for ' + configPath);\n }\n const code = built.outputFiles[0].text;\n\n const Module = require('module') as typeof import('module');\n const m = new Module(configPath, module);\n // Make the loaded module's `require` resolve relative to the config dir\n // so user `import { defineConfig } from 'frontmcp'` keeps working.\n m.filename = configPath;\n m.paths = (Module as unknown as { _nodeModulePaths(p: string): string[] })._nodeModulePaths(path.dirname(configPath));\n\n (m as any)._compile(code, configPath);\n\n const exported = (m as any).exports as { default?: unknown };\n return exported?.default ?? exported;\n}\n\n/**\n * Derive minimal config from package.json.\n */\nfunction deriveFromPackageJson(cwd: string): FrontMcpConfig {\n const pkgPath = path.join(cwd, 'package.json');\n if (!fs.existsSync(pkgPath)) {\n throw new Error(\n 'No frontmcp.config found and no package.json. Create a frontmcp.config.ts to configure build targets.',\n );\n }\n\n const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));\n return {\n name: pkg.name?.replace(/^@[^/]+\\//, '') || path.basename(cwd),\n version: pkg.version || '1.0.0',\n entry: pkg.main,\n deployments: [{ target: 'node' }],\n };\n}\n\n/**\n * Validate raw config against the Zod schema.\n */\nexport function validateConfig(raw: unknown): FrontMcpConfigParsed {\n const result = frontmcpConfigSchema.safeParse(raw);\n if (!result.success) {\n const issues = result.error.issues.map((i) => ` - ${i.path.join('.')}: ${i.message}`).join('\\n');\n throw new Error(`Invalid frontmcp.config:\\n${issues}`);\n }\n return result.data;\n}\n\n/**\n * Find a deployment target by type.\n */\nexport function findDeployment(config: FrontMcpConfigParsed, target: string): DeploymentTarget | undefined {\n return config.deployments.find((d) => d.target === target);\n}\n\n/**\n * Get all deployment target types from the config.\n */\nexport function getDeploymentTargets(config: FrontMcpConfigParsed): string[] {\n return config.deployments.map((d) => d.target);\n}\n"]}