From 40dc2720d08e7874a17658cb0d561771d6c4ca3d Mon Sep 17 00:00:00 2001 From: Askhz <1361267452@qq.com> Date: Mon, 11 May 2026 20:59:10 +0800 Subject: [PATCH] =?UTF-8?q?fix(server):=20=E4=BF=AE=E5=A4=8D=20csc.exe=20s?= =?UTF-8?q?erver=20=E6=A8=A1=E5=BC=8F=E4=B8=8B=E6=8E=A2=E9=92=88=E8=BF=9B?= =?UTF-8?q?=E7=A8=8B=E5=9B=A0=20--feature=20=E5=8F=82=E6=95=B0=E5=B4=A9?= =?UTF-8?q?=E6=BA=83=E5=AF=BC=E8=87=B4=E6=A8=A1=E5=9E=8B=E5=88=97=E8=A1=A8?= =?UTF-8?q?=E4=B8=BA=E7=A9=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bun 将源码编译为独立可执行文件后,process.argv[1] 变为内部虚拟路径 (B:/~BUN/root/cli.js),该路径含 / 被误判为脚本模式,导致 saveChildSpawnPrefix 为子进程生成 --feature / -d 参数。子进程(csc.exe 本身)不识别这些 Bun 运行时参数,以 exit code 1 退出,探针失败,/provider/capabilities 返回空模型。 修复: - getScriptArgsForChild: 识别并跳过 Bun snapshot 虚拟路径前缀 (B:/~BUN/, /snapshot/) - saveChildSpawnPrefix: 仅在 scriptArgs 非空(脚本模式)时才生成 --feature/define 参数 - build.ts: 补充 Step 7 编译独立可执行文件 csc.exe / csc Co-Authored-By: Claude Opus 4.7 --- build.ts | 37 +++++++++++++++++++++++++++++++++++++ src/server/childSpawn.ts | 37 ++++++++++++++++++++++++------------- 2 files changed, 61 insertions(+), 13 deletions(-) diff --git a/build.ts b/build.ts index 927952589..6cffa52b1 100644 --- a/build.ts +++ b/build.ts @@ -172,3 +172,40 @@ chmodSync(cliBun, 0o755) chmodSync(cliNode, 0o755) console.log(`Generated ${cliBun} (shebang: bun) and ${cliNode} (shebang: node)`) + +// Step 7: Compile standalone executable (csc.exe on Windows, csc on Unix) +// Must use Bun.build({ compile: true, features }) instead of `bun build --compile` +// because the CLI command doesn't support the --feature flag. +// NOTE: compile mode uses outdir (not outfile) — Bun names the output after the entrypoint. +const isWin = process.platform === 'win32' +const exeName = isWin ? 'csc.exe' : 'csc' + +const compileResult = await Bun.build({ + entrypoints: ['src/entrypoints/cli.tsx'], + target: 'bun', + compile: true, + define: getMacroDefines(), + features, + outdir: import.meta.dir, +}) + +if (!compileResult.success) { + console.error('Compile failed:') + for (const log of compileResult.logs) { + console.error(log) + } + process.exit(1) +} + +// Rename auto-generated cli.tsx.exe → csc.exe +const generated = compileResult.outputs[0] +const targetPath = join(import.meta.dir, exeName) +if (generated && generated.path !== targetPath) { + const { renameSync, unlinkSync, existsSync } = await import('fs') + if (existsSync(targetPath)) { + try { unlinkSync(targetPath) } catch { /* locked by running process */ } + } + renameSync(generated.path, targetPath) +} + +console.log(`Compiled standalone executable: ${join(import.meta.dir, exeName)}`) diff --git a/src/server/childSpawn.ts b/src/server/childSpawn.ts index f4cc8cb1c..9ef52e639 100644 --- a/src/server/childSpawn.ts +++ b/src/server/childSpawn.ts @@ -6,6 +6,10 @@ export const INIT_TIMEOUT_MS = (() => { export function getScriptArgsForChild(): string[] { const argv1 = process.argv[1] if (!argv1) return [] + // Bun standalone executable embeds a virtual snapshot path like "B:/~BUN/root/cli.js". + // This is not a real file on disk — skip it so the child process (the same exe) + // is not launched with a bogus script argument. + if (argv1.startsWith('B:/~BUN/') || argv1.startsWith('/snapshot/')) return [] if (argv1.endsWith('.ts') || argv1.endsWith('.tsx') || argv1.includes('/') || argv1.includes('\\')) { return [argv1] } @@ -23,19 +27,26 @@ export async function saveChildSpawnPrefix(): Promise { const { execPath, scriptArgs } = getChildSpawnArgs() let defineArgs: string[] = [] let featureArgs: string[] = [] - try { - const definesMod = await import('../../scripts/defines.js') as { getMacroDefines: () => Record; DEFAULT_BUILD_FEATURES: readonly string[] } - const defines = definesMod.getMacroDefines() - defineArgs = Object.entries(defines).flatMap(([k, v]) => ['-d', `${k}:${v}`]) - const features = definesMod.DEFAULT_BUILD_FEATURES - featureArgs = features.flatMap((f: string) => ['--feature', f]) - } catch {} - const envFeatures = Object.entries(process.env) - .filter(([k]) => k.startsWith('FEATURE_') && k.slice(8)) - .map(([k]) => ['--feature', k.slice(8)] as [string, string]) - .flat() - const allFeatureArgs = [...featureArgs, ...envFeatures] - const prefix = JSON.stringify({ execPath, scriptArgs, defineArgs, featureArgs: allFeatureArgs }) + // Only pass --feature/define flags when running as a script (dev mode). + // In compiled standalone executable mode scriptArgs is empty, and feature + // flags are already baked in at compile time — passing --feature to the + // child process would cause "unknown option" errors. + const isScriptMode = scriptArgs.length > 0 + if (isScriptMode) { + try { + const definesMod = await import('../../scripts/defines.js') as { getMacroDefines: () => Record; DEFAULT_BUILD_FEATURES: readonly string[] } + const defines = definesMod.getMacroDefines() + defineArgs = Object.entries(defines).flatMap(([k, v]) => ['-d', `${k}:${v}`]) + const features = definesMod.DEFAULT_BUILD_FEATURES + featureArgs = features.flatMap((f: string) => ['--feature', f]) + } catch {} + const envFeatures = Object.entries(process.env) + .filter(([k]) => k.startsWith('FEATURE_') && k.slice(8)) + .map(([k]) => ['--feature', k.slice(8)] as [string, string]) + .flat() + featureArgs = [...featureArgs, ...envFeatures] + } + const prefix = JSON.stringify({ execPath, scriptArgs, defineArgs, featureArgs }) process.env._CSC_CHILD_SPAWN_PREFIX = prefix }