229 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			229 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
|  | #!/usr/bin/env node
 | ||
|  | 
 | ||
|  | /** | ||
|  |  * SZCI npm postinstall script | ||
|  |  * Downloads the appropriate binary for the current platform from repository releases | ||
|  |  */ | ||
|  | 
 | ||
|  | import { platform, arch } from 'os'; | ||
|  | import { existsSync, mkdirSync, writeFileSync, chmodSync, unlinkSync } from 'fs'; | ||
|  | import { join, dirname } from 'path'; | ||
|  | import { fileURLToPath } from 'url'; | ||
|  | import https from 'https'; | ||
|  | import { pipeline } from 'stream'; | ||
|  | import { promisify } from 'util'; | ||
|  | import { createWriteStream } from 'fs'; | ||
|  | 
 | ||
|  | const __filename = fileURLToPath(import.meta.url); | ||
|  | const __dirname = dirname(__filename); | ||
|  | const streamPipeline = promisify(pipeline); | ||
|  | 
 | ||
|  | // Configuration
 | ||
|  | const REPO_BASE = 'https://code.foss.global/ship.zone/szci'; | ||
|  | const VERSION = process.env.npm_package_version || '4.1.37'; | ||
|  | 
 | ||
|  | function getBinaryInfo() { | ||
|  |   const plat = platform(); | ||
|  |   const architecture = arch(); | ||
|  | 
 | ||
|  |   const platformMap = { | ||
|  |     'darwin': 'macos', | ||
|  |     'linux': 'linux', | ||
|  |     'win32': 'windows' | ||
|  |   }; | ||
|  | 
 | ||
|  |   const archMap = { | ||
|  |     'x64': 'x64', | ||
|  |     'arm64': 'arm64' | ||
|  |   }; | ||
|  | 
 | ||
|  |   const mappedPlatform = platformMap[plat]; | ||
|  |   const mappedArch = archMap[architecture]; | ||
|  | 
 | ||
|  |   if (!mappedPlatform || !mappedArch) { | ||
|  |     return { supported: false, platform: plat, arch: architecture }; | ||
|  |   } | ||
|  | 
 | ||
|  |   let binaryName = `szci-${mappedPlatform}-${mappedArch}`; | ||
|  |   if (plat === 'win32') { | ||
|  |     binaryName += '.exe'; | ||
|  |   } | ||
|  | 
 | ||
|  |   return { | ||
|  |     supported: true, | ||
|  |     platform: mappedPlatform, | ||
|  |     arch: mappedArch, | ||
|  |     binaryName, | ||
|  |     originalPlatform: plat | ||
|  |   }; | ||
|  | } | ||
|  | 
 | ||
|  | function downloadFile(url, destination) { | ||
|  |   return new Promise((resolve, reject) => { | ||
|  |     console.log(`Downloading from: ${url}`); | ||
|  | 
 | ||
|  |     // Follow redirects
 | ||
|  |     const download = (url, redirectCount = 0) => { | ||
|  |       if (redirectCount > 5) { | ||
|  |         reject(new Error('Too many redirects')); | ||
|  |         return; | ||
|  |       } | ||
|  | 
 | ||
|  |       https.get(url, (response) => { | ||
|  |         if (response.statusCode === 301 || response.statusCode === 302) { | ||
|  |           console.log(`Following redirect to: ${response.headers.location}`); | ||
|  |           download(response.headers.location, redirectCount + 1); | ||
|  |           return; | ||
|  |         } | ||
|  | 
 | ||
|  |         if (response.statusCode !== 200) { | ||
|  |           reject(new Error(`Failed to download: ${response.statusCode} ${response.statusMessage}`)); | ||
|  |           return; | ||
|  |         } | ||
|  | 
 | ||
|  |         const totalSize = parseInt(response.headers['content-length'], 10); | ||
|  |         let downloadedSize = 0; | ||
|  |         let lastProgress = 0; | ||
|  | 
 | ||
|  |         response.on('data', (chunk) => { | ||
|  |           downloadedSize += chunk.length; | ||
|  |           const progress = Math.round((downloadedSize / totalSize) * 100); | ||
|  | 
 | ||
|  |           // Only log every 10% to reduce noise
 | ||
|  |           if (progress >= lastProgress + 10) { | ||
|  |             console.log(`Download progress: ${progress}%`); | ||
|  |             lastProgress = progress; | ||
|  |           } | ||
|  |         }); | ||
|  | 
 | ||
|  |         const file = createWriteStream(destination); | ||
|  | 
 | ||
|  |         pipeline(response, file, (err) => { | ||
|  |           if (err) { | ||
|  |             reject(err); | ||
|  |           } else { | ||
|  |             console.log('Download complete!'); | ||
|  |             resolve(); | ||
|  |           } | ||
|  |         }); | ||
|  |       }).on('error', reject); | ||
|  |     }; | ||
|  | 
 | ||
|  |     download(url); | ||
|  |   }); | ||
|  | } | ||
|  | 
 | ||
|  | async function main() { | ||
|  |   console.log('==========================================='); | ||
|  |   console.log('  SZCI - Binary Installation'); | ||
|  |   console.log('==========================================='); | ||
|  |   console.log(''); | ||
|  | 
 | ||
|  |   const binaryInfo = getBinaryInfo(); | ||
|  | 
 | ||
|  |   if (!binaryInfo.supported) { | ||
|  |     console.error(`❌ Error: Unsupported platform/architecture: ${binaryInfo.platform}/${binaryInfo.arch}`); | ||
|  |     console.error(''); | ||
|  |     console.error('Supported platforms:'); | ||
|  |     console.error('  • Linux (x64, arm64)'); | ||
|  |     console.error('  • macOS (x64, arm64)'); | ||
|  |     console.error('  • Windows (x64)'); | ||
|  |     console.error(''); | ||
|  |     console.error('If you believe your platform should be supported, please file an issue:'); | ||
|  |     console.error('  https://code.foss.global/ship.zone/szci/issues'); | ||
|  |     process.exit(1); | ||
|  |   } | ||
|  | 
 | ||
|  |   console.log(`Platform: ${binaryInfo.platform} (${binaryInfo.originalPlatform})`); | ||
|  |   console.log(`Architecture: ${binaryInfo.arch}`); | ||
|  |   console.log(`Binary: ${binaryInfo.binaryName}`); | ||
|  |   console.log(`Version: ${VERSION}`); | ||
|  |   console.log(''); | ||
|  | 
 | ||
|  |   // Create dist/binaries directory if it doesn't exist
 | ||
|  |   const binariesDir = join(__dirname, '..', 'dist', 'binaries'); | ||
|  |   if (!existsSync(binariesDir)) { | ||
|  |     console.log('Creating binaries directory...'); | ||
|  |     mkdirSync(binariesDir, { recursive: true }); | ||
|  |   } | ||
|  | 
 | ||
|  |   const binaryPath = join(binariesDir, binaryInfo.binaryName); | ||
|  | 
 | ||
|  |   // Check if binary already exists and skip download
 | ||
|  |   if (existsSync(binaryPath)) { | ||
|  |     console.log('✓ Binary already exists, skipping download'); | ||
|  |   } else { | ||
|  |     // Construct download URL
 | ||
|  |     // Try release URL first, fall back to raw branch if needed
 | ||
|  |     const releaseUrl = `${REPO_BASE}/releases/download/v${VERSION}/${binaryInfo.binaryName}`; | ||
|  |     const fallbackUrl = `${REPO_BASE}/raw/branch/master/dist/binaries/${binaryInfo.binaryName}`; | ||
|  | 
 | ||
|  |     console.log('Downloading platform-specific binary...'); | ||
|  |     console.log('This may take a moment depending on your connection speed.'); | ||
|  |     console.log(''); | ||
|  | 
 | ||
|  |     try { | ||
|  |       // Try downloading from release
 | ||
|  |       await downloadFile(releaseUrl, binaryPath); | ||
|  |     } catch (err) { | ||
|  |       console.log(`Release download failed: ${err.message}`); | ||
|  |       console.log('Trying fallback URL...'); | ||
|  | 
 | ||
|  |       try { | ||
|  |         // Try fallback URL
 | ||
|  |         await downloadFile(fallbackUrl, binaryPath); | ||
|  |       } catch (fallbackErr) { | ||
|  |         console.error(`❌ Error: Failed to download binary`); | ||
|  |         console.error(`  Primary URL: ${releaseUrl}`); | ||
|  |         console.error(`  Fallback URL: ${fallbackUrl}`); | ||
|  |         console.error(''); | ||
|  |         console.error('This might be because:'); | ||
|  |         console.error('1. The release has not been created yet'); | ||
|  |         console.error('2. Network connectivity issues'); | ||
|  |         console.error('3. The version specified does not exist'); | ||
|  |         console.error(''); | ||
|  |         console.error('You can try:'); | ||
|  |         console.error('1. Installing from source: https://code.foss.global/ship.zone/szci'); | ||
|  |         console.error('2. Downloading the binary manually from the releases page'); | ||
|  |         console.error('3. Building from source with: deno task compile'); | ||
|  | 
 | ||
|  |         // Clean up partial download
 | ||
|  |         if (existsSync(binaryPath)) { | ||
|  |           unlinkSync(binaryPath); | ||
|  |         } | ||
|  | 
 | ||
|  |         process.exit(1); | ||
|  |       } | ||
|  |     } | ||
|  | 
 | ||
|  |     console.log(`✓ Binary downloaded successfully`); | ||
|  |   } | ||
|  | 
 | ||
|  |   // On Unix-like systems, ensure the binary is executable
 | ||
|  |   if (binaryInfo.originalPlatform !== 'win32') { | ||
|  |     try { | ||
|  |       console.log('Setting executable permissions...'); | ||
|  |       chmodSync(binaryPath, 0o755); | ||
|  |       console.log('✓ Binary permissions updated'); | ||
|  |     } catch (err) { | ||
|  |       console.error(`⚠️  Warning: Could not set executable permissions: ${err.message}`); | ||
|  |       console.error('   You may need to manually run:'); | ||
|  |       console.error(`   chmod +x ${binaryPath}`); | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   console.log(''); | ||
|  |   console.log('✅ SZCI installation completed successfully!'); | ||
|  |   console.log(''); | ||
|  |   console.log('You can now use SZCI by running:'); | ||
|  |   console.log('  szci --help'); | ||
|  |   console.log(''); | ||
|  |   console.log('==========================================='); | ||
|  | } | ||
|  | 
 | ||
|  | // Run the installation
 | ||
|  | main().catch(err => { | ||
|  |   console.error(`❌ Installation failed: ${err.message}`); | ||
|  |   process.exit(1); | ||
|  | }); |