download-ripgrep.mjs 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. #!/usr/bin/env node
  2. /**
  3. * Download ripgrep binaries for all target platforms
  4. * This script downloads official ripgrep binaries from GitHub releases
  5. * and extracts them to dist-standalone/ripgrep-binaries/
  6. */
  7. import { exec } from "child_process"
  8. import fs from "fs"
  9. import https from "https"
  10. import path from "path"
  11. import { pipeline } from "stream/promises"
  12. import * as tar from "tar"
  13. import { promisify } from "util"
  14. import { createGunzip } from "zlib"
  15. const execAsync = promisify(exec)
  16. const RIPGREP_VERSION = "14.1.1"
  17. const OUTPUT_DIR = "dist-standalone/ripgrep-binaries"
  18. // Platform configurations
  19. const PLATFORMS = [
  20. {
  21. name: "darwin-x64",
  22. archiveName: `ripgrep-${RIPGREP_VERSION}-x86_64-apple-darwin.tar.gz`,
  23. url: `https://github.com/BurntSushi/ripgrep/releases/download/${RIPGREP_VERSION}/ripgrep-${RIPGREP_VERSION}-x86_64-apple-darwin.tar.gz`,
  24. binaryPath: "rg",
  25. isZip: false,
  26. },
  27. {
  28. name: "darwin-arm64",
  29. archiveName: `ripgrep-${RIPGREP_VERSION}-aarch64-apple-darwin.tar.gz`,
  30. url: `https://github.com/BurntSushi/ripgrep/releases/download/${RIPGREP_VERSION}/ripgrep-${RIPGREP_VERSION}-aarch64-apple-darwin.tar.gz`,
  31. binaryPath: "rg",
  32. isZip: false,
  33. },
  34. {
  35. name: "linux-x64",
  36. archiveName: `ripgrep-${RIPGREP_VERSION}-x86_64-unknown-linux-musl.tar.gz`,
  37. url: `https://github.com/BurntSushi/ripgrep/releases/download/${RIPGREP_VERSION}/ripgrep-${RIPGREP_VERSION}-x86_64-unknown-linux-musl.tar.gz`,
  38. binaryPath: "rg",
  39. isZip: false,
  40. },
  41. {
  42. name: "win-x64",
  43. archiveName: `ripgrep-${RIPGREP_VERSION}-x86_64-pc-windows-msvc.zip`,
  44. url: `https://github.com/BurntSushi/ripgrep/releases/download/${RIPGREP_VERSION}/ripgrep-${RIPGREP_VERSION}-x86_64-pc-windows-msvc.zip`,
  45. binaryPath: "rg.exe",
  46. isZip: true,
  47. },
  48. ]
  49. /**
  50. * Download a file from a URL
  51. */
  52. async function downloadFile(url, destPath) {
  53. return new Promise((resolve, reject) => {
  54. console.log(` Downloading: ${url}`)
  55. const file = fs.createWriteStream(destPath)
  56. https
  57. .get(url, (response) => {
  58. if (response.statusCode === 302 || response.statusCode === 301) {
  59. // Handle redirect
  60. return downloadFile(response.headers.location, destPath).then(resolve).catch(reject)
  61. }
  62. if (response.statusCode !== 200) {
  63. reject(new Error(`Failed to download: ${response.statusCode} ${response.statusMessage}`))
  64. return
  65. }
  66. response.pipe(file)
  67. file.on("finish", () => {
  68. file.close()
  69. resolve()
  70. })
  71. })
  72. .on("error", (err) => {
  73. fs.unlink(destPath, () => {}) // Delete the file on error
  74. reject(err)
  75. })
  76. file.on("error", (err) => {
  77. fs.unlink(destPath, () => {}) // Delete the file on error
  78. reject(err)
  79. })
  80. })
  81. }
  82. /**
  83. * Extract a tar.gz file
  84. */
  85. async function extractTarGz(tarPath, destDir) {
  86. console.log(` Extracting tar.gz to: ${destDir}`)
  87. return pipeline(
  88. fs.createReadStream(tarPath),
  89. createGunzip(),
  90. tar.extract({
  91. cwd: destDir,
  92. strip: 1, // Remove the top-level directory from the archive
  93. }),
  94. )
  95. }
  96. /**
  97. * Extract a zip file using unzip command
  98. */
  99. async function extractZip(zipPath, destDir) {
  100. console.log(` Extracting zip to: ${destDir}`)
  101. try {
  102. // Use -o to overwrite existing files without prompting
  103. await execAsync(`unzip -o -q "${zipPath}" -d "${destDir}"`)
  104. // Find the extracted directory (usually ripgrep-VERSION-arch)
  105. const items = fs.readdirSync(destDir)
  106. const extractedDir = items.find((item) => item.startsWith("ripgrep-"))
  107. if (extractedDir) {
  108. // Move files from subdirectory to destDir
  109. const subDir = path.join(destDir, extractedDir)
  110. const files = fs.readdirSync(subDir)
  111. for (const file of files) {
  112. const srcPath = path.join(subDir, file)
  113. const destPath = path.join(destDir, file)
  114. // Remove destination if it exists (to avoid ENOTEMPTY error)
  115. if (fs.existsSync(destPath)) {
  116. const stats = fs.statSync(destPath)
  117. if (stats.isDirectory()) {
  118. fs.rmSync(destPath, { recursive: true, force: true })
  119. } else {
  120. fs.unlinkSync(destPath)
  121. }
  122. }
  123. fs.renameSync(srcPath, destPath)
  124. }
  125. // Remove the now-empty subdirectory
  126. fs.rmdirSync(subDir)
  127. }
  128. } catch (error) {
  129. throw new Error(`Failed to extract zip: ${error.message}`)
  130. }
  131. }
  132. /**
  133. * Download and extract ripgrep for a specific platform
  134. */
  135. async function downloadRipgrepForPlatform(platform) {
  136. console.log(`\n📦 Processing ${platform.name}...`)
  137. const platformDir = path.join(OUTPUT_DIR, platform.name)
  138. const archivePath = path.join(OUTPUT_DIR, platform.archiveName)
  139. // Create output directory
  140. fs.mkdirSync(platformDir, { recursive: true })
  141. try {
  142. // Download
  143. await downloadFile(platform.url, archivePath)
  144. console.log(` ✓ Downloaded`)
  145. // Extract
  146. if (platform.isZip) {
  147. await extractZip(archivePath, platformDir)
  148. } else {
  149. await extractTarGz(archivePath, platformDir)
  150. }
  151. console.log(` ✓ Extracted`)
  152. // Verify the binary exists
  153. const binaryPath = path.join(platformDir, platform.binaryPath)
  154. if (!fs.existsSync(binaryPath)) {
  155. throw new Error(`Binary not found at ${binaryPath}`)
  156. }
  157. // Make binary executable (Unix only)
  158. if (!platform.isZip) {
  159. fs.chmodSync(binaryPath, 0o755)
  160. }
  161. console.log(` ✓ Binary ready: ${binaryPath}`)
  162. // Clean up archive file
  163. fs.unlinkSync(archivePath)
  164. console.log(` ✓ Cleaned up`)
  165. return true
  166. } catch (error) {
  167. console.error(` ✗ Failed: ${error.message}`)
  168. throw error
  169. }
  170. }
  171. /**
  172. * Main function
  173. */
  174. async function main() {
  175. console.log("🚀 Ripgrep Binary Downloader")
  176. console.log(` Version: ${RIPGREP_VERSION}`)
  177. console.log(` Output: ${OUTPUT_DIR}`)
  178. // Create output directory
  179. fs.mkdirSync(OUTPUT_DIR, { recursive: true })
  180. // Download for all platforms
  181. const results = []
  182. for (const platform of PLATFORMS) {
  183. try {
  184. await downloadRipgrepForPlatform(platform)
  185. results.push({ platform: platform.name, success: true })
  186. } catch (error) {
  187. results.push({ platform: platform.name, success: false, error: error.message })
  188. }
  189. }
  190. // Print summary
  191. console.log("\n" + "=".repeat(50))
  192. console.log("📊 Summary:")
  193. console.log("=".repeat(50))
  194. let successCount = 0
  195. for (const result of results) {
  196. const status = result.success ? "✅" : "❌"
  197. console.log(`${status} ${result.platform}`)
  198. if (result.success) {
  199. successCount++
  200. } else {
  201. console.log(` Error: ${result.error}`)
  202. }
  203. }
  204. console.log("=".repeat(50))
  205. console.log(`✓ ${successCount}/${PLATFORMS.length} platforms successful`)
  206. if (successCount < PLATFORMS.length) {
  207. process.exit(1)
  208. }
  209. console.log("\n✅ All ripgrep binaries downloaded successfully!")
  210. }
  211. // Run the script
  212. main().catch((error) => {
  213. console.error("\n❌ Fatal error:", error)
  214. process.exit(1)
  215. })