|
|
/**
|
|
|
* 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,
|
|
|
BUDDY: true,
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* 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)`);
|
|
|
}
|
|
|
}
|
|
|
|