#!/usr/bin/env node const https = require('https'); const fs = require('fs'); const path = require('path'); const os = require('os'); const { execSync } = require('child_process'); // Get package version to determine which release to download const packageJson = require('../package.json'); const VERSION = packageJson.version; // Determine platform and architecture function getPlatformInfo() { const platform = os.platform(); const arch = os.arch(); let platformName; let archName; let binaryName = 'bd'; // Map Node.js platform names to GitHub release names switch (platform) { case 'darwin': platformName = 'darwin'; break; case 'linux': platformName = 'linux'; break; case 'win32': platformName = 'windows'; binaryName = 'bd.exe'; break; default: throw new Error(`Unsupported platform: ${platform}`); } // Map Node.js arch names to GitHub release names switch (arch) { case 'x64': archName = 'amd64'; break; case 'arm64': archName = 'arm64'; break; default: throw new Error(`Unsupported architecture: ${arch}`); } return { platformName, archName, binaryName }; } // Download file from URL function downloadFile(url, dest) { return new Promise((resolve, reject) => { console.log(`Downloading from: ${url}`); const file = fs.createWriteStream(dest); const request = https.get(url, (response) => { // Handle redirects if (response.statusCode === 301 || response.statusCode === 302) { const redirectUrl = response.headers.location; console.log(`Following redirect to: ${redirectUrl}`); downloadFile(redirectUrl, dest).then(resolve).catch(reject); return; } if (response.statusCode !== 200) { reject(new Error(`Failed to download: HTTP ${response.statusCode}`)); return; } response.pipe(file); file.on('finish', () => { file.close(); resolve(); }); }); request.on('error', (err) => { fs.unlink(dest, () => {}); reject(err); }); file.on('error', (err) => { fs.unlink(dest, () => {}); reject(err); }); }); } // Extract tar.gz file function extractTarGz(tarGzPath, destDir, binaryName) { console.log(`Extracting ${tarGzPath}...`); try { // Use tar command to extract execSync(`tar -xzf "${tarGzPath}" -C "${destDir}"`, { stdio: 'inherit' }); // The binary should now be in destDir const extractedBinary = path.join(destDir, binaryName); if (!fs.existsSync(extractedBinary)) { throw new Error(`Binary not found after extraction: ${extractedBinary}`); } // Make executable on Unix-like systems if (os.platform() !== 'win32') { fs.chmodSync(extractedBinary, 0o755); } console.log(`Binary extracted to: ${extractedBinary}`); } catch (err) { throw new Error(`Failed to extract archive: ${err.message}`); } } // Re-sign binary for macOS to avoid slow Gatekeeper checks // See: https://github.com/steveyegge/beads/issues/466 function resignForMacOS(binaryPath) { if (os.platform() !== 'darwin') { return; } console.log('Re-signing binary for macOS...'); try { // Remove existing signature try { execSync(`codesign --remove-signature "${binaryPath}"`, { stdio: 'pipe' }); } catch (e) { // Ignore errors - binary may not have a signature } // Add ad-hoc signature for this machine execSync(`codesign --force --sign - "${binaryPath}"`, { stdio: 'pipe' }); console.log('✓ Binary re-signed for this machine'); } catch (err) { console.warn('Warning: Failed to re-sign binary (non-fatal):', err.message); } } // Extract zip file (for Windows) function extractZip(zipPath, destDir, binaryName) { console.log(`Extracting ${zipPath}...`); try { // Use unzip command or powershell on Windows if (os.platform() === 'win32') { execSync(`powershell -command "Expand-Archive -Path '${zipPath}' -DestinationPath '${destDir}' -Force"`, { stdio: 'inherit' }); } else { execSync(`unzip -o "${zipPath}" -d "${destDir}"`, { stdio: 'inherit' }); } // The binary should now be in destDir const extractedBinary = path.join(destDir, binaryName); if (!fs.existsSync(extractedBinary)) { throw new Error(`Binary not found after extraction: ${extractedBinary}`); } console.log(`Binary extracted to: ${extractedBinary}`); } catch (err) { throw new Error(`Failed to extract archive: ${err.message}`); } } // Main installation function async function install() { try { const { platformName, archName, binaryName } = getPlatformInfo(); console.log(`Installing bd v${VERSION} for ${platformName}-${archName}...`); // Construct download URL // Format: https://github.com/steveyegge/beads/releases/download/v0.21.5/beads_0.21.5_darwin_amd64.tar.gz const releaseVersion = VERSION; const archiveExt = platformName === 'windows' ? 'zip' : 'tar.gz'; const archiveName = `beads_${releaseVersion}_${platformName}_${archName}.${archiveExt}`; const downloadUrl = `https://github.com/steveyegge/beads/releases/download/v${releaseVersion}/${archiveName}`; // Determine destination paths const binDir = path.join(__dirname, '..', 'bin'); const archivePath = path.join(binDir, archiveName); const binaryPath = path.join(binDir, binaryName); // Ensure bin directory exists if (!fs.existsSync(binDir)) { fs.mkdirSync(binDir, { recursive: true }); } // Download the archive console.log(`Downloading bd binary...`); await downloadFile(downloadUrl, archivePath); // Extract the archive based on platform if (platformName === 'windows') { extractZip(archivePath, binDir, binaryName); } else { extractTarGz(archivePath, binDir, binaryName); } // Re-sign for macOS to avoid Gatekeeper delays resignForMacOS(binaryPath); // Clean up archive fs.unlinkSync(archivePath); // Verify the binary works try { const output = execSync(`"${binaryPath}" version`, { encoding: 'utf8' }); console.log(`✓ bd installed successfully: ${output.trim()}`); } catch (err) { console.warn('Warning: Could not verify binary version'); } } catch (err) { console.error(`Error installing bd: ${err.message}`); console.error(''); console.error('Installation failed. You can try:'); console.error('1. Installing manually from: https://github.com/steveyegge/beads/releases'); console.error('2. Using the install script: curl -fsSL https://raw.githubusercontent.com/steveyegge/beads/main/scripts/install.sh | bash'); console.error('3. Opening an issue: https://github.com/steveyegge/beads/issues'); process.exit(1); } } // Run installation if not in CI environment if (!process.env.CI) { install(); } else { console.log('Skipping binary download in CI environment'); }