import * as plugins from './plugins.js'; import * as paths from './paths.js'; import { promises as fs } from 'fs'; import { platform } from 'os'; interface IDenoRelease { name: string; assets: IAsset[]; } interface IAsset { name: string; browser_download_url: string; } export class DenoDownloader { private denoBinaryPath: string | null = null; private async getDenoDownloadUrl(): Promise { const osPlatform = platform(); const arch = process.arch; let osPart: string; switch (osPlatform) { case 'darwin': osPart = 'apple-darwin'; break; case 'win32': osPart = 'pc-windows-msvc'; break; case 'linux': osPart = 'unknown-linux-gnu'; break; default: throw new Error(`Unsupported platform: ${osPlatform}`); } const archPart = arch === 'x64' ? 'x86_64' : 'aarch64'; const releasesResponse = await fetch('https://api.github.com/repos/denoland/deno/releases/latest'); if (!releasesResponse.ok) { throw new Error(`Failed to fetch Deno releases: ${releasesResponse.statusText}`); } const release: IDenoRelease = await releasesResponse.json(); const executableName = `deno-${archPart}-${osPart}.zip`; const asset = release.assets.find(a => a.name === executableName); if (!asset) { throw new Error(`Deno release for ${osPlatform} (${arch}) not found.`); } return asset.browser_download_url; } private async downloadDeno(url: string, outputPath: string): Promise { const response = await fetch(url); if (!response.ok) { throw new Error(`Failed to download Deno: ${response.statusText}`); } const buffer = await response.arrayBuffer(); await fs.writeFile(outputPath, Buffer.from(buffer)); } /** * Get the path to the Deno binary after download */ public getDenoBinaryPath(): string | null { return this.denoBinaryPath; } /** * Download and extract Deno to the specified directory * @param outputPath Path where the deno.zip will be downloaded * @returns Path to the Deno binary */ public async download(outputPath: string = './deno.zip'): Promise { const fsInstance = new plugins.smartfs.SmartFs(new plugins.smartfs.SmartFsProviderNode()); const directory = plugins.path.dirname(outputPath); const denoBinaryPath = plugins.path.join(directory, platform() === 'win32' ? 'deno.exe' : 'deno'); // Check if Deno is already downloaded if (await fsInstance.file(denoBinaryPath).exists()) { console.log(`Deno already exists at ${denoBinaryPath}`); this.denoBinaryPath = denoBinaryPath; return denoBinaryPath; } // Ensure the directory exists await fsInstance.directory(directory).create(); // Download Deno const url = await this.getDenoDownloadUrl(); await this.downloadDeno(url, outputPath); console.log(`Deno downloaded successfully to ${outputPath}`); // Extract the archive console.log(`Extracting deno.zip to ${directory}`); await plugins.smartarchive.SmartArchive.create() .file(outputPath) .extract(directory); // Make the binary executable (Unix-like systems) if (platform() !== 'win32') { const smartshellInstance = new plugins.smartshell.Smartshell({ executor: 'bash' }); await smartshellInstance.exec(`chmod +x "${denoBinaryPath}"`); } // Clean up the zip file try { await fsInstance.file(outputPath).delete(); } catch { // Ignore cleanup errors } this.denoBinaryPath = denoBinaryPath; return denoBinaryPath; } }