Files
claude-mem/plugin/scripts/smart-install.js
Jonas Hanisch 2f08db3c01 fix: add npm fallback when bun install fails with alias packages (#265)
* fix: add npm fallback when bun install fails with alias packages

Bun has issues resolving npm alias packages (e.g., string-width-cjs,
strip-ansi-cjs, wrap-ansi-cjs) that are defined in package-lock.json.
When bun fails with 404 errors for these packages, we now fall back
to npm which handles aliases correctly.

This fixes the installation failure that many users are experiencing
where bun install fails with:
  error: GET https://registry.npmjs.org/string-width-cjs/-/string-width-cjs-4.2.3.tgz - 404

The fallback is transparent to users - they will see a warning message
and the installation will continue with npm.

Fixes #262
Related: #261, #253

🤖 Generated with Claude Code (https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: preserve original code style (single quotes)

---------

Co-authored-by: Jonas Hanisch <jhanisch@matero.de>
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-13 15:34:13 -05:00

358 lines
11 KiB
JavaScript

#!/usr/bin/env node
/**
* Smart Install Script for claude-mem
*
* Ensures Bun runtime and uv (Python package manager) are installed
* (auto-installs if missing) and handles dependency installation when needed.
*/
import { existsSync, readFileSync, writeFileSync } from 'fs';
import { execSync, spawnSync } from 'child_process';
import { join } from 'path';
import { homedir } from 'os';
const ROOT = join(homedir(), '.claude', 'plugins', 'marketplaces', 'thedotmack');
const MARKER = join(ROOT, '.install-version');
const IS_WINDOWS = process.platform === 'win32';
/**
* Check if Bun is installed and accessible
*/
function isBunInstalled() {
try {
const result = spawnSync('bun', ['--version'], {
encoding: 'utf-8',
stdio: ['pipe', 'pipe', 'pipe'],
shell: IS_WINDOWS
});
if (result.status === 0) return true;
} catch {
// PATH check failed, try common installation paths
}
// Check common installation paths (handles fresh installs before PATH reload)
const bunPaths = IS_WINDOWS
? [join(homedir(), '.bun', 'bin', 'bun.exe')]
: [join(homedir(), '.bun', 'bin', 'bun'), '/usr/local/bin/bun'];
return bunPaths.some(existsSync);
}
/**
* Get the Bun executable path (from PATH or common install locations)
*/
function getBunPath() {
// Try PATH first
try {
const result = spawnSync('bun', ['--version'], {
encoding: 'utf-8',
stdio: ['pipe', 'pipe', 'pipe'],
shell: IS_WINDOWS
});
if (result.status === 0) return 'bun';
} catch {
// Not in PATH
}
// Check common installation paths
const bunPaths = IS_WINDOWS
? [join(homedir(), '.bun', 'bin', 'bun.exe')]
: [join(homedir(), '.bun', 'bin', 'bun'), '/usr/local/bin/bun'];
for (const bunPath of bunPaths) {
if (existsSync(bunPath)) return bunPath;
}
return null;
}
/**
* Get Bun version if installed
*/
function getBunVersion() {
const bunPath = getBunPath();
if (!bunPath) return null;
try {
const result = spawnSync(bunPath, ['--version'], {
encoding: 'utf-8',
stdio: ['pipe', 'pipe', 'pipe'],
shell: IS_WINDOWS
});
return result.status === 0 ? result.stdout.trim() : null;
} catch {
return null;
}
}
/**
* Check if uv is installed and accessible
*/
function isUvInstalled() {
try {
const result = spawnSync('uv', ['--version'], {
encoding: 'utf-8',
stdio: ['pipe', 'pipe', 'pipe'],
shell: IS_WINDOWS
});
if (result.status === 0) return true;
} catch {
// PATH check failed, try common installation paths
}
// Check common installation paths (handles fresh installs before PATH reload)
const uvPaths = IS_WINDOWS
? [join(homedir(), '.local', 'bin', 'uv.exe'), join(homedir(), '.cargo', 'bin', 'uv.exe')]
: [join(homedir(), '.local', 'bin', 'uv'), join(homedir(), '.cargo', 'bin', 'uv'), '/usr/local/bin/uv'];
return uvPaths.some(existsSync);
}
/**
* Get uv version if installed
*/
function getUvVersion() {
try {
const result = spawnSync('uv', ['--version'], {
encoding: 'utf-8',
stdio: ['pipe', 'pipe', 'pipe'],
shell: IS_WINDOWS
});
return result.status === 0 ? result.stdout.trim() : null;
} catch {
return null;
}
}
/**
* Install Bun automatically based on platform
*/
function installBun() {
console.error('🔧 Bun not found. Installing Bun runtime...');
try {
if (IS_WINDOWS) {
// Windows: Use PowerShell installer
console.error(' Installing via PowerShell...');
execSync('powershell -c "irm bun.sh/install.ps1 | iex"', {
stdio: 'inherit',
shell: true
});
} else {
// Unix/macOS: Use curl installer
console.error(' Installing via curl...');
execSync('curl -fsSL https://bun.sh/install | bash', {
stdio: 'inherit',
shell: true
});
}
// Verify installation
if (isBunInstalled()) {
const version = getBunVersion();
console.error(`✅ Bun ${version} installed successfully`);
return true;
} else {
// Bun may be installed but not in PATH yet for this session
// Try common installation paths
const bunPaths = IS_WINDOWS
? [join(homedir(), '.bun', 'bin', 'bun.exe')]
: [join(homedir(), '.bun', 'bin', 'bun'), '/usr/local/bin/bun'];
for (const bunPath of bunPaths) {
if (existsSync(bunPath)) {
console.error(`✅ Bun installed at ${bunPath}`);
console.error('⚠️ Please restart your terminal or add Bun to PATH:');
if (IS_WINDOWS) {
console.error(` $env:Path += ";${join(homedir(), '.bun', 'bin')}"`);
} else {
console.error(` export PATH="$HOME/.bun/bin:$PATH"`);
}
return true;
}
}
throw new Error('Bun installation completed but binary not found');
}
} catch (error) {
console.error('❌ Failed to install Bun automatically');
console.error(' Please install manually:');
if (IS_WINDOWS) {
console.error(' - winget install Oven-sh.Bun');
console.error(' - Or: powershell -c "irm bun.sh/install.ps1 | iex"');
} else {
console.error(' - curl -fsSL https://bun.sh/install | bash');
console.error(' - Or: brew install oven-sh/bun/bun');
}
console.error(' Then restart your terminal and try again.');
throw error;
}
}
/**
* Install uv automatically based on platform
*/
function installUv() {
console.error('🐍 Installing uv for Python/Chroma support...');
try {
if (IS_WINDOWS) {
// Windows: Use PowerShell installer
console.error(' Installing via PowerShell...');
execSync('powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"', {
stdio: 'inherit',
shell: true
});
} else {
// Unix/macOS: Use curl installer
console.error(' Installing via curl...');
execSync('curl -LsSf https://astral.sh/uv/install.sh | sh', {
stdio: 'inherit',
shell: true
});
}
// Verify installation
if (isUvInstalled()) {
const version = getUvVersion();
console.error(`✅ uv ${version} installed successfully`);
return true;
} else {
// uv may be installed but not in PATH yet for this session
// Try common installation paths
const uvPaths = IS_WINDOWS
? [join(homedir(), '.local', 'bin', 'uv.exe'), join(homedir(), '.cargo', 'bin', 'uv.exe')]
: [join(homedir(), '.local', 'bin', 'uv'), join(homedir(), '.cargo', 'bin', 'uv'), '/usr/local/bin/uv'];
for (const uvPath of uvPaths) {
if (existsSync(uvPath)) {
console.error(`✅ uv installed at ${uvPath}`);
console.error('⚠️ Please restart your terminal or add uv to PATH:');
if (IS_WINDOWS) {
console.error(` $env:Path += ";${join(homedir(), '.local', 'bin')}"`);
} else {
console.error(` export PATH="$HOME/.local/bin:$PATH"`);
}
return true;
}
}
throw new Error('uv installation completed but binary not found');
}
} catch (error) {
console.error('❌ Failed to install uv automatically');
console.error(' Please install manually:');
if (IS_WINDOWS) {
console.error(' - winget install astral-sh.uv');
console.error(' - Or: powershell -c "irm https://astral.sh/uv/install.ps1 | iex"');
} else {
console.error(' - curl -LsSf https://astral.sh/uv/install.sh | sh');
console.error(' - Or: brew install uv (macOS)');
}
console.error(' Then restart your terminal and try again.');
throw error;
}
}
/**
* Check if dependencies need to be installed
*/
function needsInstall() {
if (!existsSync(join(ROOT, 'node_modules'))) return true;
try {
const pkg = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf-8'));
const marker = JSON.parse(readFileSync(MARKER, 'utf-8'));
return pkg.version !== marker.version || getBunVersion() !== marker.bun;
} catch {
return true;
}
}
/**
* Install dependencies using Bun with npm fallback
*
* Bun has issues with npm alias packages (e.g., string-width-cjs, strip-ansi-cjs)
* that are defined in package-lock.json. When bun fails with 404 errors for these
* packages, we fall back to npm which handles aliases correctly.
*/
function installDeps() {
const bunPath = getBunPath();
if (!bunPath) {
throw new Error('Bun executable not found');
}
console.error('📦 Installing dependencies with Bun...');
// Quote path for Windows paths with spaces
const bunCmd = IS_WINDOWS && bunPath.includes(' ') ? `"${bunPath}"` : bunPath;
let bunSucceeded = false;
try {
execSync(`${bunCmd} install`, { cwd: ROOT, stdio: 'inherit', shell: IS_WINDOWS });
bunSucceeded = true;
} catch {
// First attempt failed, try with force flag
try {
execSync(`${bunCmd} install --force`, { cwd: ROOT, stdio: 'inherit', shell: IS_WINDOWS });
bunSucceeded = true;
} catch {
// Bun failed completely, will try npm fallback
}
}
// Fallback to npm if bun failed (handles npm alias packages correctly)
if (!bunSucceeded) {
console.error('⚠️ Bun install failed, falling back to npm...');
console.error(' (This can happen with npm alias packages like *-cjs)');
try {
execSync('npm install', { cwd: ROOT, stdio: 'inherit', shell: IS_WINDOWS });
} catch (npmError) {
throw new Error('Both bun and npm install failed: ' + npmError.message);
}
}
// Write version marker
const pkg = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf-8'));
writeFileSync(MARKER, JSON.stringify({
version: pkg.version,
bun: getBunVersion(),
uv: getUvVersion(),
installedAt: new Date().toISOString()
}));
}
// Main execution
try {
// Step 1: Ensure Bun is installed (REQUIRED)
if (!isBunInstalled()) {
installBun();
// Re-check after installation
if (!isBunInstalled()) {
console.error('❌ Bun is required but not available in PATH');
console.error(' Please restart your terminal after installation');
process.exit(1);
}
}
// Step 2: Ensure uv is installed (REQUIRED for vector search)
if (!isUvInstalled()) {
installUv();
// Re-check after installation
if (!isUvInstalled()) {
console.error('❌ uv is required but not available in PATH');
console.error(' Please restart your terminal after installation');
process.exit(1);
}
}
// Step 3: Install dependencies if needed
if (needsInstall()) {
installDeps();
console.error('✅ Dependencies installed');
}
} catch (e) {
console.error('❌ Installation failed:', e.message);
process.exit(1);
}