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.
- package/package.json +5 -5
- package/src/commands/build/adapters/cloudflare.js +56 -4
- package/src/commands/build/adapters/cloudflare.js.map +1 -1
- package/src/commands/build/adapters/distributed.js +4 -1
- package/src/commands/build/adapters/distributed.js.map +1 -1
- package/src/commands/build/adapters/lambda.js +32 -0
- package/src/commands/build/adapters/lambda.js.map +1 -1
- package/src/commands/build/bundler.js +23 -1
- package/src/commands/build/bundler.js.map +1 -1
- package/src/commands/build/exec/cli-runtime/extract-public-message.snippet.d.ts +18 -0
- package/src/commands/build/exec/cli-runtime/extract-public-message.snippet.js +58 -0
- package/src/commands/build/exec/cli-runtime/extract-public-message.snippet.js.map +1 -0
- package/src/commands/build/exec/cli-runtime/generate-cli-entry.d.ts +2 -2
- package/src/commands/build/exec/cli-runtime/generate-cli-entry.js +195 -47
- package/src/commands/build/exec/cli-runtime/generate-cli-entry.js.map +1 -1
- package/src/commands/build/exec/cli-runtime/schema-extractor.d.ts +7 -0
- package/src/commands/build/exec/cli-runtime/schema-extractor.js +17 -1
- package/src/commands/build/exec/cli-runtime/schema-extractor.js.map +1 -1
- package/src/commands/build/exec/index.d.ts +11 -0
- package/src/commands/build/exec/index.js +68 -20
- package/src/commands/build/exec/index.js.map +1 -1
- package/src/commands/build/exec/installer-script.d.ts +8 -2
- package/src/commands/build/exec/installer-script.js +34 -15
- package/src/commands/build/exec/installer-script.js.map +1 -1
- package/src/commands/build/exec/manifest.d.ts +16 -3
- package/src/commands/build/exec/manifest.js +17 -5
- package/src/commands/build/exec/manifest.js.map +1 -1
- package/src/commands/build/exec/runner-script.d.ts +9 -1
- package/src/commands/build/exec/runner-script.js +60 -2
- package/src/commands/build/exec/runner-script.js.map +1 -1
- package/src/commands/build/index.js +86 -22
- package/src/commands/build/index.js.map +1 -1
- package/src/commands/build/load-entry-config.d.ts +33 -0
- package/src/commands/build/load-entry-config.js +206 -0
- package/src/commands/build/load-entry-config.js.map +1 -0
- package/src/commands/build/mcpb/manifest.d.ts +14 -0
- package/src/commands/build/mcpb/manifest.js +29 -0
- package/src/commands/build/mcpb/manifest.js.map +1 -1
- package/src/commands/build/types.d.ts +36 -1
- package/src/commands/build/types.js.map +1 -1
- package/src/commands/dev/doctor.js +7 -3
- package/src/commands/dev/doctor.js.map +1 -1
- package/src/commands/package/install.d.ts +1 -1
- package/src/commands/package/install.js +10 -8
- package/src/commands/package/install.js.map +1 -1
- package/src/commands/package/types.d.ts +2 -1
- package/src/commands/package/types.js.map +1 -1
- package/src/config/frontmcp-config.loader.d.ts +20 -0
- package/src/config/frontmcp-config.loader.js +152 -5
- package/src/config/frontmcp-config.loader.js.map +1 -1
- package/src/config/index.d.ts +1 -1
- package/src/config/index.js +2 -1
- 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;;
|
|
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
|
|
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
|
-
|
|
55
|
-
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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({
|
|
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(
|
|
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":"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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 { 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>;
|