git-ripper 1.0.12 → 1.1.1

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/bin/git-ripper.js CHANGED
@@ -1,3 +1,3 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
  const { downloadFolder } = require('../src/index');
3
3
  downloadFolder();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "git-ripper",
3
- "version": "1.0.12",
3
+ "version": "1.1.1",
4
4
  "description": "CLI tool that lets you download specific folders from GitHub repositories without cloning the entire repo.",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -30,7 +30,9 @@
30
30
  "license": "MIT",
31
31
  "dependencies": {
32
32
  "axios": "^1.6.7",
33
- "commander": "^12.0.0"
33
+ "cli-progress": "^3.12.0",
34
+ "commander": "^12.0.0",
35
+ "p-limit": "^6.2.0"
34
36
  },
35
37
  "repository": {
36
38
  "type": "git",
package/src/downloader.js CHANGED
@@ -1,39 +1,111 @@
1
- const axios = require('axios');
2
- const fs = require('fs');
3
- const path = require('path');
1
+ const axios = require("axios");
2
+ const fs = require("fs");
3
+ const path = require("path");
4
+ const pLimit = require('p-limit').default || require('p-limit');
5
+ const cliProgress = require("cli-progress"); // Progress bar
6
+
7
+ // Set concurrency limit (adjustable based on network performance)
8
+ const limit = pLimit(5);
9
+
10
+ /**
11
+ * Fetches the contents of a folder from a GitHub repository
12
+ * @param {string} owner - Repository owner
13
+ * @param {string} repo - Repository name
14
+ * @param {string} branch - Branch name
15
+ * @param {string} folderPath - Path to the folder
16
+ * @returns {Promise<Array>} - Promise resolving to an array of file objects
17
+ */
4
18
  const fetchFolderContents = async (owner, repo, branch, folderPath) => {
5
- const apiUrl =
6
- `https://api.github.com/repos/${owner}/${repo}/git/trees/${branch}?recurs
7
- ive=1`;
8
- const response = await axios.get(apiUrl);
9
- return response.data.tree.filter((item) =>
10
- item.path.startsWith(folderPath));
19
+ const apiUrl = `https://api.github.com/repos/${owner}/${repo}/git/trees/${branch}?recursive=1`;
20
+
21
+ try {
22
+ const response = await axios.get(apiUrl);
23
+ return response.data.tree.filter((item) => item.path.startsWith(folderPath));
24
+ } catch (error) {
25
+ if (error.response && error.response.status === 404) {
26
+ console.error(`Repository, branch, or folder not found: ${owner}/${repo}/${branch}/${folderPath}`);
27
+ return [];
28
+ }
29
+ console.error(`Failed to fetch folder contents: ${error.message}`);
30
+ return [];
31
+ }
11
32
  };
12
- const downloadFile = async (owner, repo, branch, filePath, outputPath) =>
13
- {
14
- const url =
15
- `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/${filePath}
16
- `;
17
- const response = await axios.get(url, { responseType: 'arraybuffer' });
18
- fs.mkdirSync(path.dirname(outputPath), { recursive: true });
19
- fs.writeFileSync(outputPath, Buffer.from(response.data));
33
+
34
+ /**
35
+ * Downloads a single file from a GitHub repository
36
+ * @param {string} owner - Repository owner
37
+ * @param {string} repo - Repository name
38
+ * @param {string} branch - Branch name
39
+ * @param {string} filePath - Path to the file
40
+ * @param {string} outputPath - Path where the file should be saved
41
+ * @returns {Promise<Object>} - Object containing download status
42
+ */
43
+ const downloadFile = async (owner, repo, branch, filePath, outputPath) => {
44
+ const url = `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/${filePath}`;
45
+
46
+ try {
47
+ const response = await axios.get(url, { responseType: "arraybuffer" });
48
+ fs.mkdirSync(path.dirname(outputPath), { recursive: true });
49
+ fs.writeFileSync(outputPath, Buffer.from(response.data));
50
+ return { filePath, success: true };
51
+ } catch (error) {
52
+ return { filePath, success: false, error: error.message };
53
+ }
20
54
  };
21
- const downloadFolder = async ({ owner, repo, branch, folderPath },
22
- outputDir) => {
23
- console.log(`Cloning ${folderPath} from ${owner}/${repo}
24
- (${branch})...`);
25
-
26
- const contents = await fetchFolderContents(owner, repo, branch,
27
- folderPath);
28
-
29
- for (const item of contents) {
30
- if (item.type === 'blob') {
31
- const relativeFilePath = item.path.substring(folderPath.length);
32
- const outputFilePath = path.join(outputDir, relativeFilePath);
33
- await downloadFile(owner, repo, branch, item.path, outputFilePath);
34
- }
35
- }
55
+
56
+ /**
57
+ * Downloads all files from a folder in a GitHub repository
58
+ * @param {Object} repoInfo - Object containing repository information
59
+ * @param {string} repoInfo.owner - Repository owner
60
+ * @param {string} repoInfo.repo - Repository name
61
+ * @param {string} repoInfo.branch - Branch name
62
+ * @param {string} repoInfo.folderPath - Path to the folder
63
+ * @param {string} outputDir - Directory where files should be saved
64
+ * @returns {Promise<void>} - Promise that resolves when all files are downloaded
65
+ */
66
+ const downloadFolder = async ({ owner, repo, branch, folderPath }, outputDir) => {
67
+ console.log(`Cloning ${folderPath} from ${owner}/${repo} (${branch})...`);
68
+
69
+ const contents = await fetchFolderContents(owner, repo, branch, folderPath);
70
+
71
+ if (contents.length === 0) {
72
+ console.log(`No files found in ${folderPath}`);
73
+ return;
74
+ }
75
+
76
+ let totalFiles = contents.filter(item => item.type === "blob").length;
77
+ console.log(`Preparing to download ${totalFiles} files/folders...`);
78
+
79
+ // Progress bar setup
80
+ const bar = new cliProgress.SingleBar({}, cliProgress.Presets.shades_classic);
81
+ totalFiles = contents.filter(item => item.type === "blob").length;
82
+ bar.start(totalFiles, 0);
83
+
84
+ // Create download promises with concurrency control
85
+ const fileDownloadPromises = contents
86
+ .filter((item) => item.type === "blob")
87
+ .map((item) => {
88
+ const relativePath = item.path.substring(folderPath.length).replace(/^\//, "");
89
+ const outputFilePath = path.join(outputDir, relativePath);
90
+
91
+ return limit(async () => {
92
+ const result = await downloadFile(owner, repo, branch, item.path, outputFilePath);
93
+ bar.increment(); // Update progress bar
94
+ return result;
95
+ });
96
+ });
97
+
98
+ // Execute downloads in parallel
99
+ const results = await Promise.all(fileDownloadPromises);
100
+ bar.stop(); // Stop progress bar
101
+
102
+ // Count successful and failed downloads
103
+ const succeeded = results.filter((r) => r.success).length;
104
+ const failed = results.filter((r) => !r.success).length;
105
+
106
+ console.log(`Downloaded ${succeeded} files successfully${failed > 0 ? `, ${failed} files failed` : ""}`);
36
107
  };
108
+
37
109
  module.exports = {
38
- downloadFolder
39
- };
110
+ downloadFolder,
111
+ };
package/src/index.js CHANGED
@@ -4,7 +4,7 @@ const { downloadFolder } = require('./downloader');
4
4
 
5
5
  const initializeCLI = () => {
6
6
  program
7
- .version('1.0.7')
7
+ .version('1.1.1')
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())