From a83c153531487bec08fa75bcad0640480471dc6b Mon Sep 17 00:00:00 2001 From: kangfenmao Date: Mon, 17 Mar 2025 17:25:56 +0800 Subject: [PATCH] refactor: simplify bun and uv installation scripts for improved clarity and functionality - Removed the getLatestBunVersion and getLatestUvVersion functions to streamline version handling. - Updated download functions to use temporary filenames and ensure proper cleanup of downloaded files. - Enhanced directory management by ensuring the output directory is correctly referenced and cleaned up if empty. - Refactored the install functions to directly use detected platform and architecture, improving readability and maintainability. --- resources/scripts/install-bun.js | 145 +++++++-------------------- resources/scripts/install-uv.js | 165 +++++++++++-------------------- src/main/utils/process.ts | 2 +- 3 files changed, 94 insertions(+), 218 deletions(-) diff --git a/resources/scripts/install-bun.js b/resources/scripts/install-bun.js index ddb20772..3449f017 100644 --- a/resources/scripts/install-bun.js +++ b/resources/scripts/install-bun.js @@ -24,55 +24,6 @@ const BUN_PACKAGES = { 'linux-musl-arm64': 'bun-linux-aarch64-musl.zip' } -/** - * Fetches the latest version of bun from GitHub API - * @returns {Promise} 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 @@ -129,34 +80,36 @@ async function downloadBunBinary(platform, arch, version = DEFAULT_BUN_VERSION, } // Create output directory structure - const archDir = path.join(os.homedir(), '.cherrystudio', 'bin') + const binDir = path.join(os.homedir(), '.cherrystudio', 'bin') // Ensure directories exist - fs.mkdirSync(archDir, { recursive: true }) + fs.mkdirSync(binDir, { 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) + const tempFilename = 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) + await downloadWithRedirects(downloadUrl, tempFilename) // Extract the zip file using adm-zip - console.log(`Extracting ${packageName} to ${archDir}...`) - const zip = new AdmZip(localFilename) + console.log(`Extracting ${packageName} to ${binDir}...`) + const zip = new AdmZip(tempFilename) 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) + const destPath = path.join(binDir, file) + fs.renameSync(sourcePath, destPath) // Set executable permissions for non-Windows platforms @@ -171,16 +124,29 @@ async function downloadBunBinary(platform, arch, version = DEFAULT_BUN_VERSION, } // Clean up - fs.unlinkSync(localFilename) - fs.rmdirSync(sourceDir, { recursive: true }) + fs.unlinkSync(tempFilename) + fs.rmSync(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) + // Clean up temporary file if it exists + if (fs.existsSync(tempFilename)) { + fs.unlinkSync(tempFilename) } + + // Check if binDir is empty and remove it if so + try { + const files = fs.readdirSync(binDir) + if (files.length === 0) { + fs.rmSync(binDir, { recursive: true }) + console.log(`Removed empty directory: ${binDir}`) + } + } catch (cleanupError) { + console.warn(`Warning: Failed to clean up directory: ${cleanupError.message}`) + } + return false } } @@ -192,8 +158,9 @@ function detectPlatformAndArch() { const platform = os.platform() const arch = os.arch() const isMusl = platform === 'linux' && detectIsMusl() + const isBaseline = platform === 'win32' - return { platform, arch, isMusl } + return { platform, arch, isMusl, isBaseline } } /** @@ -213,61 +180,17 @@ function detectIsMusl() { * 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()) + const version = DEFAULT_BUN_VERSION 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') + const { platform, arch, isMusl, isBaseline } = detectPlatformAndArch() - if (installAll) { - console.log(`Installing all bun ${version} binaries...`) - for (const platformKey in BUN_PACKAGES) { - let platform, - arch, - isMusl = false, - isBaseline = false + console.log( + `Installing bun ${version} for ${platform}-${arch}${isMusl ? ' (MUSL)' : ''}${isBaseline ? ' (baseline)' : ''}...` + ) - 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) - } + await downloadBunBinary(platform, arch, version, isMusl, isBaseline) } // Run the installation diff --git a/resources/scripts/install-uv.js b/resources/scripts/install-uv.js index cca0d3e3..0cc8d9e8 100644 --- a/resources/scripts/install-uv.js +++ b/resources/scripts/install-uv.js @@ -4,6 +4,7 @@ const os = require('os') const { execSync } = require('child_process') const https = require('https') const tar = require('tar') +const AdmZip = require('adm-zip') // Base URL for downloading uv binaries const UV_RELEASE_BASE_URL = 'https://github.com/astral-sh/uv/releases/download' @@ -66,51 +67,6 @@ async function downloadWithRedirects(url, destinationPath) { }) } -/** - * Fetches the latest version of uv from GitHub API - * @returns {Promise} The latest version tag (without 'v' prefix) - */ -async function getLatestUvVersion() { - return new Promise((resolve, reject) => { - const options = { - hostname: 'api.github.com', - path: '/repos/astral-sh/uv/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 'v' prefix if present - const version = 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 and extracts the uv binary for the specified platform and architecture * @param {string} platform Platform to download for (e.g., 'darwin', 'win32', 'linux') @@ -128,61 +84,82 @@ async function downloadUvBinary(platform, arch, version = DEFAULT_UV_VERSION, is } // Create output directory structure - const archDir = path.join(os.homedir(), '.cherrystudio', 'bin') + const binDir = path.join(os.homedir(), '.cherrystudio', 'bin') // Ensure directories exist - fs.mkdirSync(archDir, { recursive: true }) + fs.mkdirSync(binDir, { recursive: true }) // Download URL for the specific binary const downloadUrl = `${UV_RELEASE_BASE_URL}/${version}/${packageName}` const tempdir = os.tmpdir() - const localFilename = path.join(tempdir, packageName) + const tempFilename = path.join(tempdir, packageName) try { console.log(`Downloading uv ${version} for ${platformKey}...`) console.log(`URL: ${downloadUrl}`) - // Use the new download function - await downloadWithRedirects(downloadUrl, localFilename) + await downloadWithRedirects(downloadUrl, tempFilename) - // Extract files using tar - console.log(`Extracting ${packageName} to ${archDir}...`) - await tar.x({ - file: localFilename, - cwd: tempdir, - // zip 文件也可以用 tar 解压 - z: packageName.endsWith('.tar.gz') // 对于 .tar.gz 文件启用 gzip 解压 - }) + console.log(`Extracting ${packageName} to ${binDir}...`) - // 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) + // 根据文件扩展名选择解压方法 + if (packageName.endsWith('.zip')) { + // 使用 adm-zip 处理 zip 文件 + const zip = new AdmZip(tempFilename) + zip.extractAllTo(binDir, true) + fs.unlinkSync(tempFilename) + console.log(`Successfully installed uv ${version} for ${platform}-${arch}`) + return true + } else { + // tar.gz 文件的处理保持不变 + await tar.x({ + file: tempFilename, + cwd: tempdir, + z: true + }) - // 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}`) + // 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(binDir, file) + fs.renameSync(sourcePath, destPath) + + // Set executable permissions for non-Windows platforms + if (platform !== 'win32') { + try { + 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 }) + // Clean up + fs.unlinkSync(tempFilename) + fs.rmSync(sourceDir, { recursive: true }) + } console.log(`Successfully installed uv ${version} for ${platform}-${arch}`) return true } catch (error) { console.error(`Error installing uv for ${platformKey}: ${error.message}`) - if (fs.existsSync(localFilename)) { - fs.unlinkSync(localFilename) + + if (fs.existsSync(tempFilename)) { + fs.unlinkSync(tempFilename) } + + // Check if binDir is empty and remove it if so + try { + const files = fs.readdirSync(binDir) + if (files.length === 0) { + fs.rmSync(binDir, { recursive: true }) + console.log(`Removed empty directory: ${binDir}`) + } + } catch (cleanupError) { + console.warn(`Warning: Failed to clean up directory: ${cleanupError.message}`) + } + return false } } @@ -215,39 +192,15 @@ function detectIsMusl() { * Main function to install uv */ async function installUv() { - 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 getLatestUvVersion()) + const version = DEFAULT_UV_VERSION console.log(`Using uv 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 installAll = args.includes('--all') + const { platform, arch, isMusl } = detectPlatformAndArch() - if (installAll) { - console.log(`Installing all uv ${version} binaries...`) - for (const platformKey in UV_PACKAGES) { - const [platformArch, musl] = platformKey.split('-musl-') - if (musl) { - const [platform, arch] = platformArch.split('-') - await downloadUvBinary(platform, arch, version, true) - } else { - const [platform, arch] = platformKey.split('-') - await downloadUvBinary(platform, arch, version, false) - } - } - } else { - const { platform, arch, isMusl } = detectPlatformAndArch() - const targetPlatform = specificPlatform || platform - const targetArch = specificArch || arch - const targetMusl = specificMusl || isMusl + console.log(`Installing uv ${version} for ${platform}-${arch}${isMusl ? ' (MUSL)' : ''}...`) - console.log(`Installing uv ${version} for ${targetPlatform}-${targetArch}${targetMusl ? ' (MUSL)' : ''}...`) - await downloadUvBinary(targetPlatform, targetArch, version, targetMusl) - } + await downloadUvBinary(platform, arch, version, isMusl) } // Run the installation diff --git a/src/main/utils/process.ts b/src/main/utils/process.ts index 19cbaa66..e4151c01 100644 --- a/src/main/utils/process.ts +++ b/src/main/utils/process.ts @@ -39,7 +39,7 @@ export async function getBinaryPath(name: string): Promise { let cmd = process.platform === 'win32' ? `${name}.exe` : name const binariesDir = path.join(os.homedir(), '.cherrystudio', 'bin') const binariesDirExists = await fs.existsSync(binariesDir) - cmd = binariesDirExists ? path.join(binariesDir, name) : name + cmd = binariesDirExists ? path.join(binariesDir, cmd) : name return cmd }