feat: 补充构建配置,实现 Claude Code 源码编译
添加缺失的项目配置文件,使源码快照可以成功编译和运行: - package.json: 85+ 依赖项 + 构建脚本 - tsconfig.json: TypeScript + JSX + src/ 路径别名 - globals.d.ts: MACRO 构建常量 + bun:bundle 类型声明 - scripts/build.ts: Bun 构建脚本(feature flag polyfill + MACRO 注入 + 内部包 stub + 缺失源文件自动 stub) - BUILD_GUIDE.md: 中文编译文档 - 修 Commander.js v13 的 -d2e 短标志兼容问题 构建脚本通过 missingSourceStubPlugin 插件自动检测并 stub 缺失的源文件, 无需在源码树中手动创建 stub 文件。 构建结果:dist/cli.js (11.7 MB),CLI 可正常启动并显示交互式终端 UI。 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
committed by
liuzenghui
co-authored by
Copilot
parent
16a676ffa3
commit
83fb08926d
@@ -0,0 +1,477 @@
|
||||
/**
|
||||
* Build script for Claude Code using Bun's bundler API.
|
||||
*
|
||||
* Handles:
|
||||
* - MACRO.* build-time constant injection
|
||||
* - bun:bundle feature flag polyfill (all flags default to false)
|
||||
* - src/ path alias resolution
|
||||
* - Internal Anthropic package stubs
|
||||
*
|
||||
* Usage: bun run scripts/build.ts
|
||||
*/
|
||||
|
||||
import { type BunPlugin } from "bun";
|
||||
import { existsSync, mkdirSync } from "fs";
|
||||
import { dirname, join, relative } from "path";
|
||||
|
||||
const PROJECT_ROOT = join(import.meta.dir, "..");
|
||||
const OUT_DIR = join(PROJECT_ROOT, "dist");
|
||||
|
||||
// Ensure output directory exists
|
||||
mkdirSync(OUT_DIR, { recursive: true });
|
||||
|
||||
// Build-time MACRO values
|
||||
const VERSION = process.env.CLAUDE_CODE_VERSION || "1.0.0-research";
|
||||
const BUILD_TIME = new Date().toISOString();
|
||||
const PACKAGE_URL = "@anthropic-ai/claude-code";
|
||||
const FEEDBACK_CHANNEL = "https://github.com/anthropics/claude-code/issues";
|
||||
const ISSUES_EXPLAINER =
|
||||
"report issues at https://github.com/anthropics/claude-code/issues";
|
||||
const VERSION_CHANGELOG = "";
|
||||
|
||||
// Feature flags — all disabled by default for external builds
|
||||
const FEATURE_FLAGS: Record<string, boolean> = {
|
||||
PROACTIVE: false,
|
||||
KAIROS: false,
|
||||
BRIDGE_MODE: false,
|
||||
DAEMON: false,
|
||||
VOICE_MODE: false,
|
||||
AGENT_TRIGGERS: false,
|
||||
MONITOR_TOOL: false,
|
||||
COORDINATOR_MODE: false,
|
||||
ABLATION_BASELINE: false,
|
||||
DUMP_SYSTEM_PROMPT: false,
|
||||
CHICAGO_MCP: false,
|
||||
};
|
||||
|
||||
/**
|
||||
* Plugin to handle `bun:bundle` imports.
|
||||
* The `feature()` function returns false for all flags in external builds.
|
||||
*/
|
||||
const bunBundlePlugin: BunPlugin = {
|
||||
name: "bun-bundle-polyfill",
|
||||
setup(build) {
|
||||
build.onResolve({ filter: /^bun:bundle$/ }, (args) => {
|
||||
return {
|
||||
path: "bun:bundle",
|
||||
namespace: "bun-bundle-polyfill",
|
||||
};
|
||||
});
|
||||
build.onLoad(
|
||||
{ filter: /.*/, namespace: "bun-bundle-polyfill" },
|
||||
(args) => {
|
||||
const featureFnBody = Object.entries(FEATURE_FLAGS)
|
||||
.map(([k, v]) => ` if (name === ${JSON.stringify(k)}) return ${v};`)
|
||||
.join("\n");
|
||||
|
||||
return {
|
||||
contents: `
|
||||
export function feature(name) {
|
||||
${featureFnBody}
|
||||
return false;
|
||||
}
|
||||
`,
|
||||
loader: "js",
|
||||
};
|
||||
}
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Plugin to provide inline stubs for internal Anthropic packages.
|
||||
* Each package gets properly named exports to satisfy static analysis.
|
||||
*/
|
||||
const internalPackageStubPlugin: BunPlugin = {
|
||||
name: "internal-package-stubs",
|
||||
setup(build) {
|
||||
// Map of package names to their required value exports
|
||||
const packageExports: Record<string, string[]> = {
|
||||
"@ant/claude-for-chrome-mcp": [
|
||||
"BROWSER_TOOLS[]",
|
||||
"createClaudeForChromeMcpServer",
|
||||
],
|
||||
"@ant/computer-use-input": [],
|
||||
"@ant/computer-use-mcp": [
|
||||
"API_RESIZE_PARAMS{}",
|
||||
"DEFAULT_GRANT_FLAGS{}",
|
||||
"bindSessionContext",
|
||||
"buildComputerUseTools",
|
||||
"createComputerUseMcpServer",
|
||||
"targetImageSize",
|
||||
"getSentinelCategory",
|
||||
],
|
||||
"@ant/computer-use-mcp/sentinelApps": ["getSentinelCategory"],
|
||||
"@ant/computer-use-mcp/types": [
|
||||
"DEFAULT_GRANT_FLAGS{}",
|
||||
"getSentinelCategory",
|
||||
],
|
||||
"@ant/computer-use-swift": [],
|
||||
"@anthropic-ai/claude-agent-sdk": [],
|
||||
"@anthropic-ai/mcpb": [],
|
||||
"@anthropic-ai/sandbox-runtime": [
|
||||
"SandboxManager",
|
||||
"SandboxRuntimeConfigSchema",
|
||||
"SandboxViolationStore",
|
||||
],
|
||||
"@anthropic-ai/foundry-sdk": [],
|
||||
"color-diff-napi": ["ColorDiff", "ColorFile", "getSyntaxTheme"],
|
||||
"modifiers-napi": ["isModifierPressed"],
|
||||
};
|
||||
|
||||
// Match any package in our map (including subpaths like @ant/computer-use-mcp/types)
|
||||
build.onResolve(
|
||||
{ filter: /^(@ant\/|@anthropic-ai\/(sandbox-runtime|claude-agent-sdk|mcpb|foundry-sdk)|color-diff-napi|modifiers-napi)/ },
|
||||
(args) => ({
|
||||
path: args.path,
|
||||
namespace: "internal-stub",
|
||||
})
|
||||
);
|
||||
|
||||
build.onLoad(
|
||||
{ filter: /.*/, namespace: "internal-stub" },
|
||||
(args) => {
|
||||
// Find the best matching exports for this path
|
||||
const exports = packageExports[args.path] || [];
|
||||
|
||||
const exportLines = exports
|
||||
.map((name) => {
|
||||
// Array exports (name[])
|
||||
if (name.endsWith("[]")) {
|
||||
const cleanName = name.slice(0, -2);
|
||||
return `export const ${cleanName} = [];`;
|
||||
}
|
||||
// Object exports (name{})
|
||||
if (name.endsWith("{}")) {
|
||||
const cleanName = name.slice(0, -2);
|
||||
return `export const ${cleanName} = {};`;
|
||||
}
|
||||
// ColorDiff/ColorFile need render() method
|
||||
if (name === "ColorDiff" || name === "ColorFile") {
|
||||
return `export class ${name} { constructor(...a){} render(...a){ return null; } static create(...a){ return new ${name}(); } }`;
|
||||
}
|
||||
if (
|
||||
name === "SandboxManager" ||
|
||||
name === "SandboxViolationStore"
|
||||
) {
|
||||
return `export class ${name} {
|
||||
constructor(...a){}
|
||||
static create(...a){ return new ${name}(); }
|
||||
static isSupportedPlatform(){ return false; }
|
||||
static checkDependencies(...a){ return { satisfied: false, missing: [] }; }
|
||||
static wrapWithSandbox(...a){ return a[0]; }
|
||||
static initialize(...a){ return Promise.resolve(); }
|
||||
static updateConfig(...a){}
|
||||
static reset(){ return Promise.resolve(); }
|
||||
static getFsReadConfig(){ return {}; }
|
||||
static getFsWriteConfig(){ return {}; }
|
||||
static getNetworkRestrictionConfig(){ return {}; }
|
||||
static getIgnoreViolations(){ return {}; }
|
||||
static getAllowUnixSockets(){ return false; }
|
||||
static getAllowLocalBinding(){ return false; }
|
||||
static getEnableWeakerNestedSandbox(){ return false; }
|
||||
static getProxyPort(){ return 0; }
|
||||
static getSocksProxyPort(){ return 0; }
|
||||
static getLinuxHttpSocketPath(){ return ''; }
|
||||
static getLinuxSocksSocketPath(){ return ''; }
|
||||
static waitForNetworkInitialization(){ return Promise.resolve(); }
|
||||
static getSandboxViolationStore(){ return new ${name === 'SandboxManager' ? 'SandboxViolationStore' : name}(); }
|
||||
static annotateStderrWithSandboxFailures(...a){ return a[0]; }
|
||||
static cleanupAfterCommand(){ return Promise.resolve(); }
|
||||
dispose(){}
|
||||
}`;
|
||||
}
|
||||
if (name === "SandboxRuntimeConfigSchema") {
|
||||
return `export const ${name} = { parse: (...a) => ({}), safeParse: (...a) => ({ success: true, data: {} }) };`;
|
||||
}
|
||||
if (
|
||||
name.startsWith("create") ||
|
||||
name.startsWith("build") ||
|
||||
name.startsWith("bind") ||
|
||||
name.startsWith("get") ||
|
||||
name === "targetImageSize"
|
||||
) {
|
||||
return `export function ${name}(...args) { return {}; }`;
|
||||
}
|
||||
// Default: export as empty object/array
|
||||
if (name.endsWith("[]")) {
|
||||
const cleanName = name.slice(0, -2);
|
||||
return `export const ${cleanName} = [];`;
|
||||
}
|
||||
return `export const ${name} = {};`;
|
||||
})
|
||||
.join("\n");
|
||||
|
||||
return {
|
||||
contents: `// Stub: ${args.path}\nexport default {};\n${exportLines}\n`,
|
||||
loader: "js",
|
||||
};
|
||||
}
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Plugin to auto-stub missing source files at build time.
|
||||
* When the bundler encounters an import to a file that doesn't exist on disk,
|
||||
* this plugin provides a stub implementation instead of failing the build.
|
||||
* This eliminates the need for physical stub files in the source tree.
|
||||
*/
|
||||
const missingSourceStubPlugin: BunPlugin = {
|
||||
name: "missing-source-stubs",
|
||||
setup(build) {
|
||||
// 需要特定 named exports 的缺失模块(key: 相对路径,不含扩展名)
|
||||
const specificStubs: Record<string, string> = {
|
||||
"src/types/connectorText": [
|
||||
"export default {};",
|
||||
"export function isConnectorTextBlock(block) { return block?.type === 'connector_text'; }",
|
||||
].join("\n"),
|
||||
"src/utils/protectedNamespace": [
|
||||
"export default {};",
|
||||
"export const PROTECTED_NAMESPACE_PREFIXES = [];",
|
||||
"export function isProtectedNamespace(_name) { return false; }",
|
||||
].join("\n"),
|
||||
"src/tools/WorkflowTool/constants": [
|
||||
"export default {};",
|
||||
"export const WORKFLOW_TOOL_NAME = 'workflow';",
|
||||
].join("\n"),
|
||||
"src/utils/filePersistence/types": [
|
||||
"export default {};",
|
||||
"export const DEFAULT_UPLOAD_CONCURRENCY = 5;",
|
||||
"export const FILE_COUNT_LIMIT = 1000;",
|
||||
"export const OUTPUTS_SUBDIR = 'outputs';",
|
||||
].join("\n"),
|
||||
"src/tools/REPLTool/REPLTool": [
|
||||
"export default {};",
|
||||
"export const REPLTool = { name: 'repl', description: 'REPL tool (stub)' };",
|
||||
].join("\n"),
|
||||
|
||||
"src/tools/SuggestBackgroundPRTool/SuggestBackgroundPRTool": [
|
||||
"export default {};",
|
||||
"export const SuggestBackgroundPRTool = { name: 'suggest_background_pr', description: 'stub' };",
|
||||
].join("\n"),
|
||||
"src/tools/TungstenTool/TungstenLiveMonitor": [
|
||||
"export default {};",
|
||||
"export const TungstenLiveMonitor = {};",
|
||||
].join("\n"),
|
||||
"src/tools/TungstenTool/TungstenTool": [
|
||||
"export default {};",
|
||||
"export const TungstenTool = { name: 'tungsten', description: 'Tungsten tool (stub)' };",
|
||||
].join("\n"),
|
||||
"src/tools/VerifyPlanExecutionTool/VerifyPlanExecutionTool": [
|
||||
"export default {};",
|
||||
"export const VerifyPlanExecutionTool = { name: 'verify_plan_execution', description: 'stub' };",
|
||||
].join("\n"),
|
||||
"src/assistant/AssistantSessionChooser": [
|
||||
"export default {};",
|
||||
"export const AssistantSessionChooser = () => null;",
|
||||
].join("\n"),
|
||||
"src/components/agents/SnapshotUpdateDialog": [
|
||||
"export default {};",
|
||||
"export const SnapshotUpdateDialog = () => null;",
|
||||
].join("\n"),
|
||||
"src/commands/assistant/assistant": [
|
||||
"export default {};",
|
||||
"export const NewInstallWizard = () => null;",
|
||||
"export function computeDefaultInstallDir() { return ''; }",
|
||||
].join("\n"),
|
||||
"src/services/remoteManagedSettings/securityCheck": [
|
||||
"export default {};",
|
||||
"export function checkManagedSettingsSecurity() { return { ok: true }; }",
|
||||
"export function handleSecurityCheckResult() {}",
|
||||
].join("\n"),
|
||||
};
|
||||
|
||||
build.onResolve({ filter: /\.(?:ts|tsx|js|jsx|md|txt)$/ }, (args) => {
|
||||
if (args.namespace !== "file") return;
|
||||
|
||||
let basePath: string;
|
||||
if (args.path.startsWith("src/")) {
|
||||
basePath = join(PROJECT_ROOT, args.path);
|
||||
} else if (args.path.startsWith(".") || args.path.startsWith("/")) {
|
||||
if (!args.importer) return;
|
||||
basePath = join(dirname(args.importer), args.path);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
// 对 .js/.jsx 导入尝试 .ts/.tsx 变体
|
||||
const candidates = [basePath];
|
||||
if (basePath.endsWith(".js")) {
|
||||
const base = basePath.slice(0, -3);
|
||||
candidates.push(base + ".ts", base + ".tsx", base + ".d.ts");
|
||||
} else if (basePath.endsWith(".jsx")) {
|
||||
const base = basePath.slice(0, -4);
|
||||
candidates.push(base + ".tsx", base + ".ts");
|
||||
}
|
||||
|
||||
for (const c of candidates) {
|
||||
if (existsSync(c)) return;
|
||||
}
|
||||
|
||||
// 文件不存在 → 重定向到 stub 命名空间
|
||||
let stubKey = relative(PROJECT_ROOT, basePath).replace(/\\/g, "/");
|
||||
if (stubKey.endsWith(".js") || stubKey.endsWith(".jsx")) {
|
||||
stubKey = stubKey.replace(/\.jsx?$/, "");
|
||||
}
|
||||
|
||||
return { path: stubKey, namespace: "missing-source" };
|
||||
});
|
||||
|
||||
build.onLoad({ filter: /.*/, namespace: "missing-source" }, (args) => {
|
||||
const key = args.path;
|
||||
|
||||
if (key.endsWith(".md") || key.endsWith(".txt")) {
|
||||
return { contents: `export default '';`, loader: "js" };
|
||||
}
|
||||
|
||||
if (key.endsWith(".d.ts")) {
|
||||
return { contents: `export {};`, loader: "js" };
|
||||
}
|
||||
|
||||
const stubContent = specificStubs[key];
|
||||
if (stubContent !== undefined) {
|
||||
return { contents: stubContent, loader: "js" };
|
||||
}
|
||||
|
||||
console.log(` ⚠️ Auto-stubbing missing module: ${key}`);
|
||||
return { contents: `export default {};`, loader: "js" };
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
console.log("🔨 Building Claude Code...");
|
||||
console.log(` Version: ${VERSION}`);
|
||||
console.log(` Build time: ${BUILD_TIME}`);
|
||||
console.log(` Output: ${OUT_DIR}`);
|
||||
console.log(` Feature flags: all disabled (external build)`);
|
||||
console.log("");
|
||||
|
||||
const result = await Bun.build({
|
||||
entrypoints: [join(PROJECT_ROOT, "src/entrypoints/cli.tsx")],
|
||||
outdir: OUT_DIR,
|
||||
target: "bun",
|
||||
format: "esm",
|
||||
splitting: false,
|
||||
sourcemap: "external",
|
||||
minify: false,
|
||||
plugins: [bunBundlePlugin, internalPackageStubPlugin, missingSourceStubPlugin],
|
||||
define: {
|
||||
"MACRO.VERSION": JSON.stringify(VERSION),
|
||||
"MACRO.BUILD_TIME": JSON.stringify(BUILD_TIME),
|
||||
"MACRO.PACKAGE_URL": JSON.stringify(PACKAGE_URL),
|
||||
"MACRO.NATIVE_PACKAGE_URL": "undefined",
|
||||
"MACRO.FEEDBACK_CHANNEL": JSON.stringify(FEEDBACK_CHANNEL),
|
||||
"MACRO.ISSUES_EXPLAINER": JSON.stringify(ISSUES_EXPLAINER),
|
||||
"MACRO.VERSION_CHANGELOG": JSON.stringify(VERSION_CHANGELOG),
|
||||
},
|
||||
external: [
|
||||
// Node.js built-ins
|
||||
"node:*",
|
||||
"fs",
|
||||
"fs/promises",
|
||||
"path",
|
||||
"crypto",
|
||||
"os",
|
||||
"util",
|
||||
"url",
|
||||
"http",
|
||||
"https",
|
||||
"net",
|
||||
"tls",
|
||||
"dns",
|
||||
"stream",
|
||||
"events",
|
||||
"buffer",
|
||||
"child_process",
|
||||
"process",
|
||||
"readline",
|
||||
"tty",
|
||||
"v8",
|
||||
"zlib",
|
||||
"async_hooks",
|
||||
"perf_hooks",
|
||||
// Keep all npm deps external (not bundled)
|
||||
"@alcalzone/ansi-tokenize",
|
||||
"@anthropic-ai/sdk",
|
||||
"@aws-sdk/client-bedrock-runtime",
|
||||
"@commander-js/extra-typings",
|
||||
"@growthbook/growthbook",
|
||||
"@modelcontextprotocol/sdk",
|
||||
"@opentelemetry/*",
|
||||
"ajv",
|
||||
"asciichart",
|
||||
"auto-bind",
|
||||
"axios",
|
||||
"bidi-js",
|
||||
"chalk",
|
||||
"chokidar",
|
||||
"cli-boxes",
|
||||
"code-excerpt",
|
||||
"diff",
|
||||
"emoji-regex",
|
||||
"env-paths",
|
||||
"execa",
|
||||
"figures",
|
||||
"fuse.js",
|
||||
"get-east-asian-width",
|
||||
"google-auth-library",
|
||||
"highlight.js",
|
||||
"https-proxy-agent",
|
||||
"ignore",
|
||||
"indent-string",
|
||||
"jsonc-parser",
|
||||
"lodash-es",
|
||||
"lru-cache",
|
||||
"marked",
|
||||
"p-map",
|
||||
"picomatch",
|
||||
"proper-lockfile",
|
||||
"qrcode",
|
||||
"react",
|
||||
"react-reconciler",
|
||||
"semver",
|
||||
"shell-quote",
|
||||
"signal-exit",
|
||||
"stack-utils",
|
||||
"strip-ansi",
|
||||
"supports-hyperlinks",
|
||||
"tree-kill",
|
||||
"type-fest",
|
||||
"undici",
|
||||
"usehooks-ts",
|
||||
"vscode-jsonrpc",
|
||||
"vscode-languageserver-protocol",
|
||||
"vscode-languageserver-types",
|
||||
"wrap-ansi",
|
||||
"ws",
|
||||
"xss",
|
||||
"zod",
|
||||
"@anthropic-ai/bedrock-sdk",
|
||||
"@anthropic-ai/vertex-sdk",
|
||||
"@anthropic-ai/foundry-sdk",
|
||||
"@azure/identity",
|
||||
"@aws-sdk/client-bedrock",
|
||||
"@aws-sdk/client-sts",
|
||||
"fflate",
|
||||
"yaml",
|
||||
"sharp",
|
||||
"modifiers-napi",
|
||||
"turndown",
|
||||
],
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
console.error("❌ Build failed!");
|
||||
for (const log of result.logs) {
|
||||
console.error(` ${log.message}`);
|
||||
}
|
||||
process.exit(1);
|
||||
} else {
|
||||
console.log(`✅ Build succeeded!`);
|
||||
console.log(` Output files:`);
|
||||
for (const output of result.outputs) {
|
||||
const sizeKB = (output.size / 1024).toFixed(1);
|
||||
console.log(` ${output.path} (${sizeKB} KB)`);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user