#!/usr/bin/env node /** * Postinstall script for npm package * Handles prebuild extraction and fallback compilation */ const fs = require('fs'); const path = require('path'); const { execSync } = require('child_process'); const os = require('os'); console.log('Setting up native modules for VibeTunnel...'); // Check for npm_config_prefix conflict with NVM if (process.env.npm_config_prefix && process.env.NVM_DIR) { const nvmNodeVersion = process.execPath; const npmPrefix = process.env.npm_config_prefix; // Check if npm_config_prefix conflicts with NVM path if (!nvmNodeVersion.includes(npmPrefix) && nvmNodeVersion.includes('.nvm')) { console.warn('⚠️ Detected npm_config_prefix conflict with NVM'); console.warn(` npm_config_prefix: ${npmPrefix}`); console.warn(` NVM Node path: ${nvmNodeVersion}`); console.warn(' This may cause npm global installs to fail or install in wrong location.'); console.warn(' Run: unset npm_config_prefix'); console.warn(' Then reinstall VibeTunnel for proper NVM compatibility.'); } } // Check if we're in development (has src directory) or npm install const isDevelopment = fs.existsSync(path.join(__dirname, '..', 'src')); if (isDevelopment) { // In development, run the existing ensure-native-modules script require('./ensure-native-modules.js'); return; } // For npm package, node-pty is bundled in the package root // No need to create symlinks as it's accessed directly // Get Node ABI version const nodeABI = process.versions.modules; // Get platform and architecture const platform = process.platform; const arch = os.arch(); // Convert architecture names const archMap = { 'arm64': 'arm64', 'aarch64': 'arm64', 'x64': 'x64', 'x86_64': 'x64' }; const normalizedArch = archMap[arch] || arch; console.log(`Platform: ${platform}-${normalizedArch}, Node ABI: ${nodeABI}`); // Function to try prebuild-install first const tryPrebuildInstall = (moduleName, moduleDir) => { try { // Check if prebuild-install is available const prebuildInstallPath = require.resolve('prebuild-install/bin.js'); console.log(` Attempting to use prebuild-install for ${moduleName}...`); execSync(`node "${prebuildInstallPath}"`, { cwd: moduleDir, stdio: 'inherit', env: { ...process.env, npm_config_build_from_source: 'false' } }); return true; } catch (error) { console.log(` prebuild-install failed for ${moduleName}, will try manual extraction`); return false; } }; // Function to manually extract prebuild const extractPrebuild = (name, version, targetDir, skipDirCheck = false) => { const prebuildFile = path.join(__dirname, '..', 'prebuilds', `${name}-v${version}-node-v${nodeABI}-${platform}-${normalizedArch}.tar.gz`); if (!fs.existsSync(prebuildFile)) { console.log(` No prebuild found for ${name} on this platform`); return false; } // For optional dependencies like authenticate-pam, check if the module exists // If not, extract to a different location let extractDir = targetDir; if (skipDirCheck && name === 'authenticate-pam' && !fs.existsSync(targetDir)) { // Extract to a controlled location since node_modules/authenticate-pam doesn't exist extractDir = path.join(__dirname, '..', 'optional-modules', name); } // Create the parent directory fs.mkdirSync(extractDir, { recursive: true }); try { // Extract directly into the module directory - the tar already contains build/Release structure execSync(`tar -xzf "${prebuildFile}" -C "${extractDir}"`, { stdio: 'inherit' }); console.log(`✓ ${name} prebuilt binary extracted`); return true; } catch (error) { console.error(` Failed to extract ${name} prebuild:`, error.message); return false; } }; // Function to compile from source const compileFromSource = (moduleName, moduleDir) => { console.log(` Building ${moduleName} from source...`); try { // First check if node-gyp is available try { execSync('node-gyp --version', { stdio: 'pipe' }); } catch (e) { console.log(' Installing node-gyp...'); execSync('npm install -g node-gyp', { stdio: 'inherit' }); } // For node-pty, node-addon-api is included as a dependency in its package.json // npm should handle it automatically during source compilation execSync('node-gyp rebuild', { cwd: moduleDir, stdio: 'inherit' }); console.log(`✓ ${moduleName} built successfully`); return true; } catch (error) { console.error(` Failed to build ${moduleName}:`, error.message); return false; } }; // Handle both native modules const modules = [ { name: 'node-pty', version: '1.0.0', dir: path.join(__dirname, '..', 'node-pty'), build: path.join(__dirname, '..', 'node-pty', 'build', 'Release', 'pty.node'), essential: true }, { name: 'authenticate-pam', version: '1.0.5', dir: path.join(__dirname, '..', 'node_modules', 'authenticate-pam'), build: path.join(__dirname, '..', 'node_modules', 'authenticate-pam', 'build', 'Release', 'authenticate_pam.node'), essential: false, // Optional - falls back to other auth methods platforms: ['linux', 'darwin'], // Needed on Linux and macOS skipDirCheck: true // Don't check if dir exists since it's optional } ]; let hasErrors = false; for (const module of modules) { console.log(`\nProcessing ${module.name}...`); // Skip platform-specific modules if not on that platform if (module.platforms && !module.platforms.includes(platform)) { console.log(` Skipping ${module.name} (not needed on ${platform})`); continue; } // Check if module directory exists if (!fs.existsSync(module.dir)) { if (module.skipDirCheck) { // For optional modules, we'll try to extract the prebuild anyway console.log(` ${module.name} not installed via npm (optional dependency), will extract prebuild`); } else { console.warn(` Warning: ${module.name} directory not found at ${module.dir}`); if (module.essential) { hasErrors = true; } continue; } } // Check if already built // For optional modules, also check the alternative location let buildPath = module.build; if (module.skipDirCheck && module.name === 'authenticate-pam' && !fs.existsSync(module.dir)) { // Check the optional-modules location instead buildPath = path.join(__dirname, '..', 'optional-modules', module.name, 'build', 'Release', 'authenticate_pam.node'); } if (fs.existsSync(buildPath)) { console.log(`✓ ${module.name} already available`); continue; } // Try installation methods in order let success = false; // Method 1: Try prebuild-install (preferred) - skip if directory doesn't exist if (fs.existsSync(module.dir)) { success = tryPrebuildInstall(module.name, module.dir); } // Method 2: Manual prebuild extraction if (!success) { success = extractPrebuild(module.name, module.version, module.dir, module.skipDirCheck); } // Method 3: Compile from source (skip if directory doesn't exist) if (!success && fs.existsSync(module.dir) && fs.existsSync(path.join(module.dir, 'binding.gyp'))) { success = compileFromSource(module.name, module.dir); } // Check final result if (!success) { // Special handling for authenticate-pam on macOS if (module.name === 'authenticate-pam' && process.platform === 'darwin') { console.warn(`⚠️ Warning: ${module.name} installation failed on macOS.`); console.warn(' This is expected - macOS will fall back to environment variable or SSH key authentication.'); console.warn(' To enable PAM authentication, install Xcode Command Line Tools and rebuild.'); } else if (module.essential) { console.error(`\n❌ ${module.name} is required for VibeTunnel to function.`); console.error('You may need to install build tools for your platform:'); console.error('- macOS: Install Xcode Command Line Tools'); console.error('- Linux: Install build-essential and libpam0g-dev packages'); hasErrors = true; } else { console.warn(`⚠️ Warning: ${module.name} installation failed. Some features may be limited.`); } } } // Import vt installation functions const { detectGlobalInstall, installVtCommand } = require('./install-vt-command'); // Install vt symlink/wrapper if (!hasErrors && !isDevelopment) { console.log('\nSetting up vt command...'); const vtSource = path.join(__dirname, '..', 'bin', 'vt'); // Use the improved global install detection const isGlobalInstall = detectGlobalInstall(); console.log(` Detected ${isGlobalInstall ? 'global' : 'local'} installation`); installVtCommand(vtSource, isGlobalInstall); } if (hasErrors) { console.error('\n❌ Setup failed with errors'); process.exit(1); } else { console.log('\n✅ VibeTunnel is ready to use'); console.log('Run "vibetunnel --help" for usage information'); }