From 526db2f92546395d148aab09b109bd1777cf3c3e Mon Sep 17 00:00:00 2001 From: Vivek R <123vivekr@gmail.com> Date: Thu, 3 Jul 2025 20:11:10 +0530 Subject: [PATCH] feat(build): add comprehensive Claude Code executable build system Add complete build infrastructure for creating single-file Claude Code executables across all supported platforms using Bun's native compilation features. - **Cross-platform builds**: Support for Linux (glibc/musl), macOS (Intel/ARM), Windows - **CPU optimization variants**: Modern (AVX2+) and baseline (pre-2013) CPU support - **Embedded assets**: All executables include yoga.wasm and ripgrep binaries - **Optimized builds**: Minification and sourcemaps for production-ready binaries --- .gitignore | 5 +- package.json | 5 + scripts/README.md | 84 +++++++++ scripts/build-executables.js | 205 ++++++++++++++++++++++ scripts/fetch-and-build.js | 285 +++++++++++++++++++++++++++++++ scripts/prepare-bundle-native.js | 145 ++++++++++++++++ 6 files changed, 728 insertions(+), 1 deletion(-) create mode 100644 scripts/README.md create mode 100755 scripts/build-executables.js create mode 100755 scripts/fetch-and-build.js create mode 100644 scripts/prepare-bundle-native.js diff --git a/.gitignore b/.gitignore index 02de0b6..6021567 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,9 @@ dist dist-ssr *.local +# Tauri binaries (built executables) +src-tauri/binaries/ + # Editor directories and files .vscode/* !.vscode/extensions.json @@ -27,4 +30,4 @@ temp_lib/ .cursor/ AGENTS.md CLAUDE.md -*_TASK.md \ No newline at end of file +*_TASK.md diff --git a/package.json b/package.json index f7db9e5..e23d3b8 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,11 @@ "scripts": { "dev": "vite", "build": "tsc && vite build", + "build:executables": "bun run scripts/fetch-and-build.js", + "build:executables:current": "bun run scripts/fetch-and-build.js current", + "build:executables:linux": "bun run scripts/fetch-and-build.js linux", + "build:executables:macos": "bun run scripts/fetch-and-build.js macos", + "build:executables:windows": "bun run scripts/fetch-and-build.js windows", "preview": "vite preview", "tauri": "tauri" }, diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 0000000..6dbc30a --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,84 @@ +# Build Scripts + +This directory contains scripts for building Claude Code executables for all supported platforms. + +## Scripts + +### `fetch-and-build.js` +Main build script that: +1. Downloads the `@anthropic-ai/claude-code` package from npm +2. Extracts and copies required files (cli.js, yoga.wasm, vendor/) +3. Builds executables for specified platforms +4. Cleans up temporary files + +**Usage:** +```bash +# Build for all platforms +bun run scripts/fetch-and-build.js + +# Build for specific platform +bun run scripts/fetch-and-build.js linux +bun run scripts/fetch-and-build.js macos +bun run scripts/fetch-and-build.js windows +bun run scripts/fetch-and-build.js current # Current platform only +``` + +### `build-executables.js` +Low-level script that builds executables from existing source files. This is called automatically by `fetch-and-build.js`. + +### `prepare-bundle-native.js` +Prepares the CLI source for bundling by embedding assets using Bun's native embedding features. + +## NPM Scripts + +The following npm scripts are available in `package.json`: + +```bash +# Build executables for all platforms +npm run build:executables + +# Build for specific platforms +npm run build:executables:current +npm run build:executables:linux +npm run build:executables:macos +npm run build:executables:windows +``` + +## Output + +All executables are created in the `src-tauri/binaries/` directory with the following naming convention: + +### Linux Executables +- `claude-code-linux-x64` - Standard Linux x64 (glibc) +- `claude-code-linux-x64-modern` - Modern CPUs (AVX2+) +- `claude-code-linux-x64-baseline` - Older CPUs (pre-2013) +- `claude-code-linux-arm64` - ARM64 Linux +- `claude-code-linux-x64-musl` - Alpine Linux (musl) +- `claude-code-linux-x64-musl-modern` - Alpine + modern CPUs +- `claude-code-linux-x64-musl-baseline` - Alpine + older CPUs +- `claude-code-linux-arm64-musl` - ARM64 Alpine + +### macOS Executables +- `claude-code-macos-x64` - Intel Mac +- `claude-code-macos-x64-modern` - Intel Mac (modern CPUs) +- `claude-code-macos-x64-baseline` - Intel Mac (older CPUs) +- `claude-code-macos-arm64` - Apple Silicon Mac + +### Windows Executables +- `claude-code-windows-x64.exe` - Windows x64 +- `claude-code-windows-x64-modern.exe` - Windows x64 (modern CPUs) +- `claude-code-windows-x64-baseline.exe` - Windows x64 (older CPUs) + +## Features + +- **Embedded Assets**: All executables include embedded yoga.wasm and ripgrep binaries +- **Optimizations**: Built with minification and sourcemaps +- **Cross-platform**: Supports all major operating systems and architectures +- **CPU Variants**: Modern variants for newer CPUs (2013+), baseline for compatibility +- **Self-contained**: No external dependencies required at runtime + +## Requirements + +- **Bun**: Required for building (uses Bun's native compilation features) +- **npm**: Used to download the Claude Code package +- **tar**: For extracting the package (standard on Unix systems) diff --git a/scripts/build-executables.js b/scripts/build-executables.js new file mode 100755 index 0000000..fda2c7b --- /dev/null +++ b/scripts/build-executables.js @@ -0,0 +1,205 @@ +#!/usr/bin/env bun + +/** + * Build script for creating single-file executables from Claude Code package + * Uses Bun's native embedding features with all optimizations enabled + * + * Usage: + * bun run build-executables.js # Build all platforms + * bun run build-executables.js linux # Build Linux executables only + * bun run build-executables.js macos # Build macOS executables only + * bun run build-executables.js windows # Build Windows executables only + * bun run build-executables.js current # Build for current platform only + */ + +import { spawn } from 'child_process'; +import { mkdir, rm } from 'fs/promises'; +import { existsSync } from 'fs'; +import { join } from 'path'; + +// All supported targets with proper output names +const PLATFORMS = { + linux: [ + // Linux x64 - glibc + { target: 'bun-linux-x64', output: 'claude-code-linux-x64' }, + { target: 'bun-linux-x64-modern', output: 'claude-code-linux-x64-modern' }, + { target: 'bun-linux-x64-baseline', output: 'claude-code-linux-x64-baseline' }, + + // Linux ARM64 - glibc + { target: 'bun-linux-arm64', output: 'claude-code-linux-arm64' }, + + // Linux x64 - musl (Alpine Linux, etc.) + { target: 'bun-linux-x64-musl', output: 'claude-code-linux-x64-musl' }, + { target: 'bun-linux-x64-musl-modern', output: 'claude-code-linux-x64-musl-modern' }, + { target: 'bun-linux-x64-musl-baseline', output: 'claude-code-linux-x64-musl-baseline' }, + + // Linux ARM64 - musl + { target: 'bun-linux-arm64-musl', output: 'claude-code-linux-arm64-musl' } + ], + macos: [ + // macOS x64 + { target: 'bun-darwin-x64', output: 'claude-code-macos-x64' }, + { target: 'bun-darwin-x64-modern', output: 'claude-code-macos-x64-modern' }, + { target: 'bun-darwin-x64-baseline', output: 'claude-code-macos-x64-baseline' }, + + // macOS ARM64 (Apple Silicon) + { target: 'bun-darwin-arm64', output: 'claude-code-macos-arm64' } + ], + windows: [ + // Windows x64 + { target: 'bun-windows-x64', output: 'claude-code-windows-x64.exe' }, + { target: 'bun-windows-x64-modern', output: 'claude-code-windows-x64-modern.exe' }, + { target: 'bun-windows-x64-baseline', output: 'claude-code-windows-x64-baseline.exe' } + ] +}; + +async function runCommand(command, args) { + return new Promise((resolve, reject) => { + console.log(`Running: ${command} ${args.join(' ')}`); + const child = spawn(command, args, { stdio: 'inherit' }); + + child.on('error', reject); + child.on('exit', (code) => { + if (code === 0) { + resolve(); + } else { + reject(new Error(`Command failed with exit code ${code}`)); + } + }); + }); +} + +async function prepareBundle() { + console.log('\nPreparing bundle with native Bun embedding...'); + await runCommand('bun', ['run', 'scripts/prepare-bundle-native.js']); +} + +async function buildExecutable(target, output) { + console.log(`\nBuilding ${output}...`); + const startTime = Date.now(); + + try { + await runCommand('bun', [ + 'build', + '--compile', + '--minify', // Optimize size + '--sourcemap', // Embed sourcemap for debugging + // '--bytecode', // Commented out - experimental feature that often fails + `--target=${target}`, + './cli-native-bundled.js', + `--outfile=src-tauri/binaries/${output}` + ]); + + const elapsed = ((Date.now() - startTime) / 1000).toFixed(1); + console.log(`โœ“ Built ${output} in ${elapsed}s`); + } catch (error) { + // If compilation fails, throw the error + throw error; + } +} + +async function cleanupBundledFile() { + const filesToClean = ['./cli-bundled.js', './cli-native-bundled.js']; + for (const file of filesToClean) { + if (existsSync(file)) { + await rm(file); + } + } + console.log('\nโœ“ Cleaned up temporary files'); +} + +async function getCurrentPlatform() { + const arch = process.arch === 'x64' ? 'x64' : 'arm64'; + const platform = process.platform === 'darwin' ? 'darwin' : + process.platform === 'linux' ? 'linux' : + process.platform === 'win32' ? 'windows' : null; + + if (!platform) { + throw new Error(`Unsupported platform: ${process.platform}`); + } + + return `bun-${platform}-${arch}`; +} + +async function main() { + const arg = process.argv[2]; + + // Create src-tauri/binaries directory if it doesn't exist + if (!existsSync('src-tauri/binaries')) { + await mkdir('src-tauri/binaries', { recursive: true }); + } + + let platformsToBuild = []; + + if (!arg || arg === 'all') { + // Build all platforms + platformsToBuild = [ + ...PLATFORMS.linux, + ...PLATFORMS.macos, + ...PLATFORMS.windows + ]; + } else if (arg === 'linux') { + platformsToBuild = PLATFORMS.linux; + } else if (arg === 'macos' || arg === 'darwin') { + platformsToBuild = PLATFORMS.macos; + } else if (arg === 'windows' || arg === 'win32') { + platformsToBuild = PLATFORMS.windows; + } else if (arg === 'current') { + // Build only for current platform + const currentTarget = await getCurrentPlatform(); + const allPlatforms = [ + ...PLATFORMS.linux, + ...PLATFORMS.macos, + ...PLATFORMS.windows + ]; + const current = allPlatforms.find(p => p.target === currentTarget); + if (current) { + platformsToBuild = [current]; + } else { + console.error(`Current platform ${currentTarget} not found in build targets`); + process.exit(1); + } + } else { + console.error(`Unknown argument: ${arg}`); + console.error('Usage: bun run build-executables.js [all|linux|macos|windows|current]'); + process.exit(1); + } + + console.log(`Building ${platformsToBuild.length} executable(s) with full optimizations...`); + console.log('Optimizations enabled: --minify --sourcemap'); + const startTime = Date.now(); + + try { + // Prepare the bundle once with native embedding + await prepareBundle(); + + // Build executables sequentially to avoid resource conflicts + let successCount = 0; + for (const platform of platformsToBuild) { + try { + await buildExecutable(platform.target, platform.output); + successCount++; + } catch (error) { + console.error(`Failed to build ${platform.output}:`, error.message); + } + } + + const totalElapsed = ((Date.now() - startTime) / 1000).toFixed(1); + console.log(`\nโœ… Successfully built ${successCount}/${platformsToBuild.length} executables in ${totalElapsed}s`); + console.log('\nExecutables are available in the src-tauri/binaries/ directory'); + console.log('\nNotes:'); + console.log('- All executables include embedded assets (yoga.wasm, ripgrep binaries)'); + console.log('- Modern variants require CPUs from 2013+ (AVX2 support)'); + console.log('- Baseline variants support older CPUs (pre-2013)'); + console.log('- Musl variants are for Alpine Linux and similar distributions'); + console.log('- All executables are optimized with minification and sourcemaps'); + } finally { + // Clean up temporary files + await cleanupBundledFile(); + } +} + +main().catch(error => { + console.error('Build failed:', error); + process.exit(1); +}); diff --git a/scripts/fetch-and-build.js b/scripts/fetch-and-build.js new file mode 100755 index 0000000..fc42fe2 --- /dev/null +++ b/scripts/fetch-and-build.js @@ -0,0 +1,285 @@ +#!/usr/bin/env bun + +/** + * Fetch Claude Code package from npm and build executables for all platforms + * + * This script: + * 1. Downloads the @anthropic-ai/claude-code package from npm + * 2. Extracts it to a temporary directory + * 3. Runs the build-executables script to create binaries for all platforms + * 4. Cleans up temporary files + * + * Usage: + * bun run fetch-and-build.js [platform] + * + * Where platform can be: all, linux, macos, windows, current + */ + +import { spawn } from 'child_process'; +import { mkdir, rm, readdir, copyFile, access } from 'fs/promises'; +import { existsSync } from 'fs'; +import { join, resolve } from 'path'; + +/** + * Execute a shell command and return a promise + * @param {string} command - The command to execute + * @param {string[]} args - Command arguments + * @param {object} options - Spawn options + * @returns {Promise} + */ +async function runCommand(command, args = [], options = {}) { + return new Promise((resolve, reject) => { + console.log(`Running: ${command} ${args.join(' ')}`); + const child = spawn(command, args, { + stdio: 'inherit', + shell: process.platform === 'win32', + ...options + }); + + child.on('error', (error) => { + console.error(`Failed to execute command: ${error.message}`); + reject(error); + }); + + child.on('exit', (code) => { + if (code === 0) { + resolve(); + } else { + reject(new Error(`Command failed with exit code ${code}`)); + } + }); + }); +} + +/** + * Check if a file or directory exists + * @param {string} path - Path to check + * @returns {Promise} + */ +async function pathExists(path) { + try { + await access(path); + return true; + } catch { + return false; + } +} + +/** + * Download and extract the Claude Code package from npm + * @returns {Promise} - Path to the extracted package directory + */ +async function fetchClaudeCodePackage() { + console.log('\n๐Ÿ“ฆ Fetching @anthropic-ai/claude-code package from npm...'); + + const tempDir = resolve('./temp-claude-package'); + const packageDir = join(tempDir, 'package'); + + try { + // Clean up any existing temp directory + if (await pathExists(tempDir)) { + console.log('Cleaning up existing temp directory...'); + await rm(tempDir, { recursive: true, force: true }); + } + + // Create temp directory + await mkdir(tempDir, { recursive: true }); + + // Download the package tarball + console.log('Downloading package tarball...'); + await runCommand('npm', ['pack', '@anthropic-ai/claude-code'], { + cwd: tempDir + }); + + // Find the downloaded tarball + const files = await readdir(tempDir); + const tarball = files.find(file => file.startsWith('anthropic-ai-claude-code-') && file.endsWith('.tgz')); + + if (!tarball) { + throw new Error('Failed to find downloaded tarball'); + } + + console.log(`Found tarball: ${tarball}`); + + // Extract the tarball + console.log('Extracting package...'); + await runCommand('tar', ['-xzf', tarball], { + cwd: tempDir + }); + + // Verify extraction + if (!(await pathExists(packageDir))) { + throw new Error('Package extraction failed - package directory not found'); + } + + console.log(`โœ“ Package extracted to: ${packageDir}`); + return packageDir; + + } catch (error) { + // Clean up on error + if (await pathExists(tempDir)) { + await rm(tempDir, { recursive: true, force: true }); + } + throw error; + } +} + +/** + * Copy required files from the Claude Code package to current directory + * @param {string} packageDir - Path to the extracted package directory + */ +async function copyRequiredFiles(packageDir) { + console.log('\n๐Ÿ“‹ Copying required files from Claude Code package...'); + + const filesToCopy = [ + 'cli.js', + 'yoga.wasm' + ]; + + const directoriesToCopy = [ + 'vendor' + ]; + + // Copy individual files + for (const file of filesToCopy) { + const srcPath = join(packageDir, file); + const destPath = resolve(file); + + if (await pathExists(srcPath)) { + console.log(`Copying ${file}...`); + await copyFile(srcPath, destPath); + } else { + console.warn(`Warning: ${file} not found in package`); + } + } + + // Copy directories recursively + for (const dir of directoriesToCopy) { + const srcPath = join(packageDir, dir); + const destPath = resolve(dir); + + if (await pathExists(srcPath)) { + console.log(`Copying ${dir}/ directory...`); + + // Remove existing directory if it exists + if (await pathExists(destPath)) { + await rm(destPath, { recursive: true, force: true }); + } + + // Copy directory recursively using cp command + await runCommand('cp', ['-r', srcPath, destPath]); + } else { + console.warn(`Warning: ${dir}/ directory not found in package`); + } + } + + console.log('โœ“ Required files copied successfully'); +} + +/** + * Clean up temporary files and directories + * @param {string} packageDir - Path to the package directory to clean up + */ +async function cleanup(packageDir) { + console.log('\n๐Ÿงน Cleaning up temporary files...'); + + const tempDir = resolve('./temp-claude-package'); + + try { + if (await pathExists(tempDir)) { + await rm(tempDir, { recursive: true, force: true }); + console.log('โœ“ Temporary package directory cleaned up'); + } + + // Clean up copied files that are no longer needed + const filesToCleanup = [ + './cli.js', + './cli-bundled.js', + './cli-native-bundled.js', + './yoga.wasm' + ]; + + for (const file of filesToCleanup) { + if (await pathExists(file)) { + await rm(file); + } + } + + // Clean up vendor directory + const vendorDir = './vendor'; + if (await pathExists(vendorDir)) { + await rm(vendorDir, { recursive: true, force: true }); + } + + console.log('โœ“ Cleanup completed'); + } catch (error) { + console.warn(`Warning: Cleanup failed: ${error.message}`); + } +} + +/** + * Build executables for the specified platform(s) + * @param {string} platform - Platform to build for (all, linux, macos, windows, current) + */ +async function buildExecutables(platform = 'all') { + console.log(`\n๐Ÿ”จ Building executables for platform: ${platform}`); + + // Ensure src-tauri/binaries directory exists + if (!await pathExists('./src-tauri/binaries')) { + await mkdir('./src-tauri/binaries', { recursive: true }); + } + + // Run the build-executables script + const args = platform === 'all' ? [] : [platform]; + await runCommand('bun', ['run', './scripts/build-executables.js', ...args]); +} + +/** + * Main execution function + */ +async function main() { + const platform = process.argv[2] || 'all'; + const validPlatforms = ['all', 'linux', 'macos', 'darwin', 'windows', 'win32', 'current']; + + if (!validPlatforms.includes(platform)) { + console.error(`Invalid platform: ${platform}`); + console.error(`Valid platforms: ${validPlatforms.join(', ')}`); + process.exit(1); + } + + console.log('๐Ÿš€ Starting Claude Code fetch and build process...'); + console.log(`Target platform: ${platform}`); + + const startTime = Date.now(); + let packageDir; + + try { + // Step 1: Fetch and extract the package + packageDir = await fetchClaudeCodePackage(); + + // Step 2: Copy required files + await copyRequiredFiles(packageDir); + + // Step 3: Build executables + await buildExecutables(platform); + + const totalTime = ((Date.now() - startTime) / 1000).toFixed(1); + console.log(`\nโœ… Build process completed successfully in ${totalTime}s`); + console.log('\n๐Ÿ“ Executables are available in the src-tauri/binaries/ directory'); + + } catch (error) { + console.error(`\nโŒ Build process failed: ${error.message}`); + process.exit(1); + } finally { + // Always clean up, even if there was an error + if (packageDir) { + await cleanup(packageDir); + } + } +} + +// Run the main function +main().catch(error => { + console.error('Unexpected error:', error); + process.exit(1); +}); diff --git a/scripts/prepare-bundle-native.js b/scripts/prepare-bundle-native.js new file mode 100644 index 0000000..2e8529d --- /dev/null +++ b/scripts/prepare-bundle-native.js @@ -0,0 +1,145 @@ +#!/usr/bin/env bun + +/** + * Prepare the CLI for bundling using Bun's native embedding features + * This modifies the source to use embedded files directly + */ + +import { readFileSync, writeFileSync, existsSync } from 'fs'; +import { join } from 'path'; + +// Read the original CLI file +const cliPath = './cli.js'; +let cliContent = readFileSync(cliPath, 'utf-8'); + +console.log('Preparing CLI for native Bun embedding...'); + +// 1. Build list of embedded imports based on what files actually exist +const embeddedImports = []; +const embeddedFilesMapping = []; + +// Define all possible ripgrep files +const ripgrepFiles = [ + { path: './vendor/ripgrep/arm64-darwin/rg', var: '__embeddedRgDarwinArm64' }, + { path: './vendor/ripgrep/arm64-darwin/ripgrep.node', var: '__embeddedRgNodeDarwinArm64' }, + { path: './vendor/ripgrep/arm64-linux/rg', var: '__embeddedRgLinuxArm64' }, + { path: './vendor/ripgrep/arm64-linux/ripgrep.node', var: '__embeddedRgNodeLinuxArm64' }, + { path: './vendor/ripgrep/x64-darwin/rg', var: '__embeddedRgDarwinX64' }, + { path: './vendor/ripgrep/x64-darwin/ripgrep.node', var: '__embeddedRgNodeDarwinX64' }, + { path: './vendor/ripgrep/x64-linux/rg', var: '__embeddedRgLinuxX64' }, + { path: './vendor/ripgrep/x64-linux/ripgrep.node', var: '__embeddedRgNodeLinuxX64' }, + { path: './vendor/ripgrep/x64-win32/rg.exe', var: '__embeddedRgWin32' }, + { path: './vendor/ripgrep/x64-win32/ripgrep.node', var: '__embeddedRgNodeWin32' }, +]; + +// Always include yoga.wasm +if (existsSync('./yoga.wasm')) { + embeddedImports.push('import __embeddedYogaWasm from "./yoga.wasm" with { type: "file" };'); + embeddedFilesMapping.push(" 'yoga.wasm': __embeddedYogaWasm,"); +} else { + console.error('Warning: yoga.wasm not found'); +} + +// Only import ripgrep files that exist +for (const file of ripgrepFiles) { + if (existsSync(file.path)) { + embeddedImports.push(`import ${file.var} from "${file.path}" with { type: "file" };`); + const key = file.path.replace('./', ''); + embeddedFilesMapping.push(` '${key}': ${file.var},`); + } +} + +const embeddedCode = ` +// Embedded files using Bun's native embedding +${embeddedImports.join('\n')} + +const __embeddedFiles = { +${embeddedFilesMapping.join('\n')} +}; + +`; + +// Add imports after the shebang +const shebangMatch = cliContent.match(/^#!.*\n/); +if (shebangMatch) { + cliContent = shebangMatch[0] + embeddedCode + cliContent.substring(shebangMatch[0].length); +} else { + cliContent = embeddedCode + cliContent; +} + +// 2. Replace yoga.wasm loading - handle top-level await properly +// Original: var k81=await nUA(await VP9(CP9(import.meta.url).resolve("./yoga.wasm"))); +// Since this uses top-level await, we need to preserve that structure +const yogaLoadPattern = /var k81=await nUA\(await VP9\(CP9\(import\.meta\.url\)\.resolve\("\.\/yoga\.wasm"\)\)\);/; +// Use an IIFE to handle the async loading +const yogaLoadReplacement = `var k81=await(async()=>{return await nUA(await Bun.file(__embeddedYogaWasm).arrayBuffer())})();`; + +if (yogaLoadPattern.test(cliContent)) { + cliContent = cliContent.replace(yogaLoadPattern, yogaLoadReplacement); + console.log('โœ“ Replaced yoga.wasm loading with embedded version'); +} else { + console.error('Warning: Could not find yoga.wasm loading pattern'); + // Try a more general pattern + const generalYogaPattern = /var\s+(\w+)\s*=\s*await\s+nUA\s*\(\s*await\s+VP9\s*\([^)]+\.resolve\s*\(\s*["']\.\/yoga\.wasm["']\s*\)\s*\)\s*\)/; + if (generalYogaPattern.test(cliContent)) { + cliContent = cliContent.replace(generalYogaPattern, (match, varName) => { + return `var ${varName}=await(async()=>{return await nUA(await Bun.file(__embeddedYogaWasm).arrayBuffer())})()`; + }); + console.log('โœ“ Replaced yoga.wasm loading with embedded version (general pattern)'); + } +} + +// 3. Replace ripgrep path resolution +// Add check for embedded files in the ripgrep resolver +const ripgrepPattern = /let B=Db\.resolve\(et9,"vendor","ripgrep"\);/; +const ripgrepReplacement = ` +if(process.env.CLAUDE_CODE_BUNDLED || typeof __embeddedFiles !== 'undefined'){ + const platform = process.platform === "win32" ? "x64-win32" : \`\${process.arch}-\${process.platform}\`; + const rgKey = \`vendor/ripgrep/\${platform}/rg\${process.platform === "win32" ? ".exe" : ""}\`; + if(__embeddedFiles[rgKey]) return __embeddedFiles[rgKey]; +} +let B=Db.resolve(et9,"vendor","ripgrep");`; + +if (ripgrepPattern.test(cliContent)) { + cliContent = cliContent.replace(ripgrepPattern, ripgrepReplacement); + console.log('โœ“ Added embedded file handling for ripgrep'); +} + +// 4. Replace ripgrep.node loading - handle the entire if-else structure +// Look for the complete if-else pattern where B is assigned +const ripgrepNodePattern = /if\(typeof Bun!=="undefined"&&Bun\.embeddedFiles\?\.length>0\)B="\.\/ripgrep\.node";else/; +const ripgrepNodeReplacement = `if(typeof Bun!=="undefined"&&Bun.embeddedFiles?.length>0)B=(()=>{ + const platform = process.platform === "win32" ? "x64-win32" : \`\${process.arch}-\${process.platform}\`; + const nodeKey = \`vendor/ripgrep/\${platform}/ripgrep.node\`; + return __embeddedFiles[nodeKey] || "./ripgrep.node"; +})();else`; + +if (ripgrepNodePattern.test(cliContent)) { + cliContent = cliContent.replace(ripgrepNodePattern, ripgrepNodeReplacement); + console.log('โœ“ Added embedded file handling for ripgrep.node'); +} else { + // Fallback to simpler pattern if the exact pattern doesn't match + const simplePattern = /B="\.\/ripgrep\.node"/; + if (simplePattern.test(cliContent)) { + cliContent = cliContent.replace(simplePattern, `B=(()=>{ + const platform = process.platform === "win32" ? "x64-win32" : \`\${process.arch}-\${process.platform}\`; + const nodeKey = \`vendor/ripgrep/\${platform}/ripgrep.node\`; + return __embeddedFiles[nodeKey] || "./ripgrep.node"; + })()`); + console.log('โœ“ Added embedded file handling for ripgrep.node (fallback pattern)'); + } +} + +// Set bundled mode indicator +cliContent = cliContent.replace( + /process\.env\.CLAUDE_CODE_ENTRYPOINT="cli"/, + 'process.env.CLAUDE_CODE_ENTRYPOINT="cli";process.env.CLAUDE_CODE_BUNDLED="1"' +); + +// Write the modified content +const outputPath = './cli-native-bundled.js'; +writeFileSync(outputPath, cliContent); + +console.log(`\nโœ… Created ${outputPath} ready for bundling with native embedding`); +console.log('\nNow you can run:'); +console.log(` bun build --compile --minify ./cli-native-bundled.js --outfile dist/claude-code`);