vibetunnel/web/scripts/postinstall-npm.js
2025-07-17 19:04:30 +02:00

256 lines
No EOL
8.8 KiB
JavaScript
Executable file

#!/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');
}