Files
beads/npm-package/scripts/postinstall.js
Ariff Azman 2a579a539b feat: add native Android/Termux binary support (#887)
feat: added android support for running with termux

Adds GOOS=android arm64 build to goreleaser and platform detection in npm postinstall.

Tested by contributor on Termux where os.platform() returns 'android' and Linux binaries fail due to bionic vs glibc.
2026-01-05 20:06:25 -08:00

239 lines
7.2 KiB
JavaScript
Executable File

#!/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 'android':
platformName = 'android';
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', () => {
// Wait for file.close() to complete before resolving
// This is critical on Windows where the file may still be locked
file.close((err) => {
if (err) reject(err);
else 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 (Linux, macOS, Android)
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}`);
}
}
// Sleep helper for retry logic
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Extract zip file (for Windows) with retry logic
async function extractZip(zipPath, destDir, binaryName) {
console.log(`Extracting ${zipPath}...`);
const maxRetries = 5;
const baseDelayMs = 500;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
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}`);
return; // Success
} catch (err) {
const isFileLockError = err.message && (
err.message.includes('being used by another process') ||
err.message.includes('Access is denied') ||
err.message.includes('cannot access the file')
);
if (isFileLockError && attempt < maxRetries) {
const delayMs = baseDelayMs * Math.pow(2, attempt - 1);
console.log(`File may be locked (attempt ${attempt}/${maxRetries}). Retrying in ${delayMs}ms...`);
await sleep(delayMs);
} else if (attempt === maxRetries) {
throw new Error(`Failed to extract archive after ${maxRetries} attempts: ${err.message}`);
} else {
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}...`);
// Determine destination paths
const binDir = path.join(__dirname, '..', 'bin');
const binaryPath = path.join(binDir, binaryName);
// Ensure bin directory exists
if (!fs.existsSync(binDir)) {
fs.mkdirSync(binDir, { recursive: true });
}
// 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}`;
const archivePath = path.join(binDir, archiveName);
// Download the archive
console.log(`Downloading bd binary...`);
await downloadFile(downloadUrl, archivePath);
// Extract the archive based on platform
if (platformName === 'windows') {
await extractZip(archivePath, binDir, binaryName);
} else {
extractTarGz(archivePath, binDir, binaryName);
}
// 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) {
throw new Error(`Binary verification failed: ${err.message}`);
}
} 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');
}