git-ripper 1.3.0 → 1.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/downloader.js +155 -32
- package/src/index.js +1 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/git-ripper)
|
|
6
6
|
[](https://github.com/sairajB/git-ripper/blob/main/LICENSE)
|
|
7
|
-
[](https://www.npmjs.com/package/git-ripper)
|
|
8
8
|
[](https://github.com/sairajB/git-ripper/issues)
|
|
9
9
|
[](https://github.com/sairajB/git-ripper/stargazers)
|
|
10
10
|
|
package/package.json
CHANGED
package/src/downloader.js
CHANGED
|
@@ -5,6 +5,8 @@ import { fileURLToPath } from "url";
|
|
|
5
5
|
import { dirname } from "path";
|
|
6
6
|
import cliProgress from "cli-progress";
|
|
7
7
|
import pLimit from "p-limit";
|
|
8
|
+
import chalk from "chalk";
|
|
9
|
+
import prettyBytes from "pretty-bytes";
|
|
8
10
|
|
|
9
11
|
// Set concurrency limit (adjustable based on network performance)
|
|
10
12
|
const limit = pLimit(500);
|
|
@@ -13,6 +15,27 @@ const limit = pLimit(500);
|
|
|
13
15
|
const __filename = fileURLToPath(import.meta.url);
|
|
14
16
|
const __dirname = dirname(__filename);
|
|
15
17
|
|
|
18
|
+
// Define spinner animation frames
|
|
19
|
+
const spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
20
|
+
// Alternative progress bar characters for more visual appeal
|
|
21
|
+
const progressChars = {
|
|
22
|
+
complete: '▰', // Alternative: '■', '●', '◆', '▣'
|
|
23
|
+
incomplete: '▱', // Alternative: '□', '○', '◇', '▢'
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// Track frame index for spinner animation
|
|
27
|
+
let spinnerFrameIndex = 0;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Returns the next spinner frame for animation
|
|
31
|
+
* @returns {string} - The spinner character
|
|
32
|
+
*/
|
|
33
|
+
const getSpinnerFrame = () => {
|
|
34
|
+
const frame = spinnerFrames[spinnerFrameIndex];
|
|
35
|
+
spinnerFrameIndex = (spinnerFrameIndex + 1) % spinnerFrames.length;
|
|
36
|
+
return frame;
|
|
37
|
+
};
|
|
38
|
+
|
|
16
39
|
/**
|
|
17
40
|
* Fetches the contents of a folder from a GitHub repository
|
|
18
41
|
* @param {string} owner - Repository owner
|
|
@@ -53,12 +76,68 @@ const downloadFile = async (owner, repo, branch, filePath, outputPath) => {
|
|
|
53
76
|
const response = await axios.get(url, { responseType: "arraybuffer" });
|
|
54
77
|
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
55
78
|
fs.writeFileSync(outputPath, Buffer.from(response.data));
|
|
56
|
-
return {
|
|
79
|
+
return {
|
|
80
|
+
filePath,
|
|
81
|
+
success: true,
|
|
82
|
+
size: response.data.length
|
|
83
|
+
};
|
|
57
84
|
} catch (error) {
|
|
58
|
-
return {
|
|
85
|
+
return {
|
|
86
|
+
filePath,
|
|
87
|
+
success: false,
|
|
88
|
+
error: error.message,
|
|
89
|
+
size: 0
|
|
90
|
+
};
|
|
59
91
|
}
|
|
60
92
|
};
|
|
61
93
|
|
|
94
|
+
/**
|
|
95
|
+
* Creates a simplified progress bar renderer with animation
|
|
96
|
+
* @param {string} owner - Repository owner
|
|
97
|
+
* @param {string} repo - Repository name
|
|
98
|
+
* @param {string} folderPath - Path to the folder
|
|
99
|
+
* @returns {Function} - Function to render progress bar
|
|
100
|
+
*/
|
|
101
|
+
const createProgressRenderer = (owner, repo, folderPath) => {
|
|
102
|
+
// Default terminal width
|
|
103
|
+
const terminalWidth = process.stdout.columns || 80;
|
|
104
|
+
|
|
105
|
+
return (options, params, payload) => {
|
|
106
|
+
try {
|
|
107
|
+
const { value, total, startTime } = params;
|
|
108
|
+
const { downloadedSize = 0 } = payload || { downloadedSize: 0 };
|
|
109
|
+
|
|
110
|
+
// Calculate progress percentage
|
|
111
|
+
const progress = Math.min(1, Math.max(0, value / Math.max(1, total)));
|
|
112
|
+
const percentage = Math.floor(progress * 100);
|
|
113
|
+
|
|
114
|
+
// Calculate elapsed time
|
|
115
|
+
const elapsedSecs = Math.max(0.1, (Date.now() - startTime) / 1000);
|
|
116
|
+
|
|
117
|
+
// Create the progress bar
|
|
118
|
+
const barLength = Math.max(20, Math.min(40, Math.floor(terminalWidth / 2)));
|
|
119
|
+
const completedLength = Math.round(barLength * progress);
|
|
120
|
+
const remainingLength = barLength - completedLength;
|
|
121
|
+
|
|
122
|
+
// Build the bar with custom progress characters
|
|
123
|
+
const completedBar = chalk.greenBright(progressChars.complete.repeat(completedLength));
|
|
124
|
+
const remainingBar = chalk.gray(progressChars.incomplete.repeat(remainingLength));
|
|
125
|
+
|
|
126
|
+
// Add spinner for animation
|
|
127
|
+
const spinner = chalk.cyanBright(getSpinnerFrame());
|
|
128
|
+
|
|
129
|
+
// Format the output
|
|
130
|
+
const progressInfo = `${chalk.cyan(`${value}/${total}`)} files`;
|
|
131
|
+
const sizeInfo = prettyBytes(downloadedSize || 0);
|
|
132
|
+
|
|
133
|
+
return `${spinner} ${completedBar}${remainingBar} ${chalk.yellow(percentage + '%')} | ${progressInfo} | ${chalk.magenta(sizeInfo)}`;
|
|
134
|
+
} catch (error) {
|
|
135
|
+
// Fallback to a very simple progress indicator
|
|
136
|
+
return `${Math.floor((params.value / params.total) * 100)}% complete`;
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
};
|
|
140
|
+
|
|
62
141
|
/**
|
|
63
142
|
* Downloads all files from a folder in a GitHub repository
|
|
64
143
|
* @param {Object} repoInfo - Object containing repository information
|
|
@@ -70,47 +149,91 @@ const downloadFile = async (owner, repo, branch, filePath, outputPath) => {
|
|
|
70
149
|
* @returns {Promise<void>} - Promise that resolves when all files are downloaded
|
|
71
150
|
*/
|
|
72
151
|
const downloadFolder = async ({ owner, repo, branch, folderPath }, outputDir) => {
|
|
73
|
-
console.log(`
|
|
152
|
+
console.log(chalk.cyan(`Analyzing repository structure for ${owner}/${repo}...`));
|
|
74
153
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
154
|
+
try {
|
|
155
|
+
const contents = await fetchFolderContents(owner, repo, branch, folderPath);
|
|
156
|
+
|
|
157
|
+
if (!contents || contents.length === 0) {
|
|
158
|
+
console.log(chalk.yellow(`No files found in ${folderPath || 'repository root'}`));
|
|
159
|
+
console.log(chalk.green(`Folder cloned successfully!`));
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
81
162
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
163
|
+
const files = contents.filter(item => item.type === "blob");
|
|
164
|
+
const totalFiles = files.length;
|
|
165
|
+
|
|
166
|
+
console.log(chalk.cyan(`Downloading ${totalFiles} files from ${chalk.white(owner + '/' + repo)}...`));
|
|
167
|
+
|
|
168
|
+
// Simplified progress bar setup
|
|
169
|
+
const progressBar = new cliProgress.SingleBar({
|
|
170
|
+
format: createProgressRenderer(owner, repo, folderPath),
|
|
171
|
+
hideCursor: true,
|
|
172
|
+
clearOnComplete: false,
|
|
173
|
+
stopOnComplete: true,
|
|
174
|
+
forceRedraw: true
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// Track download metrics
|
|
178
|
+
let downloadedSize = 0;
|
|
179
|
+
const startTime = Date.now();
|
|
180
|
+
|
|
181
|
+
// Start progress bar
|
|
182
|
+
progressBar.start(totalFiles, 0, {
|
|
183
|
+
downloadedSize: 0,
|
|
184
|
+
startTime
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// Create download promises with concurrency control
|
|
188
|
+
const fileDownloadPromises = files.map((item) => {
|
|
93
189
|
// Keep the original structure by preserving the folder name
|
|
94
|
-
|
|
95
|
-
|
|
190
|
+
let relativePath = item.path;
|
|
191
|
+
if (folderPath && folderPath.trim() !== '') {
|
|
192
|
+
relativePath = item.path.substring(folderPath.length).replace(/^\//, "");
|
|
193
|
+
}
|
|
96
194
|
const outputFilePath = path.join(outputDir, relativePath);
|
|
97
195
|
|
|
98
196
|
return limit(async () => {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
197
|
+
try {
|
|
198
|
+
const result = await downloadFile(owner, repo, branch, item.path, outputFilePath);
|
|
199
|
+
|
|
200
|
+
// Update progress metrics
|
|
201
|
+
if (result.success) {
|
|
202
|
+
downloadedSize += (result.size || 0);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Update progress bar with current metrics
|
|
206
|
+
progressBar.increment(1, {
|
|
207
|
+
downloadedSize
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
return result;
|
|
211
|
+
} catch (error) {
|
|
212
|
+
return { filePath: item.path, success: false, error: error.message, size: 0 };
|
|
213
|
+
}
|
|
102
214
|
});
|
|
103
215
|
});
|
|
104
216
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
217
|
+
// Execute downloads in parallel
|
|
218
|
+
const results = await Promise.all(fileDownloadPromises);
|
|
219
|
+
progressBar.stop();
|
|
220
|
+
|
|
221
|
+
console.log(); // Add an empty line after progress bar
|
|
108
222
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
223
|
+
// Count successful and failed downloads
|
|
224
|
+
const succeeded = results.filter((r) => r.success).length;
|
|
225
|
+
const failed = results.filter((r) => !r.success).length;
|
|
112
226
|
|
|
113
|
-
|
|
227
|
+
if (failed > 0) {
|
|
228
|
+
console.log(chalk.yellow(`Downloaded ${succeeded} files successfully, ${failed} files failed`));
|
|
229
|
+
} else {
|
|
230
|
+
console.log(chalk.green(` All ${succeeded} files downloaded successfully!`));
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
console.log(chalk.green(`Folder cloned successfully!`));
|
|
234
|
+
} catch (error) {
|
|
235
|
+
console.error(chalk.red(`Error downloading folder: ${error.message}`));
|
|
236
|
+
}
|
|
114
237
|
};
|
|
115
238
|
|
|
116
239
|
// Export functions in ESM format
|
package/src/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import { downloadFolder } from './downloader.js';
|
|
|
4
4
|
|
|
5
5
|
const initializeCLI = () => {
|
|
6
6
|
program
|
|
7
|
-
.version('1.3.
|
|
7
|
+
.version('1.3.4')
|
|
8
8
|
.description('Clone specific folders from GitHub repositories')
|
|
9
9
|
.argument('<url>', 'GitHub URL of the folder to clone')
|
|
10
10
|
.option('-o, --output <directory>', 'Output directory', process.cwd())
|