Files
codeaashu-claude-code/prompts/03-build-config.md
ashutoshpythoncs@gmail.com b564857c0b claude-code
2026-03-31 18:58:05 +05:30

5.1 KiB

Prompt 03: Create esbuild-Based Build System

Context

You are working in /workspaces/claude-code. This is the Claude Code CLI — a TypeScript/TSX terminal app using React + Ink. It was originally built using Bun's bundler with feature flags, but that build config wasn't included in the leak.

We need to create a build system that:

  1. Bundles the entire src/ tree into a runnable output
  2. Aliases bun:bundle → our shim at src/shims/bun-bundle.ts
  3. Injects the MACRO global (via src/shims/macro.ts preload)
  4. Handles TSX/JSX (React)
  5. Handles ESM .js extension imports (the code uses import from './foo.js' which maps to ./foo.ts)
  6. Produces output that can run under Bun (primary) or Node.js 20+ (secondary)

Existing Files

  • src/shims/bun-bundle.ts — runtime feature() function (created in Prompt 02)
  • src/shims/macro.ts — global MACRO object (created in Prompt 02)
  • src/shims/preload.ts — preload bootstrap (created in Prompt 02)
  • src/entrypoints/cli.tsx — main entrypoint
  • tsconfig.json — has "jsx": "react-jsx", "module": "ESNext", "moduleResolution": "bundler"

Task

Part A: Install esbuild

bun add -d esbuild

Part B: Create build script

Create scripts/build-bundle.ts (a Bun-runnable build script):

// scripts/build-bundle.ts
// Usage: bun scripts/build-bundle.ts [--watch] [--minify]

import * as esbuild from 'esbuild'
import { resolve } from 'path'

const ROOT = resolve(import.meta.dir, '..')
const watch = process.argv.includes('--watch')
const minify = process.argv.includes('--minify')

const buildOptions: esbuild.BuildOptions = {
  entryPoints: [resolve(ROOT, 'src/entrypoints/cli.tsx')],
  bundle: true,
  platform: 'node',
  target: 'node20',
  format: 'esm',
  outdir: resolve(ROOT, 'dist'),
  outExtension: { '.js': '.mjs' },
  
  // Inject the MACRO global before all other code
  inject: [resolve(ROOT, 'src/shims/macro.ts')],
  
  // Alias bun:bundle to our runtime shim
  alias: {
    'bun:bundle': resolve(ROOT, 'src/shims/bun-bundle.ts'),
  },
  
  // Don't bundle node built-ins or native packages
  external: [
    // Node built-ins
    'fs', 'path', 'os', 'crypto', 'child_process', 'http', 'https',
    'net', 'tls', 'url', 'util', 'stream', 'events', 'buffer',
    'querystring', 'readline', 'zlib', 'assert', 'tty', 'worker_threads',
    'perf_hooks', 'async_hooks', 'dns', 'dgram', 'cluster',
    'node:*',
    // Native addons that can't be bundled
    'fsevents',
  ],
  
  jsx: 'automatic',
  
  // Source maps for debugging
  sourcemap: true,
  
  minify,
  
  // Banner: shebang for CLI + preload the MACRO global
  banner: {
    js: '#!/usr/bin/env node\n',
  },
  
  // Handle the .js → .ts resolution that the codebase uses
  resolveExtensions: ['.tsx', '.ts', '.jsx', '.js', '.json'],
  
  logLevel: 'info',
}

async function main() {
  if (watch) {
    const ctx = await esbuild.context(buildOptions)
    await ctx.watch()
    console.log('Watching for changes...')
  } else {
    const result = await esbuild.build(buildOptions)
    if (result.errors.length > 0) {
      console.error('Build failed')
      process.exit(1)
    }
    console.log('Build complete → dist/')
  }
}

main().catch(err => {
  console.error(err)
  process.exit(1)
})

Important: This is a starting point. You will likely need to iterate on the externals list and alias configuration. The codebase has ~1,900 files — some imports may need special handling. When you run the build:

  1. Run it: bun scripts/build-bundle.ts
  2. Look at the errors
  3. Fix them (add externals, fix aliases, etc.)
  4. Repeat until it bundles successfully

Common issues you'll hit:

  • npm packages that use native modules → add to external
  • Dynamic require() calls behind process.env.USER_TYPE === 'ant' → these are Anthropic-internal, wrap them or stub them
  • Circular dependencies → esbuild handles these but may warn
  • Re-exports from barrel files → should work but watch for issues

Part C: Add npm scripts

Add these to package.json "scripts":

{
  "build": "bun scripts/build-bundle.ts",
  "build:watch": "bun scripts/build-bundle.ts --watch",
  "build:prod": "bun scripts/build-bundle.ts --minify"
}

Part D: Create dist output directory

Add dist/ to .gitignore (create one if it doesn't exist).

Part E: Iterate on build errors

Run the build and fix whatever comes up. The goal is a clean bun scripts/build-bundle.ts that produces dist/cli.mjs.

Strategy for unresolvable modules: If modules reference Anthropic-internal packages or Bun-specific APIs (like Bun.hash, Bun.file), create minimal stubs in src/shims/ that provide compatible fallbacks.

Part F: Test the output

After a successful build:

node dist/cli.mjs --version
# or
bun dist/cli.mjs --version

This should print the version. It will likely crash after that because no API key is configured — that's fine for now.

Verification

  1. bun scripts/build-bundle.ts completes without errors
  2. dist/cli.mjs exists
  3. bun dist/cli.mjs --version or node dist/cli.mjs --version prints a version string
  4. package.json has build, build:watch, build:prod scripts