/* eslint-disable no-console */ import {WalletPluginPrivateKey} from '@wharfkit/wallet-plugin-privatekey' import {executeCommand, getDevKeys, getPlatform} from './utils' import {NonInteractiveConsoleUI} from '../../utils/wharfkit-ui' import * as os from 'os' import * as path from 'path' import * as fs from 'fs' const SPRING_VERSION = 'v1.1.5' const SPRING_REPO = 'https://github.com/AntelopeIO/spring' export interface InstallationStatus { installed: boolean nodeos: boolean nodeosPath?: string version?: string wharfkit: { consoleRenderer: boolean walletPlugin: boolean } } /** * Get the directory where Spring will be cloned and built */ function getSpringBuildDir(): string { return path.join(os.homedir(), '.wharfkit', 'spring-build') } /** * Check if Spring is installed */ export async function checkSpringInstallation(): Promise { const status: InstallationStatus = { installed: false, nodeos: false, wharfkit: { consoleRenderer: false, walletPlugin: false, }, } // Check nodeos try { const {stdout} = await executeCommand('which nodeos') status.nodeosPath = stdout.trim() status.nodeos = true } catch { // nodeos not found } status.wharfkit.consoleRenderer = checkConsoleRenderer() status.wharfkit.walletPlugin = checkWalletPlugin() // Get version if nodeos is installed if (status.nodeos) { try { const {stdout} = await executeCommand('nodeos --version') const versionMatch = stdout.match(/v?(\d+\.\d+\.\d+)/) if (versionMatch) { status.version = versionMatch[1] } } catch { // Version check failed } } status.installed = status.nodeos && status.wharfkit.consoleRenderer && status.wharfkit.walletPlugin return status } /** * Ensure directory exists */ async function ensureBuildDir(dir: string): Promise { if (!fs.existsSync(dir)) { await fs.promises.mkdir(dir, {recursive: true}) } } /** * Clone and checkout Spring repository */ async function cloneSpringRepo(buildDir: string): Promise { const springDir = path.join(buildDir, 'spring') // Check if already cloned if (fs.existsSync(path.join(springDir, '.git'))) { console.log('Spring repository already cloned, updating...') try { await executeCommand(`cd ${springDir} && git fetch --all --tags`) await executeCommand(`cd ${springDir} && git checkout ${SPRING_VERSION}`) await executeCommand(`cd ${springDir} && git pull || true`) await executeCommand(`cd ${springDir} && git submodule update --init --recursive`) } catch (error: unknown) { const message = error instanceof Error ? error.message : String(error) throw new Error(`Failed to update Spring repository: ${message}`) } } else { console.log('Cloning Spring repository...') try { await executeCommand(`git clone --recursive ${SPRING_REPO} ${springDir}`) await executeCommand(`cd ${springDir} && git fetch --all --tags`) await executeCommand(`cd ${springDir} && git checkout ${SPRING_VERSION}`) await executeCommand(`cd ${springDir} && git submodule update --init --recursive`) } catch (error: unknown) { const message = error instanceof Error ? error.message : String(error) throw new Error(`Failed to clone Spring repository: ${message}`) } } } /** * Build Spring from source */ async function buildSpring(buildDir: string, numJobs?: number): Promise { const springDir = path.join(buildDir, 'spring') const springBuildDir = path.join(springDir, 'build') // Create build directory await ensureBuildDir(springBuildDir) const jobs = numJobs || Math.max(1, Math.floor(os.cpus().length / 2)) console.log(`Building Spring with ${jobs} parallel jobs (this may take a while)...`) try { const {os: platform} = getPlatform() if (platform === 'darwin') { // macOS build - use llvm from Homebrew const llvmPrefix = await getLlvmPrefix() await executeCommand( `cd ${springBuildDir} && cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=${llvmPrefix} ..` ) } else { // Linux build await executeCommand( `cd ${springBuildDir} && cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=/usr/lib/llvm-11 ..` ) } await executeCommand(`cd ${springBuildDir} && make -j ${jobs}`) } catch (error: unknown) { const message = error instanceof Error ? error.message : String(error) throw new Error(`Failed to build Spring: ${message}`) } } /** * Get LLVM prefix path on macOS */ async function getLlvmPrefix(): Promise { try { const {stdout} = await executeCommand('brew --prefix llvm@11') return stdout.trim() } catch { // Try llvm without version try { const {stdout} = await executeCommand('brew --prefix llvm') return stdout.trim() } catch { return '/usr/local/opt/llvm' } } } /** * Install built Spring binaries */ async function installBuiltSpring(buildDir: string): Promise { const springDir = path.join(buildDir, 'spring') const springBuildDir = path.join(springDir, 'build') const binDir = path.join(springBuildDir, 'bin') console.log('Installing Spring binaries...') const binaries = ['nodeos', 'cleos', 'keosd'] const targetDir = '/usr/local/bin' try { const {os: platform} = getPlatform() // First, try to copy binaries directly (works if user owns /usr/local/bin) try { for (const binary of binaries) { const src = path.join(binDir, binary) const dest = path.join(targetDir, binary) if (fs.existsSync(src)) { await fs.promises.copyFile(src, dest) await fs.promises.chmod(dest, 0o755) } } console.log('Binaries copied to /usr/local/bin') return } catch { // Direct copy failed, try sudo methods console.log('Direct copy failed, trying with elevated permissions...') } if (platform === 'darwin') { // On macOS, use make install with sudo await executeCommand(`cd ${springBuildDir} && sudo make install`) } else { // On Linux, install the .deb package if available, otherwise make install try { const {stdout} = await executeCommand( `ls ${springBuildDir}/spring*.deb 2>/dev/null | head -1` ) if (stdout.trim()) { await executeCommand(`sudo apt-get install -y ${stdout.trim()}`) } else { await executeCommand(`cd ${springBuildDir} && sudo make install`) } } catch { await executeCommand(`cd ${springBuildDir} && sudo make install`) } } } catch (error: unknown) { const message = error instanceof Error ? error.message : String(error) throw new Error(`Failed to install Spring: ${message}`) } } /** * Install Spring on macOS by building from source */ async function installSpringMacOS(): Promise { console.log('Installing Spring on macOS by building from source...') // Check if Homebrew is installed try { await executeCommand('which brew') } catch { throw new Error( 'Homebrew is not installed. Please install it from https://brew.sh/ and try again.' ) } // Install build dependencies console.log('Installing build dependencies...') try { await executeCommand('brew install cmake git llvm@11 gmp curl python3 numpy || true') } catch (error: unknown) { const message = error instanceof Error ? error.message : String(error) throw new Error(`Failed to install dependencies: ${message}`) } const buildDir = getSpringBuildDir() await ensureBuildDir(buildDir) // Clone repository await cloneSpringRepo(buildDir) // Build from source await buildSpring(buildDir) // Install await installBuiltSpring(buildDir) console.log('Spring installed successfully!') } /** * Get Ubuntu version for determining LLVM version */ async function getUbuntuVersion(): Promise { try { const {stdout} = await executeCommand('lsb_release -rs') return stdout.trim() } catch { return '22.04' } } /** * Install Spring on Linux by building from source */ async function installSpringLinux(): Promise { console.log('Installing Spring on Linux by building from source...') const ubuntuVersion = await getUbuntuVersion() const majorVersion = parseInt(ubuntuVersion.split('.')[0], 10) // Update package list console.log('Updating package list...') try { await executeCommand('sudo apt-get update') } catch (error: unknown) { const message = error instanceof Error ? error.message : String(error) throw new Error(`Failed to update package list: ${message}`) } // Install build dependencies console.log('Installing build dependencies...') try { await executeCommand(`sudo apt-get install -y \ build-essential \ cmake \ git \ libcurl4-openssl-dev \ libgmp-dev \ llvm-11-dev \ python3-numpy \ file \ zlib1g-dev`) // On Ubuntu 20.04, install gcc-10 for C++20 support if (majorVersion === 20) { await executeCommand('sudo apt-get install -y g++-10') } } catch (error: unknown) { const message = error instanceof Error ? error.message : String(error) throw new Error(`Failed to install dependencies: ${message}`) } const buildDir = getSpringBuildDir() await ensureBuildDir(buildDir) // Clone repository await cloneSpringRepo(buildDir) // Build from source (with Ubuntu 20.04 specific compiler flags) await buildSpringLinux(buildDir, majorVersion) // Install await installBuiltSpring(buildDir) console.log('Spring installed successfully!') } /** * Build Spring on Linux with version-specific settings */ async function buildSpringLinux(buildDir: string, ubuntuMajorVersion: number): Promise { const springDir = path.join(buildDir, 'spring') const springBuildDir = path.join(springDir, 'build') // Create build directory await ensureBuildDir(springBuildDir) const jobs = Math.max(1, Math.floor(os.cpus().length / 2)) console.log(`Building Spring with ${jobs} parallel jobs (this may take a while)...`) try { if (ubuntuMajorVersion === 20) { // Ubuntu 20.04 needs gcc-10 specified await executeCommand( `cd ${springBuildDir} && cmake \ -DCMAKE_C_COMPILER=gcc-10 \ -DCMAKE_CXX_COMPILER=g++-10 \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_PREFIX_PATH=/usr/lib/llvm-11 ..` ) } else { // Ubuntu 22.04+ has gcc-11 by default await executeCommand( `cd ${springBuildDir} && cmake \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_PREFIX_PATH=/usr/lib/llvm-11 ..` ) } await executeCommand(`cd ${springBuildDir} && make -j ${jobs}`) } catch (error: unknown) { const message = error instanceof Error ? error.message : String(error) throw new Error(`Failed to build Spring: ${message}`) } } /** * Install Spring based on platform */ export async function installSpring(): Promise { const {os} = getPlatform() switch (os) { case 'darwin': await installSpringMacOS() break case 'linux': await installSpringLinux() break default: throw new Error( `Automatic installation is not supported on ${os}. ` + 'Please install Spring manually from https://github.com/AntelopeIO/spring/releases' ) } // Verify installation const status = await checkSpringInstallation() if (!status.installed) { throw new Error('Installation completed but Spring binaries are not available in PATH') } console.log(`Spring ${status.version} is now installed and ready to use!`) } /** * Ensure Spring is installed, install automatically if not found */ export async function ensureSpringInstalled(): Promise { const status = await checkSpringInstallation() if (status.installed) { console.log(`Spring ${status.version} is already installed`) return } // In CI mode, skip auto-installation and just check if nodeos is available if (process.env.GITHUB_CI) { if (!status.nodeos) { throw new Error( 'Spring is not installed and auto-installation is disabled in CI mode. ' + 'Please install Spring manually or ensure nodeos is available in PATH.' ) } // If nodeos is available, continue even if other components are missing console.log(`Spring nodeos is available (version: ${status.version || 'unknown'})`) return } console.log('Spring is not installed, installing automatically...') if (!status.nodeos) { console.log(' - nodeos is not installed') } if (!status.wharfkit.consoleRenderer) { console.log(' - WharfKit console renderer is unavailable') } if (!status.wharfkit.walletPlugin) { console.log(' - WharfKit private key wallet plugin is unavailable') } await installSpring() } function checkConsoleRenderer(): boolean { try { new NonInteractiveConsoleUI() return true } catch { return false } } function checkWalletPlugin(): boolean { try { const devKeys = getDevKeys() new WalletPluginPrivateKey(devKeys.privateKey) return true } catch { return false } }