setup.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. #!/usr/bin/env node
  2. const fs = require('fs');
  3. const path = require('path');
  4. const https = require('https');
  5. const { execSync } = require('child_process');
  6. console.log('🚀 Setting up GrabZilla development environment...\n');
  7. // Platform detection
  8. const platform = process.platform; // 'darwin', 'win32', 'linux'
  9. const arch = process.arch; // 'x64', 'arm64', etc.
  10. console.log(`📋 Platform: ${platform} ${arch}`);
  11. // Create necessary directories
  12. const dirs = [
  13. 'binaries',
  14. 'assets/icons',
  15. 'dist',
  16. 'tests'
  17. ];
  18. dirs.forEach(dir => {
  19. if (!fs.existsSync(dir)) {
  20. fs.mkdirSync(dir, { recursive: true });
  21. console.log(`✅ Created directory: ${dir}`);
  22. }
  23. });
  24. /**
  25. * Download file from URL with progress tracking
  26. */
  27. function downloadFile(url, dest) {
  28. return new Promise((resolve, reject) => {
  29. const file = fs.createWriteStream(dest);
  30. let downloadedBytes = 0;
  31. let totalBytes = 0;
  32. https.get(url, {
  33. headers: {
  34. 'User-Agent': 'GrabZilla-Setup/2.1.0'
  35. }
  36. }, (response) => {
  37. // Handle redirects
  38. if (response.statusCode === 302 || response.statusCode === 301) {
  39. file.close();
  40. fs.unlinkSync(dest);
  41. return downloadFile(response.headers.location, dest)
  42. .then(resolve)
  43. .catch(reject);
  44. }
  45. if (response.statusCode !== 200) {
  46. file.close();
  47. fs.unlinkSync(dest);
  48. return reject(new Error(`Failed to download: HTTP ${response.statusCode}`));
  49. }
  50. totalBytes = parseInt(response.headers['content-length'], 10);
  51. response.on('data', (chunk) => {
  52. downloadedBytes += chunk.length;
  53. const progress = ((downloadedBytes / totalBytes) * 100).toFixed(1);
  54. process.stdout.write(`\r Progress: ${progress}% (${(downloadedBytes / 1024 / 1024).toFixed(1)} MB / ${(totalBytes / 1024 / 1024).toFixed(1)} MB)`);
  55. });
  56. response.pipe(file);
  57. file.on('finish', () => {
  58. file.close();
  59. process.stdout.write('\n');
  60. resolve();
  61. });
  62. }).on('error', (err) => {
  63. file.close();
  64. fs.unlinkSync(dest);
  65. reject(err);
  66. });
  67. file.on('error', (err) => {
  68. file.close();
  69. fs.unlinkSync(dest);
  70. reject(err);
  71. });
  72. });
  73. }
  74. /**
  75. * Get latest yt-dlp release info from GitHub
  76. */
  77. function getLatestYtDlpRelease() {
  78. return new Promise((resolve, reject) => {
  79. const options = {
  80. hostname: 'api.github.com',
  81. path: '/repos/yt-dlp/yt-dlp/releases/latest',
  82. method: 'GET',
  83. headers: {
  84. 'User-Agent': 'GrabZilla-Setup/2.1.0',
  85. 'Accept': 'application/vnd.github.v3+json'
  86. },
  87. timeout: 10000
  88. };
  89. const req = https.request(options, (res) => {
  90. let data = '';
  91. res.on('data', (chunk) => {
  92. data += chunk;
  93. });
  94. res.on('end', () => {
  95. try {
  96. const release = JSON.parse(data);
  97. resolve(release);
  98. } catch (error) {
  99. reject(new Error('Failed to parse GitHub API response'));
  100. }
  101. });
  102. });
  103. req.on('error', reject);
  104. req.on('timeout', () => {
  105. req.destroy();
  106. reject(new Error('GitHub API request timed out'));
  107. });
  108. req.end();
  109. });
  110. }
  111. /**
  112. * Download and install yt-dlp
  113. */
  114. async function installYtDlp() {
  115. console.log('\n📥 Installing yt-dlp...');
  116. try {
  117. const release = await getLatestYtDlpRelease();
  118. const version = release.tag_name || 'latest';
  119. console.log(` Latest version: ${version}`);
  120. // Determine download URL based on platform
  121. let assetName;
  122. if (platform === 'darwin' || platform === 'linux') {
  123. assetName = 'yt-dlp';
  124. } else if (platform === 'win32') {
  125. assetName = 'yt-dlp.exe';
  126. } else {
  127. throw new Error(`Unsupported platform: ${platform}`);
  128. }
  129. const asset = release.assets.find(a => a.name === assetName);
  130. if (!asset) {
  131. throw new Error(`No suitable yt-dlp binary found for ${platform}`);
  132. }
  133. const downloadUrl = asset.browser_download_url;
  134. const binaryPath = path.join('binaries', assetName);
  135. console.log(` Downloading from: ${downloadUrl}`);
  136. await downloadFile(downloadUrl, binaryPath);
  137. // Make executable on Unix-like systems
  138. if (platform !== 'win32') {
  139. fs.chmodSync(binaryPath, 0o755);
  140. console.log(' Made executable');
  141. }
  142. console.log('✅ yt-dlp installed successfully');
  143. return true;
  144. } catch (error) {
  145. console.error(`❌ Failed to install yt-dlp: ${error.message}`);
  146. return false;
  147. }
  148. }
  149. /**
  150. * Download and install ffmpeg
  151. */
  152. async function installFfmpeg() {
  153. console.log('\n📥 Installing ffmpeg...');
  154. try {
  155. let downloadUrl;
  156. let binaryName;
  157. let needsExtraction = false;
  158. if (platform === 'darwin') {
  159. // For macOS, use static builds from evermeet
  160. downloadUrl = 'https://evermeet.cx/ffmpeg/ffmpeg-7.1.zip';
  161. binaryName = 'ffmpeg';
  162. needsExtraction = true;
  163. } else if (platform === 'win32') {
  164. // Windows: Use gyan.dev builds
  165. downloadUrl = 'https://github.com/GyanD/codexffmpeg/releases/download/6.1.1/ffmpeg-6.1.1-essentials_build.zip';
  166. binaryName = 'ffmpeg.exe';
  167. needsExtraction = true;
  168. } else if (platform === 'linux') {
  169. // Linux: Use johnvansickle builds
  170. if (arch === 'x64') {
  171. downloadUrl = 'https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz';
  172. } else if (arch === 'arm64') {
  173. downloadUrl = 'https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-arm64-static.tar.xz';
  174. } else {
  175. throw new Error(`Unsupported Linux architecture: ${arch}`);
  176. }
  177. binaryName = 'ffmpeg';
  178. needsExtraction = true;
  179. } else {
  180. throw new Error(`Unsupported platform: ${platform}`);
  181. }
  182. console.log(` Downloading from: ${downloadUrl}`);
  183. if (needsExtraction) {
  184. // Download archive
  185. const archiveName = path.basename(downloadUrl);
  186. const archivePath = path.join('binaries', archiveName);
  187. await downloadFile(downloadUrl, archivePath);
  188. console.log(' Extracting ffmpeg...');
  189. // Extract based on archive type
  190. const binaryPath = path.join('binaries', binaryName);
  191. if (archiveName.endsWith('.zip')) {
  192. // Use unzip command for macOS/Linux, or manual extraction for Windows
  193. if (platform === 'darwin') {
  194. execSync(`unzip -o "${archivePath}" -d binaries/`, { stdio: 'inherit' });
  195. // Find the ffmpeg binary in extracted files
  196. if (fs.existsSync('binaries/ffmpeg')) {
  197. // Already at root
  198. console.log(' Found ffmpeg at root');
  199. } else {
  200. throw new Error('ffmpeg not found after extraction');
  201. }
  202. } else if (platform === 'win32') {
  203. // Windows: Need to handle ZIP extraction differently
  204. console.log('⚠️ Manual extraction required on Windows');
  205. console.log(` Please extract ${archivePath} and place ffmpeg.exe in binaries/`);
  206. return false;
  207. }
  208. } else if (archiveName.endsWith('.tar.xz')) {
  209. // Linux tar.xz extraction
  210. execSync(`tar -xf "${archivePath}" -C binaries/`, { stdio: 'inherit' });
  211. // Find ffmpeg in extracted directory
  212. const extractedDir = fs.readdirSync('binaries/').find(f => f.startsWith('ffmpeg-') && fs.statSync(path.join('binaries', f)).isDirectory());
  213. if (extractedDir) {
  214. const ffmpegInDir = path.join('binaries', extractedDir, 'ffmpeg');
  215. if (fs.existsSync(ffmpegInDir)) {
  216. fs.copyFileSync(ffmpegInDir, binaryPath);
  217. console.log(` Copied ffmpeg from ${extractedDir}`);
  218. }
  219. }
  220. }
  221. // Clean up archive
  222. if (fs.existsSync(archivePath)) {
  223. fs.unlinkSync(archivePath);
  224. console.log(' Cleaned up archive');
  225. }
  226. // Make executable on Unix-like systems
  227. if (platform !== 'win32' && fs.existsSync(binaryPath)) {
  228. fs.chmodSync(binaryPath, 0o755);
  229. console.log(' Made executable');
  230. }
  231. } else {
  232. // Direct binary download (not currently used)
  233. const binaryPath = path.join('binaries', binaryName);
  234. await downloadFile(downloadUrl, binaryPath);
  235. if (platform !== 'win32') {
  236. fs.chmodSync(binaryPath, 0o755);
  237. console.log(' Made executable');
  238. }
  239. }
  240. console.log('✅ ffmpeg installed successfully');
  241. return true;
  242. } catch (error) {
  243. console.error(`❌ Failed to install ffmpeg: ${error.message}`);
  244. console.error(' You may need to install ffmpeg manually');
  245. return false;
  246. }
  247. }
  248. /**
  249. * Main setup function
  250. */
  251. async function main() {
  252. console.log('\n🔧 Installing required binaries...\n');
  253. // Check if binaries already exist
  254. const ytdlpPath = path.join('binaries', platform === 'win32' ? 'yt-dlp.exe' : 'yt-dlp');
  255. const ffmpegPath = path.join('binaries', platform === 'win32' ? 'ffmpeg.exe' : 'ffmpeg');
  256. let ytdlpExists = fs.existsSync(ytdlpPath);
  257. let ffmpegExists = fs.existsSync(ffmpegPath);
  258. if (ytdlpExists && ffmpegExists) {
  259. console.log('✅ All binaries already installed');
  260. console.log('\n🎯 Development Commands:');
  261. console.log('- npm run dev # Run in development mode');
  262. console.log('- npm start # Run in production mode');
  263. console.log('- npm run build # Build for current platform');
  264. console.log('- npm test # Run tests\n');
  265. console.log('✨ Setup complete! Ready to develop GrabZilla 2.1');
  266. return;
  267. }
  268. // Install missing binaries
  269. if (!ytdlpExists) {
  270. await installYtDlp();
  271. } else {
  272. console.log('✅ yt-dlp already installed');
  273. }
  274. if (!ffmpegExists) {
  275. await installFfmpeg();
  276. } else {
  277. console.log('✅ ffmpeg already installed');
  278. }
  279. // Final status check
  280. ytdlpExists = fs.existsSync(ytdlpPath);
  281. ffmpegExists = fs.existsSync(ffmpegPath);
  282. console.log('\n📊 Installation Summary:');
  283. console.log(` yt-dlp: ${ytdlpExists ? '✅ Installed' : '❌ Missing'}`);
  284. console.log(` ffmpeg: ${ffmpegExists ? '✅ Installed' : '❌ Missing'}`);
  285. if (ytdlpExists && ffmpegExists) {
  286. console.log('\n✨ Setup complete! All binaries installed successfully');
  287. } else {
  288. console.log('\n⚠️ Some binaries could not be installed automatically');
  289. console.log(' Please install them manually:');
  290. if (!ytdlpExists) {
  291. console.log(' - yt-dlp: https://github.com/yt-dlp/yt-dlp/releases');
  292. }
  293. if (!ffmpegExists) {
  294. console.log(' - ffmpeg: https://ffmpeg.org/download.html');
  295. }
  296. }
  297. console.log('\n🎯 Development Commands:');
  298. console.log('- npm run dev # Run in development mode');
  299. console.log('- npm start # Run in production mode');
  300. console.log('- npm run build # Build for current platform');
  301. console.log('- npm test # Run tests');
  302. }
  303. // Run setup
  304. main().catch((error) => {
  305. console.error('\n❌ Setup failed:', error.message);
  306. process.exit(1);
  307. });