You're Invited:Meet the Socket Team at BlackHat and DEF CON in Las Vegas, Aug 4-6.RSVP
Socket
Book a DemoInstallSign in
Socket

git-ripper

Package Overview
Dependencies
Maintainers
1
Versions
30
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

git-ripper - npm Package Compare versions

Comparing version

to
1.3.4

2

package.json
{
"name": "git-ripper",
"version": "1.3.1",
"version": "1.3.4",
"description": "CLI tool that lets you download specific folders from GitHub repositories without cloning the entire repo.",

@@ -5,0 +5,0 @@ "main": "src/index.js",

@@ -8,2 +8,4 @@ import axios from "axios";

import pLimit from "p-limit";
import chalk from "chalk";
import prettyBytes from "pretty-bytes";

@@ -17,3 +19,24 @@ // Set concurrency limit (adjustable based on network performance)

// Define spinner animation frames
const spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
// Alternative progress bar characters for more visual appeal
const progressChars = {
complete: '▰', // Alternative: '■', '●', '◆', '▣'
incomplete: '▱', // Alternative: '□', '○', '◇', '▢'
};
// Track frame index for spinner animation
let spinnerFrameIndex = 0;
/**
* Returns the next spinner frame for animation
* @returns {string} - The spinner character
*/
const getSpinnerFrame = () => {
const frame = spinnerFrames[spinnerFrameIndex];
spinnerFrameIndex = (spinnerFrameIndex + 1) % spinnerFrames.length;
return frame;
};
/**
* Fetches the contents of a folder from a GitHub repository

@@ -58,5 +81,14 @@ * @param {string} owner - Repository owner

fs.writeFileSync(outputPath, Buffer.from(response.data));
return { filePath, success: true };
return {
filePath,
success: true,
size: response.data.length
};
} catch (error) {
return { filePath, success: false, error: error.message };
return {
filePath,
success: false,
error: error.message,
size: 0
};
}

@@ -66,2 +98,49 @@ };

/**
* Creates a simplified progress bar renderer with animation
* @param {string} owner - Repository owner
* @param {string} repo - Repository name
* @param {string} folderPath - Path to the folder
* @returns {Function} - Function to render progress bar
*/
const createProgressRenderer = (owner, repo, folderPath) => {
// Default terminal width
const terminalWidth = process.stdout.columns || 80;
return (options, params, payload) => {
try {
const { value, total, startTime } = params;
const { downloadedSize = 0 } = payload || { downloadedSize: 0 };
// Calculate progress percentage
const progress = Math.min(1, Math.max(0, value / Math.max(1, total)));
const percentage = Math.floor(progress * 100);
// Calculate elapsed time
const elapsedSecs = Math.max(0.1, (Date.now() - startTime) / 1000);
// Create the progress bar
const barLength = Math.max(20, Math.min(40, Math.floor(terminalWidth / 2)));
const completedLength = Math.round(barLength * progress);
const remainingLength = barLength - completedLength;
// Build the bar with custom progress characters
const completedBar = chalk.greenBright(progressChars.complete.repeat(completedLength));
const remainingBar = chalk.gray(progressChars.incomplete.repeat(remainingLength));
// Add spinner for animation
const spinner = chalk.cyanBright(getSpinnerFrame());
// Format the output
const progressInfo = `${chalk.cyan(`${value}/${total}`)} files`;
const sizeInfo = prettyBytes(downloadedSize || 0);
return `${spinner} ${completedBar}${remainingBar} ${chalk.yellow(percentage + '%')} | ${progressInfo} | ${chalk.magenta(sizeInfo)}`;
} catch (error) {
// Fallback to a very simple progress indicator
return `${Math.floor((params.value / params.total) * 100)}% complete`;
}
};
};
/**
* Downloads all files from a folder in a GitHub repository

@@ -77,43 +156,87 @@ * @param {Object} repoInfo - Object containing repository information

const downloadFolder = async ({ owner, repo, branch, folderPath }, outputDir) => {
console.log(`Cloning ${folderPath} from ${owner}/${repo} (${branch})...`);
console.log(chalk.cyan(`Analyzing repository structure for ${owner}/${repo}...`));
const contents = await fetchFolderContents(owner, repo, branch, folderPath);
if (contents.length === 0) {
console.log(`No files found in ${folderPath}`);
return;
}
try {
const contents = await fetchFolderContents(owner, repo, branch, folderPath);
if (!contents || contents.length === 0) {
console.log(chalk.yellow(`No files found in ${folderPath || 'repository root'}`));
console.log(chalk.green(`Folder cloned successfully!`));
return;
}
let totalFiles = contents.filter(item => item.type === "blob").length;
console.log(`Preparing to download ${totalFiles} files/folders...`);
// Progress bar setup
const bar = new cliProgress.SingleBar({}, cliProgress.Presets.shades_classic);
bar.start(totalFiles, 0);
// Create download promises with concurrency control
const fileDownloadPromises = contents
.filter((item) => item.type === "blob")
.map((item) => {
const files = contents.filter(item => item.type === "blob");
const totalFiles = files.length;
console.log(chalk.cyan(`Downloading ${totalFiles} files from ${chalk.white(owner + '/' + repo)}...`));
// Simplified progress bar setup
const progressBar = new cliProgress.SingleBar({
format: createProgressRenderer(owner, repo, folderPath),
hideCursor: true,
clearOnComplete: false,
stopOnComplete: true,
forceRedraw: true
});
// Track download metrics
let downloadedSize = 0;
const startTime = Date.now();
// Start progress bar
progressBar.start(totalFiles, 0, {
downloadedSize: 0,
startTime
});
// Create download promises with concurrency control
const fileDownloadPromises = files.map((item) => {
// Keep the original structure by preserving the folder name
// For a path like "src/components/Button.js" relative to "src", store as "components/Button.js"
const relativePath = item.path.substring(folderPath.length).replace(/^\//, "");
let relativePath = item.path;
if (folderPath && folderPath.trim() !== '') {
relativePath = item.path.substring(folderPath.length).replace(/^\//, "");
}
const outputFilePath = path.join(outputDir, relativePath);
return limit(async () => {
const result = await downloadFile(owner, repo, branch, item.path, outputFilePath);
bar.increment(); // Update progress bar
return result;
try {
const result = await downloadFile(owner, repo, branch, item.path, outputFilePath);
// Update progress metrics
if (result.success) {
downloadedSize += (result.size || 0);
}
// Update progress bar with current metrics
progressBar.increment(1, {
downloadedSize
});
return result;
} catch (error) {
return { filePath: item.path, success: false, error: error.message, size: 0 };
}
});
});
// Execute downloads in parallel
const results = await Promise.all(fileDownloadPromises);
bar.stop(); // Stop progress bar
// Execute downloads in parallel
const results = await Promise.all(fileDownloadPromises);
progressBar.stop();
console.log(); // Add an empty line after progress bar
// Count successful and failed downloads
const succeeded = results.filter((r) => r.success).length;
const failed = results.filter((r) => !r.success).length;
// Count successful and failed downloads
const succeeded = results.filter((r) => r.success).length;
const failed = results.filter((r) => !r.success).length;
console.log(`Downloaded ${succeeded} files successfully${failed > 0 ? `, ${failed} files failed` : ""}`);
if (failed > 0) {
console.log(chalk.yellow(`Downloaded ${succeeded} files successfully, ${failed} files failed`));
} else {
console.log(chalk.green(` All ${succeeded} files downloaded successfully!`));
}
console.log(chalk.green(`Folder cloned successfully!`));
} catch (error) {
console.error(chalk.red(`Error downloading folder: ${error.message}`));
}
};

@@ -120,0 +243,0 @@

@@ -7,3 +7,3 @@ import { program } from 'commander';

program
.version('1.3.1')
.version('1.3.4')
.description('Clone specific folders from GitHub repositories')

@@ -10,0 +10,0 @@ .argument('<url>', 'GitHub URL of the folder to clone')