nvenv
Version:
Python venv-like Node.js environment manager - project-local Node.js installation without global environment pollution
102 lines (80 loc) • 2.69 kB
JavaScript
const https = require('https');
const fs = require('fs');
const path = require('path');
const { shouldBeSilent, log } = require('./utils');
/**
* Download a file from URL to destination with progress indicator
* @param {string} url - URL to download from
* @param {string} destPath - Destination file path
* @param {object} options - Options { silent: boolean }
* @returns {Promise<void>}
*/
function downloadFile(url, destPath, options = {}) {
return new Promise((resolve, reject) => {
const silent = shouldBeSilent(options);
// Create destination directory if it doesn't exist
const destDir = path.dirname(destPath);
if (!fs.existsSync(destDir)) {
fs.mkdirSync(destDir, { recursive: true });
}
log(`Downloading from: ${url}`, options);
log(`Saving to: ${destPath}`, options);
const file = fs.createWriteStream(destPath);
let downloadedBytes = 0;
let totalBytes = 0;
const request = https.get(url, (response) => {
// Handle redirects
if (response.statusCode === 301 || response.statusCode === 302) {
file.close();
fs.unlinkSync(destPath);
const redirectUrl = response.headers.location;
log(`Following redirect to: ${redirectUrl}`, options);
return downloadFile(redirectUrl, destPath, options)
.then(resolve)
.catch(reject);
}
if (response.statusCode !== 200) {
file.close();
fs.unlinkSync(destPath);
reject(new Error(`Failed to download: HTTP ${response.statusCode}`));
return;
}
totalBytes = parseInt(response.headers['content-length'], 10);
response.on('data', (chunk) => {
downloadedBytes += chunk.length;
if (!silent && totalBytes) {
const percent = ((downloadedBytes / totalBytes) * 100).toFixed(1);
const downloadedMB = (downloadedBytes / 1024 / 1024).toFixed(1);
const totalMB = (totalBytes / 1024 / 1024).toFixed(1);
process.stdout.write(
`\rProgress: ${percent}% (${downloadedMB}MB / ${totalMB}MB)`
);
}
});
response.pipe(file);
file.on('finish', () => {
file.close();
log('\nDownload completed!', options);
resolve();
});
});
request.on('error', (err) => {
file.close();
// Delete partially downloaded file
if (fs.existsSync(destPath)) {
fs.unlinkSync(destPath);
}
reject(err);
});
file.on('error', (err) => {
file.close();
if (fs.existsSync(destPath)) {
fs.unlinkSync(destPath);
}
reject(err);
});
});
}
module.exports = {
downloadFile
};