git-ripper 1.0.11 → 1.1.0
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 +1 -1
- package/package.json +5 -3
- package/src/downloader.js +106 -34
package/bin/git-ripper.js
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "git-ripper",
|
|
3
|
-
"version": "1.0
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.1.0",
|
|
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": {
|
|
7
7
|
"git-ripper": "./bin/git-ripper.js"
|
|
@@ -30,7 +30,9 @@
|
|
|
30
30
|
"license": "MIT",
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"axios": "^1.6.7",
|
|
33
|
-
"
|
|
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(
|
|
2
|
-
const fs = require(
|
|
3
|
-
const path = require(
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
39
|
-
};
|
|
110
|
+
downloadFolder,
|
|
111
|
+
};
|