mirror of
https://github.com/getAsterisk/claudia.git
synced 2025-08-04 07:18:19 +00:00

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
285 lines
7.9 KiB
JavaScript
Executable file
285 lines
7.9 KiB
JavaScript
Executable file
#!/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<void>}
|
|
*/
|
|
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<boolean>}
|
|
*/
|
|
async function pathExists(path) {
|
|
try {
|
|
await access(path);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Download and extract the Claude Code package from npm
|
|
* @returns {Promise<string>} - 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);
|
|
});
|