- Added the `tar` package to handle extraction of `.tar.gz` files in the `install-uv.js` script. - Implemented a new `downloadWithRedirects` function in both `install-bun.js` and `install-uv.js` for improved file downloading with redirect handling. - Refactored the extraction process in both scripts to utilize Node.js file system methods and the `adm-zip` package for better file management and cleanup.
278 lines
8.6 KiB
JavaScript
278 lines
8.6 KiB
JavaScript
const fs = require('fs')
|
|
const path = require('path')
|
|
const os = require('os')
|
|
const { execSync } = require('child_process')
|
|
const https = require('https')
|
|
const AdmZip = require('adm-zip')
|
|
|
|
// Base URL for downloading bun binaries
|
|
const BUN_RELEASE_BASE_URL = 'https://github.com/oven-sh/bun/releases/download'
|
|
const DEFAULT_BUN_VERSION = '1.2.5' // Default fallback version
|
|
|
|
// Mapping of platform+arch to binary package name
|
|
const BUN_PACKAGES = {
|
|
'darwin-arm64': 'bun-darwin-aarch64.zip',
|
|
'darwin-x64': 'bun-darwin-x64.zip',
|
|
'win32-x64': 'bun-windows-x64.zip',
|
|
'win32-x64-baseline': 'bun-windows-x64-baseline.zip',
|
|
'linux-x64': 'bun-linux-x64.zip',
|
|
'linux-x64-baseline': 'bun-linux-x64-baseline.zip',
|
|
'linux-arm64': 'bun-linux-aarch64.zip',
|
|
// MUSL variants
|
|
'linux-musl-x64': 'bun-linux-x64-musl.zip',
|
|
'linux-musl-x64-baseline': 'bun-linux-x64-musl-baseline.zip',
|
|
'linux-musl-arm64': 'bun-linux-aarch64-musl.zip'
|
|
}
|
|
|
|
/**
|
|
* Fetches the latest version of bun from GitHub API
|
|
* @returns {Promise<string>} The latest version tag (without 'bun-v' prefix)
|
|
*/
|
|
async function getLatestBunVersion() {
|
|
return new Promise((resolve, reject) => {
|
|
const options = {
|
|
hostname: 'api.github.com',
|
|
path: '/repos/oven-sh/bun/releases/latest',
|
|
headers: {
|
|
'User-Agent': 'cherry-studio-install-script'
|
|
}
|
|
}
|
|
|
|
const req = https.get(options, (res) => {
|
|
if (res.statusCode !== 200) {
|
|
reject(new Error(`Request failed with status code ${res.statusCode}`))
|
|
return
|
|
}
|
|
|
|
let data = ''
|
|
res.on('data', (chunk) => {
|
|
data += chunk
|
|
})
|
|
|
|
res.on('end', () => {
|
|
try {
|
|
const release = JSON.parse(data)
|
|
// Remove the 'bun-v' prefix if present
|
|
const version = release.tag_name.startsWith('bun-v')
|
|
? release.tag_name.substring(5)
|
|
: release.tag_name.startsWith('v')
|
|
? release.tag_name.substring(1)
|
|
: release.tag_name
|
|
resolve(version)
|
|
} catch (error) {
|
|
reject(new Error(`Failed to parse GitHub API response: ${error.message}`))
|
|
}
|
|
})
|
|
})
|
|
|
|
req.on('error', (error) => {
|
|
reject(new Error(`Failed to fetch latest version: ${error.message}`))
|
|
})
|
|
|
|
req.end()
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Downloads a file from a URL with redirect handling
|
|
* @param {string} url The URL to download from
|
|
* @param {string} destinationPath The path to save the file to
|
|
* @returns {Promise<void>}
|
|
*/
|
|
async function downloadWithRedirects(url, destinationPath) {
|
|
return new Promise((resolve, reject) => {
|
|
const file = fs.createWriteStream(destinationPath)
|
|
const request = (url) => {
|
|
https
|
|
.get(url, (response) => {
|
|
if (response.statusCode === 302 || response.statusCode === 301) {
|
|
// Handle redirect
|
|
request(response.headers.location)
|
|
return
|
|
}
|
|
|
|
if (response.statusCode !== 200) {
|
|
reject(new Error(`Failed to download: ${response.statusCode}`))
|
|
return
|
|
}
|
|
|
|
response.pipe(file)
|
|
file.on('finish', () => {
|
|
file.close(resolve)
|
|
})
|
|
})
|
|
.on('error', reject)
|
|
}
|
|
|
|
request(url)
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Downloads and extracts the bun binary for the specified platform and architecture
|
|
* @param {string} platform Platform to download for (e.g., 'darwin', 'win32', 'linux')
|
|
* @param {string} arch Architecture to download for (e.g., 'x64', 'arm64')
|
|
* @param {string} version Version of bun to download
|
|
* @param {boolean} isMusl Whether to use MUSL variant for Linux
|
|
* @param {boolean} isBaseline Whether to use baseline variant
|
|
*/
|
|
async function downloadBunBinary(platform, arch, version = DEFAULT_BUN_VERSION, isMusl = false, isBaseline = false) {
|
|
let platformKey = isMusl ? `${platform}-musl-${arch}` : `${platform}-${arch}`
|
|
if (isBaseline) {
|
|
platformKey += '-baseline'
|
|
}
|
|
const packageName = BUN_PACKAGES[platformKey]
|
|
|
|
if (!packageName) {
|
|
console.error(`No binary available for ${platformKey}`)
|
|
return false
|
|
}
|
|
|
|
// Create output directory structure
|
|
const archDir = path.join(os.homedir(), '.cherrystudio', 'bin')
|
|
// Ensure directories exist
|
|
fs.mkdirSync(archDir, { recursive: true })
|
|
|
|
// Download URL for the specific binary
|
|
const downloadUrl = `${BUN_RELEASE_BASE_URL}/bun-v${version}/${packageName}`
|
|
const tempdir = os.tmpdir()
|
|
// Create a temporary file for the downloaded binary
|
|
const localFilename = path.join(tempdir, packageName)
|
|
|
|
try {
|
|
console.log(`Downloading bun ${version} for ${platformKey}...`)
|
|
console.log(`URL: ${downloadUrl}`)
|
|
|
|
// Use the new download function
|
|
await downloadWithRedirects(downloadUrl, localFilename)
|
|
|
|
// Extract the zip file using adm-zip
|
|
console.log(`Extracting ${packageName} to ${archDir}...`)
|
|
const zip = new AdmZip(localFilename)
|
|
zip.extractAllTo(tempdir, true)
|
|
|
|
// Move files using Node.js fs
|
|
const sourceDir = path.join(tempdir, packageName.split('.')[0])
|
|
const files = fs.readdirSync(sourceDir)
|
|
for (const file of files) {
|
|
const sourcePath = path.join(sourceDir, file)
|
|
const destPath = path.join(archDir, file)
|
|
fs.renameSync(sourcePath, destPath)
|
|
|
|
// Set executable permissions for non-Windows platforms
|
|
if (platform !== 'win32') {
|
|
try {
|
|
// 755 permission: rwxr-xr-x
|
|
fs.chmodSync(destPath, '755')
|
|
} catch (error) {
|
|
console.warn(`Warning: Failed to set executable permissions: ${error.message}`)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Clean up
|
|
fs.unlinkSync(localFilename)
|
|
fs.rmdirSync(sourceDir, { recursive: true })
|
|
|
|
console.log(`Successfully installed bun ${version} for ${platformKey}`)
|
|
return true
|
|
} catch (error) {
|
|
console.error(`Error installing bun for ${platformKey}: ${error.message}`)
|
|
if (fs.existsSync(localFilename)) {
|
|
fs.unlinkSync(localFilename)
|
|
}
|
|
return false
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Detects current platform and architecture
|
|
*/
|
|
function detectPlatformAndArch() {
|
|
const platform = os.platform()
|
|
const arch = os.arch()
|
|
const isMusl = platform === 'linux' && detectIsMusl()
|
|
|
|
return { platform, arch, isMusl }
|
|
}
|
|
|
|
/**
|
|
* Attempts to detect if running on MUSL libc
|
|
*/
|
|
function detectIsMusl() {
|
|
try {
|
|
// Simple check for Alpine Linux which uses MUSL
|
|
const output = execSync('cat /etc/os-release').toString()
|
|
return output.toLowerCase().includes('alpine')
|
|
} catch (error) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Main function to install bun
|
|
*/
|
|
async function installBun() {
|
|
const args = process.argv.slice(2)
|
|
const specifiedVersion = args.find((arg) => !arg.startsWith('--'))
|
|
|
|
// Get the latest version if no specific version is provided
|
|
const version = specifiedVersion || (await getLatestBunVersion())
|
|
console.log(`Using bun version: ${version}`)
|
|
|
|
const specificPlatform = args.find((arg) => arg.startsWith('--platform='))?.split('=')[1]
|
|
const specificArch = args.find((arg) => arg.startsWith('--arch='))?.split('=')[1]
|
|
const specificMusl = args.includes('--musl')
|
|
const specificBaseline = args.includes('--baseline')
|
|
const installAll = args.includes('--all')
|
|
|
|
if (installAll) {
|
|
console.log(`Installing all bun ${version} binaries...`)
|
|
for (const platformKey in BUN_PACKAGES) {
|
|
let platform,
|
|
arch,
|
|
isMusl = false,
|
|
isBaseline = false
|
|
|
|
if (platformKey.includes('-musl-')) {
|
|
const [platformPart, archPart] = platformKey.split('-musl-')
|
|
platform = platformPart
|
|
isMusl = true
|
|
|
|
if (archPart.includes('-baseline')) {
|
|
;[arch] = archPart.split('-baseline')
|
|
isBaseline = true
|
|
} else {
|
|
arch = archPart
|
|
}
|
|
} else if (platformKey.includes('-baseline')) {
|
|
const [platformPart, archPart] = platformKey.split('-')
|
|
platform = platformPart
|
|
arch = archPart.replace('-baseline', '')
|
|
isBaseline = true
|
|
} else {
|
|
;[platform, arch] = platformKey.split('-')
|
|
}
|
|
|
|
await downloadBunBinary(platform, arch, version, isMusl, isBaseline)
|
|
}
|
|
} else {
|
|
const { platform, arch, isMusl } = detectPlatformAndArch()
|
|
const targetPlatform = specificPlatform || platform
|
|
const targetArch = specificArch || arch
|
|
const targetMusl = specificMusl || isMusl
|
|
const targetBaseline = specificBaseline || false
|
|
|
|
console.log(
|
|
`Installing bun ${version} for ${targetPlatform}-${targetArch}${targetMusl ? ' (MUSL)' : ''}${targetBaseline ? ' (baseline)' : ''}...`
|
|
)
|
|
await downloadBunBinary(targetPlatform, targetArch, version, targetMusl, targetBaseline)
|
|
}
|
|
}
|
|
|
|
// Run the installation
|
|
installBun().catch((error) => {
|
|
console.error('Installation failed:', error)
|
|
process.exit(1)
|
|
})
|