Add build infrastructure and QUICKSTART guide

- Build script (scripts/build.mjs) using esbuild with iterative stub creation
- Stubs for Bun compile-time intrinsics (feature(), MACRO, bun:bundle)
- Stub modules for feature-gated internal code paths
- QUICKSTART.md with 3 build options (pre-built, esbuild, Bun)
- tsconfig.json, package.json for build tooling

Build reaches ~95% — 108 remaining feature-gated modules need Bun runtime
for full dead code elimination. See QUICKSTART.md for details.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
sanbuphy
2026-03-31 18:12:54 +08:00
parent f3e3075bbc
commit 1b77a70b18
23 changed files with 1436 additions and 0 deletions

245
scripts/build.mjs Normal file
View File

@@ -0,0 +1,245 @@
#!/usr/bin/env node
/**
* build.mjs — Best-effort build of Claude Code v2.1.88 from source
*
* ⚠️ IMPORTANT: A complete rebuild requires the Bun runtime's compile-time
* intrinsics (feature(), MACRO, bun:bundle). This script provides a
* best-effort build using esbuild. See KNOWN_ISSUES.md for details.
*
* What this script does:
* 1. Copy src/ → build-src/ (original untouched)
* 2. Replace `feature('X')` → `false` (compile-time → runtime)
* 3. Replace `MACRO.VERSION` etc → string literals
* 4. Replace `import from 'bun:bundle'` → stub
* 5. Create stubs for missing feature-gated modules
* 6. Bundle with esbuild → dist/cli.js
*
* Requirements: Node.js >= 18, npm
* Usage: node scripts/build.mjs
*/
import { readdir, readFile, writeFile, mkdir, cp, rm, stat } from 'node:fs/promises'
import { join, dirname } from 'node:path'
import { execSync } from 'node:child_process'
import { fileURLToPath } from 'node:url'
const __dirname = dirname(fileURLToPath(import.meta.url))
const ROOT = join(__dirname, '..')
const VERSION = '2.1.88'
const BUILD = join(ROOT, 'build-src')
const ENTRY = join(BUILD, 'entry.ts')
// ── Helpers ────────────────────────────────────────────────────────────────
async function* walk(dir) {
for (const e of await readdir(dir, { withFileTypes: true })) {
const p = join(dir, e.name)
if (e.isDirectory() && e.name !== 'node_modules') yield* walk(p)
else yield p
}
}
async function exists(p) { try { await stat(p); return true } catch { return false } }
async function ensureEsbuild() {
try { execSync('npx esbuild --version', { stdio: 'pipe' }) }
catch {
console.log('📦 Installing esbuild...')
execSync('npm install --save-dev esbuild', { cwd: ROOT, stdio: 'inherit' })
}
}
// ══════════════════════════════════════════════════════════════════════════════
// PHASE 1: Copy source
// ══════════════════════════════════════════════════════════════════════════════
await rm(BUILD, { recursive: true, force: true })
await mkdir(BUILD, { recursive: true })
await cp(join(ROOT, 'src'), join(BUILD, 'src'), { recursive: true })
console.log('✅ Phase 1: Copied src/ → build-src/')
// ══════════════════════════════════════════════════════════════════════════════
// PHASE 2: Transform source
// ══════════════════════════════════════════════════════════════════════════════
let transformCount = 0
// MACRO replacements
const MACROS = {
'MACRO.VERSION': `'${VERSION}'`,
'MACRO.BUILD_TIME': `''`,
'MACRO.FEEDBACK_CHANNEL': `'https://github.com/anthropics/claude-code/issues'`,
'MACRO.ISSUES_EXPLAINER': `'https://github.com/anthropics/claude-code/issues/new/choose'`,
'MACRO.FEEDBACK_CHANNEL_URL': `'https://github.com/anthropics/claude-code/issues'`,
'MACRO.ISSUES_EXPLAINER_URL': `'https://github.com/anthropics/claude-code/issues/new/choose'`,
'MACRO.NATIVE_PACKAGE_URL': `'@anthropic-ai/claude-code'`,
'MACRO.PACKAGE_URL': `'@anthropic-ai/claude-code'`,
'MACRO.VERSION_CHANGELOG': `''`,
}
for await (const file of walk(join(BUILD, 'src'))) {
if (!file.match(/\.[tj]sx?$/)) continue
let src = await readFile(file, 'utf8')
let changed = false
// 2a. feature('X') → false
if (/\bfeature\s*\(\s*['"][A-Z_]+['"]\s*\)/.test(src)) {
src = src.replace(/\bfeature\s*\(\s*['"][A-Z_]+['"]\s*\)/g, 'false')
changed = true
}
// 2b. MACRO.X → literals
for (const [k, v] of Object.entries(MACROS)) {
if (src.includes(k)) {
src = src.replaceAll(k, v)
changed = true
}
}
// 2c. Remove bun:bundle import (feature() is already replaced)
if (src.includes("from 'bun:bundle'") || src.includes('from "bun:bundle"')) {
src = src.replace(/import\s*\{\s*feature\s*\}\s*from\s*['"]bun:bundle['"];?\n?/g, '// feature() replaced with false at build time\n')
changed = true
}
// 2d. Remove type-only import of global.d.ts
if (src.includes("import '../global.d.ts'") || src.includes("import './global.d.ts'")) {
src = src.replace(/import\s*['"][.\/]*global\.d\.ts['"];?\n?/g, '')
changed = true
}
if (changed) {
await writeFile(file, src, 'utf8')
transformCount++
}
}
console.log(`✅ Phase 2: Transformed ${transformCount} files`)
// ══════════════════════════════════════════════════════════════════════════════
// PHASE 3: Create entry wrapper
// ══════════════════════════════════════════════════════════════════════════════
await writeFile(ENTRY, `#!/usr/bin/env node
// Claude Code v${VERSION} — built from source
// Copyright (c) Anthropic PBC. All rights reserved.
import './src/entrypoints/cli.tsx'
`, 'utf8')
console.log('✅ Phase 3: Created entry wrapper')
// ══════════════════════════════════════════════════════════════════════════════
// PHASE 4: Iterative stub + bundle
// ══════════════════════════════════════════════════════════════════════════════
await ensureEsbuild()
const OUT_DIR = join(ROOT, 'dist')
await mkdir(OUT_DIR, { recursive: true })
const OUT_FILE = join(OUT_DIR, 'cli.js')
// Run up to 5 rounds of: esbuild → collect missing → create stubs → retry
const MAX_ROUNDS = 5
let succeeded = false
for (let round = 1; round <= MAX_ROUNDS; round++) {
console.log(`\n🔨 Phase 4 round ${round}/${MAX_ROUNDS}: Bundling...`)
let esbuildOutput = ''
try {
esbuildOutput = execSync([
'npx esbuild',
`"${ENTRY}"`,
'--bundle',
'--platform=node',
'--target=node18',
'--format=esm',
`--outfile="${OUT_FILE}"`,
`--banner:js=$'#!/usr/bin/env node\\n// Claude Code v${VERSION} (built from source)\\n// Copyright (c) Anthropic PBC. All rights reserved.\\n'`,
'--packages=external',
'--external:bun:*',
'--allow-overwrite',
'--log-level=error',
'--log-limit=0',
'--sourcemap',
].join(' '), {
cwd: ROOT,
stdio: ['pipe', 'pipe', 'pipe'],
shell: true,
}).stderr?.toString() || ''
succeeded = true
break
} catch (e) {
esbuildOutput = (e.stderr?.toString() || '') + (e.stdout?.toString() || '')
}
// Parse missing modules
const missingRe = /Could not resolve "([^"]+)"/g
const missing = new Set()
let m
while ((m = missingRe.exec(esbuildOutput)) !== null) {
const mod = m[1]
if (!mod.startsWith('node:') && !mod.startsWith('bun:') && !mod.startsWith('/')) {
missing.add(mod)
}
}
if (missing.size === 0) {
// No more missing modules but still errors — check what
const errLines = esbuildOutput.split('\n').filter(l => l.includes('ERROR')).slice(0, 5)
console.log('❌ Unrecoverable errors:')
errLines.forEach(l => console.log(' ' + l))
break
}
console.log(` Found ${missing.size} missing modules, creating stubs...`)
// Create stubs
let stubCount = 0
for (const mod of missing) {
// Resolve relative path from the file that imports it — but since we
// don't have that info easily, create stubs at multiple likely locations
const cleanMod = mod.replace(/^\.\//, '')
// Text assets → empty file
if (/\.(txt|md|json)$/.test(cleanMod)) {
const p = join(BUILD, 'src', cleanMod)
await mkdir(dirname(p), { recursive: true }).catch(() => {})
if (!await exists(p)) {
await writeFile(p, cleanMod.endsWith('.json') ? '{}' : '', 'utf8')
stubCount++
}
continue
}
// JS/TS modules → export empty
if (/\.[tj]sx?$/.test(cleanMod)) {
for (const base of [join(BUILD, 'src'), join(BUILD, 'src', 'src')]) {
const p = join(base, cleanMod)
await mkdir(dirname(p), { recursive: true }).catch(() => {})
if (!await exists(p)) {
const name = cleanMod.split('/').pop().replace(/\.[tj]sx?$/, '')
const safeName = name.replace(/[^a-zA-Z0-9_$]/g, '_') || 'stub'
await writeFile(p, `// Auto-generated stub\nexport default function ${safeName}() {}\nexport const ${safeName} = () => {}\n`, 'utf8')
stubCount++
}
}
}
}
console.log(` Created ${stubCount} stubs`)
}
if (succeeded) {
const size = (await stat(OUT_FILE)).size
console.log(`\n✅ Build succeeded: ${OUT_FILE}`)
console.log(` Size: ${(size / 1024 / 1024).toFixed(1)}MB`)
console.log(`\n Usage: node ${OUT_FILE} --version`)
console.log(` node ${OUT_FILE} -p "Hello"`)
} else {
console.error('\n❌ Build failed after all rounds.')
console.error(' The transformed source is in build-src/ for inspection.')
console.error('\n To fix manually:')
console.error(' 1. Check build-src/ for the transformed files')
console.error(' 2. Create missing stubs in build-src/src/')
console.error(' 3. Re-run: node scripts/build.mjs')
process.exit(1)
}

115
scripts/prepare-src.mjs Normal file
View File

@@ -0,0 +1,115 @@
#!/usr/bin/env node
/**
* prepare-src.mjs — Pre-build source transformation
*
* This script patches the source tree to make it compilable without Bun:
* 1. Replace `import { feature } from 'bun:bundle'` with our stub
* 2. Replace `MACRO.X` references with runtime values
* 3. Create missing type declarations
*/
import fs from 'node:fs'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
const ROOT = path.resolve(__dirname, '..')
const SRC = path.join(ROOT, 'src')
const VERSION = '2.1.88'
// ── Helpers ──────────────────────────────────────────────────────────────────
function walk(dir, ext = '.ts') {
const results = []
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const full = path.join(dir, entry.name)
if (entry.isDirectory() && entry.name !== 'node_modules') {
results.push(...walk(full, ext))
} else if (entry.name.endsWith(ext) || entry.name.endsWith('.tsx')) {
results.push(full)
}
}
return results
}
function patchFile(filePath) {
let src = fs.readFileSync(filePath, 'utf8')
let changed = false
// 1. Replace `import { feature } from 'bun:bundle'` / `"bun:bundle"`
if (src.includes("from 'bun:bundle'") || src.includes('from "bun:bundle"')) {
src = src.replace(/import\s*\{\s*feature\s*\}\s*from\s*['"]bun:bundle['"]/g,
"import { feature } from '../stubs/bun-bundle.js'")
// Fix relative depth based on file location
const rel = path.relative(SRC, path.dirname(filePath))
const depth = rel ? '../'.repeat(rel.split('/').length) : ''
if (depth) {
src = src.replace("from '../stubs/bun-bundle.js'", `from '${depth}stubs/bun-bundle.js'`)
}
changed = true
}
// 2. Replace MACRO.X with string literals
const macroReplacements = {
'MACRO.VERSION': `'${VERSION}'`,
'MACRO.BUILD_TIME': `'${new Date().toISOString()}'`,
'MACRO.FEEDBACK_CHANNEL': `'https://github.com/anthropics/claude-code/issues'`,
'MACRO.ISSUES_EXPLAINER': `'https://github.com/anthropics/claude-code/issues/new/choose'`,
'MACRO.NATIVE_PACKAGE_URL': `'@anthropic-ai/claude-code'`,
'MACRO.PACKAGE_URL': `'@anthropic-ai/claude-code'`,
'MACRO.VERSION_CHANGELOG': `''`,
}
for (const [macro, replacement] of Object.entries(macroReplacements)) {
if (src.includes(macro)) {
// Don't replace inside strings
src = src.replace(new RegExp(`(?<![\\w'"])${macro.replace('.', '\\.')}(?![\\w'" ])`, 'g'), replacement)
changed = true
}
}
if (changed) {
fs.writeFileSync(filePath, src, 'utf8')
return true
}
return false
}
// ── Main ─────────────────────────────────────────────────────────────────────
console.log('🔧 Preparing source files...\n')
const files = walk(SRC)
let patched = 0
for (const file of files) {
if (patchFile(file)) {
patched++
console.log(` patched: ${path.relative(ROOT, file)}`)
}
}
// Create stub for bun:ffi (only used in upstreamproxy)
const ffiStub = path.join(ROOT, 'stubs', 'bun-ffi.ts')
if (!fs.existsSync(ffiStub)) {
fs.writeFileSync(ffiStub, `// Stub for bun:ffi — not available outside Bun runtime\nexport const ffi = {} as any\nexport function dlopen() { return {} }\n`)
console.log(' created: stubs/bun-ffi.ts')
}
// Create global MACRO type declaration
const macroDecl = path.join(ROOT, 'stubs', 'global.d.ts')
fs.writeFileSync(macroDecl, `// Global compile-time macros (normally injected by Bun bundler)
declare const MACRO: {
VERSION: string
BUILD_TIME: string
FEEDBACK_CHANNEL: string
ISSUES_EXPLAINER: string
NATIVE_PACKAGE_URL: string
PACKAGE_URL: string
VERSION_CHANGELOG: string
}
`)
console.log(' created: stubs/global.d.ts')
console.log(`\n✅ Patched ${patched} / ${files.length} source files`)

158
scripts/stub-modules.mjs Normal file
View File

@@ -0,0 +1,158 @@
#!/usr/bin/env node
/**
* stub-modules.mjs — Create stub files for all missing feature-gated modules
*
* Run: node scripts/stub-modules.mjs
* Then: npx esbuild build-src/entry.ts --bundle --platform=node --packages=external ...
*
* Reads esbuild errors, resolves each relative import to its correct absolute
* path inside build-src/src/, and creates an empty stub.
*/
import { readFile, writeFile, mkdir, stat } from 'node:fs/promises'
import { join, dirname, resolve } from 'node:path'
import { execSync } from 'node:child_process'
const ROOT = join(import.meta.dirname, '..')
const BUILD_SRC = join(ROOT, 'build-src', 'src')
async function exists(p) { try { await stat(p); return true } catch { return false } }
// Parse all missing modules from esbuild output
const out = execSync(
`npx esbuild "${join(ROOT, 'build-src', 'entry.ts')}" ` +
`--bundle --platform=node --packages=external ` +
`--external:'bun:*' --log-level=error --log-limit=0 ` +
`--outfile=/dev/null 2>&1 || true`,
{ cwd: ROOT, shell: true, encoding: 'utf8', maxBuffer: 50 * 1024 * 1024 }
)
const missingRe = /Could not resolve "([^"]+)"/g
const errors = [...out.matchAll(/\s+(\S+:\d+:\d+):\s/g)].map(m => m[1])
const moduleFiles = new Map() // module → set of importing files
let match
while ((match = missingRe.exec(out)) !== null) {
const mod = match[1]
if (mod.startsWith('node:') || mod.startsWith('bun:') || mod.startsWith('/')) continue
moduleFiles.set(mod, new Set())
}
// Now resolve each relative module path to its absolute path
// by finding which source file imports it
const importRe = /(\S+:\d+:\d+):\s*\d+.*require\(["']([^"']+)["']\)|import.*from\s*["']([^"']+)["']/g
let stubCount = 0
const created = new Set()
for (const [mod] of moduleFiles) {
// For relative imports, we need to find the importing file to resolve the path
// Search for the import in the build-src
const escapedMod = mod.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
const grepResult = execSync(
`grep -rl "${escapedMod}" "${BUILD_SRC}" 2>/dev/null || true`,
{ encoding: 'utf8', maxBuffer: 10 * 1024 * 1024, shell: true }
).trim()
const importers = grepResult.split('\n').filter(Boolean)
for (const importer of importers) {
const importerDir = dirname(importer)
const absPath = resolve(importerDir, mod)
// Check if it's a .d.ts type file — just create empty
if (mod.endsWith('.d.ts')) {
if (!created.has(absPath)) {
await mkdir(dirname(absPath), { recursive: true }).catch(() => {})
if (!await exists(absPath)) {
await writeFile(absPath, '// Type stub\nexport {}\n', 'utf8')
stubCount++
created.add(absPath)
}
}
continue
}
// Text assets (.txt, .md)
if (/\.(txt|md)$/.test(mod)) {
if (!created.has(absPath)) {
await mkdir(dirname(absPath), { recursive: true }).catch(() => {})
if (!await exists(absPath)) {
await writeFile(absPath, '', 'utf8')
stubCount++
created.add(absPath)
}
}
continue
}
// JS/TS modules
if (/\.[tj]sx?$/.test(mod)) {
if (!created.has(absPath)) {
await mkdir(dirname(absPath), { recursive: true }).catch(() => {})
if (!await exists(absPath)) {
const name = mod.split('/').pop().replace(/\.[tj]sx?$/, '')
const safeName = name.replace(/[^a-zA-Z0-9_$]/g, '_') || 'stub'
await writeFile(absPath, `// Auto-generated stub for feature-gated module: ${mod}\nexport default function ${safeName}() { return null }\nexport const ${safeName} = () => null\n`, 'utf8')
stubCount++
created.add(absPath)
}
}
}
}
// Also try resolving from src root for modules starting with ../
if (mod.startsWith('../')) {
// Try from several likely locations
for (const prefix of ['src', 'src/commands', 'src/components', 'src/services', 'src/tools', 'src/utils']) {
const absPath = join(ROOT, 'build-src', prefix, mod)
if (!created.has(absPath)) {
await mkdir(dirname(absPath), { recursive: true }).catch(() => {})
if (!await exists(absPath) && (/\.[tj]sx?$/.test(mod))) {
const name = mod.split('/').pop().replace(/\.[tj]sx?$/, '')
const safeName = name.replace(/[^a-zA-Z0-9_$]/g, '_') || 'stub'
await writeFile(absPath, `// Auto-generated stub for: ${mod}\nexport default function ${safeName}() { return null }\nexport const ${safeName} = () => null\n`, 'utf8')
stubCount++
created.add(absPath)
}
}
}
}
}
console.log(`✅ Created ${stubCount} stubs for ${moduleFiles.size} missing modules`)
// Now try the build
console.log('\n🔨 Attempting esbuild bundle...\n')
try {
const OUT = join(ROOT, 'dist', 'cli.js')
await mkdir(dirname(OUT), { recursive: true })
execSync([
'npx esbuild',
`"${join(ROOT, 'build-src', 'entry.ts')}"`,
'--bundle',
'--platform=node',
'--target=node18',
'--format=esm',
`--outfile="${OUT}"`,
'--packages=external',
'--external:bun:*',
'--banner:js=$\'#!/usr/bin/env node\\n// Claude Code v2.1.88 (built from source)\\n// Copyright (c) Anthropic PBC. All rights reserved.\\n\'',
'--allow-overwrite',
'--log-level=warning',
'--sourcemap',
].join(' '), {
cwd: ROOT,
stdio: 'inherit',
shell: true,
})
const size = (await stat(OUT)).size
console.log(`\n✅ Build succeeded: ${OUT}`)
console.log(` Size: ${(size / 1024 / 1024).toFixed(1)}MB`)
console.log(` Usage: node ${OUT} --version`)
} catch (e) {
console.error('\n❌ Build still has errors. Run again to iterate:')
console.error(' node scripts/stub-modules.mjs')
}

143
scripts/transform.mjs Normal file
View File

@@ -0,0 +1,143 @@
#!/usr/bin/env node
/**
* build.mjs — Build Claude Code from source using esbuild
*
* Strategy:
* 1. Copy src/ → build-src/ (working copy)
* 2. Transform all `from 'bun:bundle'` imports → `from './stubs/bun-bundle'`
* 3. Inject MACRO globals via esbuild --define (replaces MACRO.X at compile time)
* 4. Bundle with esbuild into a single cli.js
*/
import { readdir, readFile, writeFile, mkdir, cp, rm } from 'node:fs/promises'
import { join, relative, dirname } from 'node:path'
import { execSync } from 'node:child_process'
import { fileURLToPath } from 'node:url'
const __dirname = dirname(fileURLToPath(import.meta.url))
const ROOT = join(__dirname, '..')
const VERSION = '2.1.88'
// ── Step 1: Clean & Create build directory ─────────────────────────────────
const BUILD_DIR = join(ROOT, 'build-src')
await rm(BUILD_DIR, { recursive: true, force: true })
await mkdir(BUILD_DIR, { recursive: true })
// Copy src/ → build-src/
await cp(join(ROOT, 'src'), join(BUILD_DIR, 'src'), { recursive: true })
// Copy stubs/ → build-src/stubs/
await cp(join(ROOT, 'stubs'), join(BUILD_DIR, 'stubs'), { recursive: true })
console.log('✅ Copied source to build-src/')
// ── Step 2: Transform imports ──────────────────────────────────────────────
async function* walkFiles(dir) {
for (const entry of await readdir(dir, { withFileTypes: true })) {
const full = join(dir, entry.name)
if (entry.isDirectory()) yield* walkFiles(full)
else if (entry.name.endsWith('.ts') || entry.name.endsWith('.tsx')) yield full
}
}
let transformCount = 0
for await (const file of walkFiles(join(BUILD_DIR, 'src'))) {
let content = await readFile(file, 'utf8')
let modified = false
// Replace bun:bundle import with our stub
if (content.includes("from 'bun:bundle'") || content.includes('from "bun:bundle"')) {
const rel = relative(dirname(file), join(BUILD_DIR, 'stubs', 'bun-bundle.ts'))
const importPath = rel.startsWith('.') ? rel : './' + rel
content = content.replace(
/import\s*\{\s*feature\s*\}\s*from\s*['"]bun:bundle['"]/g,
`import { feature } from '${importPath.replace(/\.ts$/, '.js')}'`
)
modified = true
}
if (modified) {
await writeFile(file, content, 'utf8')
transformCount++
}
}
console.log(`✅ Transformed ${transformCount} files (bun:bundle → stub)`)
// ── Step 3: Create entrypoint wrapper ──────────────────────────────────────
const ENTRY = join(BUILD_DIR, 'entry.ts')
await writeFile(ENTRY, `
// MACRO globals — normally injected by Bun's --define at compile time
// We inject them here as globals so MACRO.X references resolve
const MACRO = {
VERSION: '${VERSION}',
BUILD_TIME: '',
FEEDBACK_CHANNEL: 'https://github.com/anthropics/claude-code/issues',
ISSUES_EXPLAINER: 'https://github.com/anthropics/claude-code/issues/new/choose',
FEEDBACK_CHANNEL_URL: 'https://github.com/anthropics/claude-code/issues',
ISSUES_EXPLAINER_URL: 'https://github.com/anthropics/claude-code/issues/new/choose',
NATIVE_PACKAGE_URL: '@anthropic-ai/claude-code',
PACKAGE_URL: '@anthropic-ai/claude-code',
VERSION_CHANGELOG: '',
}
// Make it global
globalThis.MACRO = MACRO
// Now load the real entrypoint
import './src/entrypoints/cli.tsx'
`)
console.log('✅ Created entry wrapper with MACRO injection')
// ── Step 4: esbuild bundle ─────────────────────────────────────────────────
const OUT_FILE = join(ROOT, 'dist', 'cli.js')
try {
// Check if esbuild is available
execSync('npx esbuild --version', { stdio: 'pipe' })
} catch {
console.log('\n📦 Installing esbuild...')
execSync('npm install --save-dev esbuild', { cwd: ROOT, stdio: 'inherit' })
}
console.log('\n🔨 Bundling with esbuild...')
try {
execSync(`npx esbuild \\
"${ENTRY}" \\
--bundle \\
--platform=node \\
--target=node18 \\
--format=esm \\
--outfile="${OUT_FILE}" \\
--banner:js='#!/usr/bin/env node' \\
--define:process.env.USER_TYPE='"external"' \\
--define:process.env.CLAUDE_CODE_VERSION='"${VERSION}"' \\
--external:bun:ffi \\
--external:bun:bundle \\
--allow-overwrite \\
--log-level=info \\
--sourcemap \\
${process.argv.includes('--minify') ? '--minify' : ''}`, {
cwd: ROOT,
stdio: 'inherit',
shell: true
})
} catch (e) {
console.error('\n❌ esbuild failed. This is expected — the source has complex Bun-specific patterns.')
console.error(' The source is primarily meant for reading/analysis, not recompilation.')
console.error('\n To proceed with fixing, you would need to:')
console.error(' 1. Install Bun runtime (bun.sh)')
console.error(' 2. Create a bun build script that uses Bun.defineMacro / feature() natively')
console.error(' 3. Or manually resolve each compile-time intrinsic')
process.exit(1)
}
console.log(`\n✅ Build complete: ${OUT_FILE}`)
console.log(` Run with: node ${OUT_FILE}`)