Skip to content

Commit 526db2f

Browse files
committed
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
1 parent 552ae03 commit 526db2f

6 files changed

Lines changed: 728 additions & 1 deletion

File tree

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ dist
1212
dist-ssr
1313
*.local
1414

15+
# Tauri binaries (built executables)
16+
src-tauri/binaries/
17+
1518
# Editor directories and files
1619
.vscode/*
1720
!.vscode/extensions.json
@@ -27,4 +30,4 @@ temp_lib/
2730
.cursor/
2831
AGENTS.md
2932
CLAUDE.md
30-
*_TASK.md
33+
*_TASK.md

package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@
66
"scripts": {
77
"dev": "vite",
88
"build": "tsc && vite build",
9+
"build:executables": "bun run scripts/fetch-and-build.js",
10+
"build:executables:current": "bun run scripts/fetch-and-build.js current",
11+
"build:executables:linux": "bun run scripts/fetch-and-build.js linux",
12+
"build:executables:macos": "bun run scripts/fetch-and-build.js macos",
13+
"build:executables:windows": "bun run scripts/fetch-and-build.js windows",
914
"preview": "vite preview",
1015
"tauri": "tauri"
1116
},

scripts/README.md

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# Build Scripts
2+
3+
This directory contains scripts for building Claude Code executables for all supported platforms.
4+
5+
## Scripts
6+
7+
### `fetch-and-build.js`
8+
Main build script that:
9+
1. Downloads the `@anthropic-ai/claude-code` package from npm
10+
2. Extracts and copies required files (cli.js, yoga.wasm, vendor/)
11+
3. Builds executables for specified platforms
12+
4. Cleans up temporary files
13+
14+
**Usage:**
15+
```bash
16+
# Build for all platforms
17+
bun run scripts/fetch-and-build.js
18+
19+
# Build for specific platform
20+
bun run scripts/fetch-and-build.js linux
21+
bun run scripts/fetch-and-build.js macos
22+
bun run scripts/fetch-and-build.js windows
23+
bun run scripts/fetch-and-build.js current # Current platform only
24+
```
25+
26+
### `build-executables.js`
27+
Low-level script that builds executables from existing source files. This is called automatically by `fetch-and-build.js`.
28+
29+
### `prepare-bundle-native.js`
30+
Prepares the CLI source for bundling by embedding assets using Bun's native embedding features.
31+
32+
## NPM Scripts
33+
34+
The following npm scripts are available in `package.json`:
35+
36+
```bash
37+
# Build executables for all platforms
38+
npm run build:executables
39+
40+
# Build for specific platforms
41+
npm run build:executables:current
42+
npm run build:executables:linux
43+
npm run build:executables:macos
44+
npm run build:executables:windows
45+
```
46+
47+
## Output
48+
49+
All executables are created in the `src-tauri/binaries/` directory with the following naming convention:
50+
51+
### Linux Executables
52+
- `claude-code-linux-x64` - Standard Linux x64 (glibc)
53+
- `claude-code-linux-x64-modern` - Modern CPUs (AVX2+)
54+
- `claude-code-linux-x64-baseline` - Older CPUs (pre-2013)
55+
- `claude-code-linux-arm64` - ARM64 Linux
56+
- `claude-code-linux-x64-musl` - Alpine Linux (musl)
57+
- `claude-code-linux-x64-musl-modern` - Alpine + modern CPUs
58+
- `claude-code-linux-x64-musl-baseline` - Alpine + older CPUs
59+
- `claude-code-linux-arm64-musl` - ARM64 Alpine
60+
61+
### macOS Executables
62+
- `claude-code-macos-x64` - Intel Mac
63+
- `claude-code-macos-x64-modern` - Intel Mac (modern CPUs)
64+
- `claude-code-macos-x64-baseline` - Intel Mac (older CPUs)
65+
- `claude-code-macos-arm64` - Apple Silicon Mac
66+
67+
### Windows Executables
68+
- `claude-code-windows-x64.exe` - Windows x64
69+
- `claude-code-windows-x64-modern.exe` - Windows x64 (modern CPUs)
70+
- `claude-code-windows-x64-baseline.exe` - Windows x64 (older CPUs)
71+
72+
## Features
73+
74+
- **Embedded Assets**: All executables include embedded yoga.wasm and ripgrep binaries
75+
- **Optimizations**: Built with minification and sourcemaps
76+
- **Cross-platform**: Supports all major operating systems and architectures
77+
- **CPU Variants**: Modern variants for newer CPUs (2013+), baseline for compatibility
78+
- **Self-contained**: No external dependencies required at runtime
79+
80+
## Requirements
81+
82+
- **Bun**: Required for building (uses Bun's native compilation features)
83+
- **npm**: Used to download the Claude Code package
84+
- **tar**: For extracting the package (standard on Unix systems)

scripts/build-executables.js

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
#!/usr/bin/env bun
2+
3+
/**
4+
* Build script for creating single-file executables from Claude Code package
5+
* Uses Bun's native embedding features with all optimizations enabled
6+
*
7+
* Usage:
8+
* bun run build-executables.js # Build all platforms
9+
* bun run build-executables.js linux # Build Linux executables only
10+
* bun run build-executables.js macos # Build macOS executables only
11+
* bun run build-executables.js windows # Build Windows executables only
12+
* bun run build-executables.js current # Build for current platform only
13+
*/
14+
15+
import { spawn } from 'child_process';
16+
import { mkdir, rm } from 'fs/promises';
17+
import { existsSync } from 'fs';
18+
import { join } from 'path';
19+
20+
// All supported targets with proper output names
21+
const PLATFORMS = {
22+
linux: [
23+
// Linux x64 - glibc
24+
{ target: 'bun-linux-x64', output: 'claude-code-linux-x64' },
25+
{ target: 'bun-linux-x64-modern', output: 'claude-code-linux-x64-modern' },
26+
{ target: 'bun-linux-x64-baseline', output: 'claude-code-linux-x64-baseline' },
27+
28+
// Linux ARM64 - glibc
29+
{ target: 'bun-linux-arm64', output: 'claude-code-linux-arm64' },
30+
31+
// Linux x64 - musl (Alpine Linux, etc.)
32+
{ target: 'bun-linux-x64-musl', output: 'claude-code-linux-x64-musl' },
33+
{ target: 'bun-linux-x64-musl-modern', output: 'claude-code-linux-x64-musl-modern' },
34+
{ target: 'bun-linux-x64-musl-baseline', output: 'claude-code-linux-x64-musl-baseline' },
35+
36+
// Linux ARM64 - musl
37+
{ target: 'bun-linux-arm64-musl', output: 'claude-code-linux-arm64-musl' }
38+
],
39+
macos: [
40+
// macOS x64
41+
{ target: 'bun-darwin-x64', output: 'claude-code-macos-x64' },
42+
{ target: 'bun-darwin-x64-modern', output: 'claude-code-macos-x64-modern' },
43+
{ target: 'bun-darwin-x64-baseline', output: 'claude-code-macos-x64-baseline' },
44+
45+
// macOS ARM64 (Apple Silicon)
46+
{ target: 'bun-darwin-arm64', output: 'claude-code-macos-arm64' }
47+
],
48+
windows: [
49+
// Windows x64
50+
{ target: 'bun-windows-x64', output: 'claude-code-windows-x64.exe' },
51+
{ target: 'bun-windows-x64-modern', output: 'claude-code-windows-x64-modern.exe' },
52+
{ target: 'bun-windows-x64-baseline', output: 'claude-code-windows-x64-baseline.exe' }
53+
]
54+
};
55+
56+
async function runCommand(command, args) {
57+
return new Promise((resolve, reject) => {
58+
console.log(`Running: ${command} ${args.join(' ')}`);
59+
const child = spawn(command, args, { stdio: 'inherit' });
60+
61+
child.on('error', reject);
62+
child.on('exit', (code) => {
63+
if (code === 0) {
64+
resolve();
65+
} else {
66+
reject(new Error(`Command failed with exit code ${code}`));
67+
}
68+
});
69+
});
70+
}
71+
72+
async function prepareBundle() {
73+
console.log('\nPreparing bundle with native Bun embedding...');
74+
await runCommand('bun', ['run', 'scripts/prepare-bundle-native.js']);
75+
}
76+
77+
async function buildExecutable(target, output) {
78+
console.log(`\nBuilding ${output}...`);
79+
const startTime = Date.now();
80+
81+
try {
82+
await runCommand('bun', [
83+
'build',
84+
'--compile',
85+
'--minify', // Optimize size
86+
'--sourcemap', // Embed sourcemap for debugging
87+
// '--bytecode', // Commented out - experimental feature that often fails
88+
`--target=${target}`,
89+
'./cli-native-bundled.js',
90+
`--outfile=src-tauri/binaries/${output}`
91+
]);
92+
93+
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
94+
console.log(`✓ Built ${output} in ${elapsed}s`);
95+
} catch (error) {
96+
// If compilation fails, throw the error
97+
throw error;
98+
}
99+
}
100+
101+
async function cleanupBundledFile() {
102+
const filesToClean = ['./cli-bundled.js', './cli-native-bundled.js'];
103+
for (const file of filesToClean) {
104+
if (existsSync(file)) {
105+
await rm(file);
106+
}
107+
}
108+
console.log('\n✓ Cleaned up temporary files');
109+
}
110+
111+
async function getCurrentPlatform() {
112+
const arch = process.arch === 'x64' ? 'x64' : 'arm64';
113+
const platform = process.platform === 'darwin' ? 'darwin' :
114+
process.platform === 'linux' ? 'linux' :
115+
process.platform === 'win32' ? 'windows' : null;
116+
117+
if (!platform) {
118+
throw new Error(`Unsupported platform: ${process.platform}`);
119+
}
120+
121+
return `bun-${platform}-${arch}`;
122+
}
123+
124+
async function main() {
125+
const arg = process.argv[2];
126+
127+
// Create src-tauri/binaries directory if it doesn't exist
128+
if (!existsSync('src-tauri/binaries')) {
129+
await mkdir('src-tauri/binaries', { recursive: true });
130+
}
131+
132+
let platformsToBuild = [];
133+
134+
if (!arg || arg === 'all') {
135+
// Build all platforms
136+
platformsToBuild = [
137+
...PLATFORMS.linux,
138+
...PLATFORMS.macos,
139+
...PLATFORMS.windows
140+
];
141+
} else if (arg === 'linux') {
142+
platformsToBuild = PLATFORMS.linux;
143+
} else if (arg === 'macos' || arg === 'darwin') {
144+
platformsToBuild = PLATFORMS.macos;
145+
} else if (arg === 'windows' || arg === 'win32') {
146+
platformsToBuild = PLATFORMS.windows;
147+
} else if (arg === 'current') {
148+
// Build only for current platform
149+
const currentTarget = await getCurrentPlatform();
150+
const allPlatforms = [
151+
...PLATFORMS.linux,
152+
...PLATFORMS.macos,
153+
...PLATFORMS.windows
154+
];
155+
const current = allPlatforms.find(p => p.target === currentTarget);
156+
if (current) {
157+
platformsToBuild = [current];
158+
} else {
159+
console.error(`Current platform ${currentTarget} not found in build targets`);
160+
process.exit(1);
161+
}
162+
} else {
163+
console.error(`Unknown argument: ${arg}`);
164+
console.error('Usage: bun run build-executables.js [all|linux|macos|windows|current]');
165+
process.exit(1);
166+
}
167+
168+
console.log(`Building ${platformsToBuild.length} executable(s) with full optimizations...`);
169+
console.log('Optimizations enabled: --minify --sourcemap');
170+
const startTime = Date.now();
171+
172+
try {
173+
// Prepare the bundle once with native embedding
174+
await prepareBundle();
175+
176+
// Build executables sequentially to avoid resource conflicts
177+
let successCount = 0;
178+
for (const platform of platformsToBuild) {
179+
try {
180+
await buildExecutable(platform.target, platform.output);
181+
successCount++;
182+
} catch (error) {
183+
console.error(`Failed to build ${platform.output}:`, error.message);
184+
}
185+
}
186+
187+
const totalElapsed = ((Date.now() - startTime) / 1000).toFixed(1);
188+
console.log(`\n✅ Successfully built ${successCount}/${platformsToBuild.length} executables in ${totalElapsed}s`);
189+
console.log('\nExecutables are available in the src-tauri/binaries/ directory');
190+
console.log('\nNotes:');
191+
console.log('- All executables include embedded assets (yoga.wasm, ripgrep binaries)');
192+
console.log('- Modern variants require CPUs from 2013+ (AVX2 support)');
193+
console.log('- Baseline variants support older CPUs (pre-2013)');
194+
console.log('- Musl variants are for Alpine Linux and similar distributions');
195+
console.log('- All executables are optimized with minification and sourcemaps');
196+
} finally {
197+
// Clean up temporary files
198+
await cleanupBundledFile();
199+
}
200+
}
201+
202+
main().catch(error => {
203+
console.error('Build failed:', error);
204+
process.exit(1);
205+
});

0 commit comments

Comments
 (0)