refactor: require Bun globally, add auto-install, remove Windows executable approach (#243)

- Delete BinaryManager.ts - no longer needed
- Simplify ProcessManager.ts - single Bun spawn path for all platforms
- Update smart-install.js - auto-install Bun if missing (Windows/Unix)
- Update documentation to reflect Bun requirement

This simplifies the codebase by:
- Using Bun consistently across all platforms (hooks + worker)
- Eliminating binary download/hosting complexity
- Zero native dependencies with bun:sqlite
- Auto-installing Bun on first run if not present

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Alex Newman
2025-12-11 17:27:18 -05:00
committed by GitHub
parent 50c7603a37
commit d24d5dda04
6 changed files with 189 additions and 177 deletions
-76
View File
@@ -1,76 +0,0 @@
import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
import { join } from 'path';
import { DATA_DIR } from '../../shared/paths.js';
const BIN_DIR = join(DATA_DIR, 'bin');
const VERSION_FILE = join(BIN_DIR, 'version.txt');
const GITHUB_RELEASES = 'https://github.com/thedotmack/claude-mem/releases/download';
export class BinaryManager {
static async getExecutablePath(): Promise<string> {
if (process.platform !== 'win32') {
throw new Error('BinaryManager only used on Windows');
}
const version = this.getCurrentVersion();
const binaryPath = join(BIN_DIR, 'worker-service.exe');
// Check if we have correct version
if (existsSync(binaryPath)) {
const installed = this.getInstalledVersion();
if (installed === version) return binaryPath;
}
// Download
await this.downloadBinary(version);
return binaryPath;
}
private static async downloadBinary(version: string): Promise<void> {
const url = `${GITHUB_RELEASES}/v${version}/worker-service-v${version}-win-x64.exe`;
console.log(`Downloading worker binary v${version}...`);
const response = await fetch(url);
if (!response.ok) {
throw new Error(
`Download failed: ${response.status}\n` +
`URL: ${url}\n` +
`Make sure the release exists with a Windows binary attached.`
);
}
const buffer = await response.arrayBuffer();
mkdirSync(BIN_DIR, { recursive: true });
const binaryPath = join(BIN_DIR, 'worker-service.exe');
writeFileSync(binaryPath, Buffer.from(buffer));
// Write version file
writeFileSync(VERSION_FILE, version);
console.log('Download complete');
}
private static getCurrentVersion(): string {
// Read from package.json in the installed plugin location
// This ensures we get the correct version even when running from marketplace
try {
const packageJson = JSON.parse(
readFileSync(join(DATA_DIR, '..', '.claude', 'plugins', 'marketplaces', 'thedotmack', 'package.json'), 'utf-8')
);
return packageJson.version;
} catch {
// Fallback to environment variable
return process.env.npm_package_version || 'unknown';
}
}
private static getInstalledVersion(): string | null {
try {
if (!existsSync(VERSION_FILE)) return null;
return readFileSync(VERSION_FILE, 'utf-8').trim();
} catch {
return null;
}
}
}
+8 -50
View File
@@ -43,21 +43,21 @@ export class ProcessManager {
const logFile = this.getLogFilePath();
// Platform-specific spawn
if (process.platform === 'win32') {
return this.startWindows(port);
} else {
return this.startUnix(workerScript, logFile, port);
}
// Use Bun on all platforms
return this.startWithBun(workerScript, logFile, port);
}
private static async startUnix(script: string, logFile: string, port: number): Promise<{ success: boolean; pid?: number; error?: string }> {
private static async startWithBun(script: string, logFile: string, port: number): Promise<{ success: boolean; pid?: number; error?: string }> {
try {
const isWindows = process.platform === 'win32';
const child = spawn('bun', [script], {
detached: true,
stdio: ['ignore', 'pipe', 'pipe'],
env: { ...process.env, CLAUDE_MEM_WORKER_PORT: String(port) },
cwd: MARKETPLACE_ROOT
cwd: MARKETPLACE_ROOT,
// Hide console window on Windows
...(isWindows && { windowsHide: true })
});
// Write logs
@@ -89,48 +89,6 @@ export class ProcessManager {
}
}
private static async startWindows(port: number): Promise<{ success: boolean; pid?: number; error?: string }> {
// Windows: Use compiled exe from ~/.claude-mem/bin/
// Import dynamically to avoid loading on non-Windows platforms
const { BinaryManager } = await import('./BinaryManager.js');
try {
const exePath = await BinaryManager.getExecutablePath();
const child = spawn(exePath, [], {
detached: true,
stdio: ['ignore', 'pipe', 'pipe'],
env: { ...process.env, CLAUDE_MEM_WORKER_PORT: String(port) },
windowsHide: true
});
const logFile = this.getLogFilePath();
const logStream = createWriteStream(logFile, { flags: 'a' });
child.stdout?.pipe(logStream);
child.stderr?.pipe(logStream);
child.unref();
if (!child.pid) {
return { success: false, error: 'Failed to get PID from spawned process' };
}
this.writePidFile({
pid: child.pid,
port,
startedAt: new Date().toISOString(),
version: process.env.npm_package_version || 'unknown'
});
return this.waitForHealth(child.pid, port);
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : String(error)
};
}
}
static async stop(timeout: number = PROCESS_STOP_TIMEOUT_MS): Promise<boolean> {
const info = this.getPidInfo();
if (!info) return true;