git-ripper 1.5.0 → 1.5.2

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,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import { initializeCLI } from '../src/index.js';
3
-
4
- initializeCLI();
2
+ import { initializeCLI } from '../src/index.js';
3
+
4
+ initializeCLI();
package/package.json CHANGED
@@ -1,62 +1,62 @@
1
- {
2
- "name": "git-ripper",
3
- "version": "1.5.0",
4
- "description": "CLI tool that lets you download specific folders from GitHub repositories without cloning the entire repo.",
5
- "main": "src/index.js",
6
- "type": "module",
7
- "bin": {
8
- "git-ripper": "bin/git-ripper.js"
9
- },
10
- "scripts": {
11
- "test": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js",
12
- "dev": "node bin/git-ripper.js"
13
- },
14
- "keywords": [
15
- "git",
16
- "clone",
17
- "github",
18
- "subfolder",
19
- "repository",
20
- "download",
21
- "partial-clone",
22
- "directory-download",
23
- "folder-download",
24
- "git-utilities",
25
- "github-api",
26
- "monorepo-tools",
27
- "sparse-checkout"
28
- ],
29
- "author": "sairajb",
30
- "license": "MIT",
31
- "dependencies": {
32
- "archiver": "^7.0.1",
33
- "axios": "^1.6.7",
34
- "chalk": "^5.3.0",
35
- "cli-progress": "^3.12.0",
36
- "commander": "^12.0.0",
37
- "p-limit": "^6.2.0",
38
- "pretty-bytes": "^6.1.1"
39
- },
40
- "repository": {
41
- "type": "git",
42
- "url": "git+https://github.com/sairajB/git-ripper.git"
43
- },
44
- "bugs": {
45
- "url": "https://github.com/sairajB/git-ripper/issues"
46
- },
47
- "homepage": "https://git-ripper.sairajb.tech/",
48
- "engines": {
49
- "node": ">=16.0.0"
50
- },
51
- "files": [
52
- "bin/",
53
- "src/",
54
- "LICENSE"
55
- ],
56
- "publishConfig": {
57
- "access": "public"
58
- },
59
- "devDependencies": {
60
- "jest": "^29.7.0"
61
- }
62
- }
1
+ {
2
+ "name": "git-ripper",
3
+ "version": "1.5.2",
4
+ "description": "CLI tool that lets you download specific folders from GitHub repositories without cloning the entire repo.",
5
+ "main": "src/index.js",
6
+ "type": "module",
7
+ "bin": {
8
+ "git-ripper": "bin/git-ripper.js"
9
+ },
10
+ "scripts": {
11
+ "test": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js",
12
+ "dev": "node bin/git-ripper.js"
13
+ },
14
+ "keywords": [
15
+ "git",
16
+ "clone",
17
+ "github",
18
+ "subfolder",
19
+ "repository",
20
+ "download",
21
+ "partial-clone",
22
+ "directory-download",
23
+ "folder-download",
24
+ "git-utilities",
25
+ "github-api",
26
+ "monorepo-tools",
27
+ "sparse-checkout"
28
+ ],
29
+ "author": "sairajb",
30
+ "license": "MIT",
31
+ "dependencies": {
32
+ "archiver": "^7.0.1",
33
+ "axios": "^1.6.7",
34
+ "chalk": "^5.3.0",
35
+ "cli-progress": "^3.12.0",
36
+ "commander": "^12.0.0",
37
+ "p-limit": "^6.2.0",
38
+ "pretty-bytes": "^6.1.1"
39
+ },
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "git+https://github.com/sairajB/git-ripper.git"
43
+ },
44
+ "bugs": {
45
+ "url": "https://github.com/sairajB/git-ripper/issues"
46
+ },
47
+ "homepage": "https://git-ripper.sairajb.tech/",
48
+ "engines": {
49
+ "node": ">=16.0.0"
50
+ },
51
+ "files": [
52
+ "bin/",
53
+ "src/",
54
+ "LICENSE"
55
+ ],
56
+ "publishConfig": {
57
+ "access": "public"
58
+ },
59
+ "devDependencies": {
60
+ "jest": "^29.7.0"
61
+ }
62
+ }
package/src/archiver.js CHANGED
@@ -1,210 +1,210 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- import archiver from "archiver";
4
- import chalk from "chalk";
5
-
6
- /**
7
- * Validates the output path for an archive file
8
- * @param {string} outputPath - Path where the archive should be saved
9
- * @returns {boolean} - True if the path is valid, throws an error otherwise
10
- * @throws {Error} - If the output path is invalid
11
- */
12
- const validateArchivePath = (outputPath) => {
13
- // Check if path is provided
14
- if (!outputPath) {
15
- throw new Error("Output path is required");
16
- }
17
-
18
- // Check if the output directory exists or can be created
19
- const outputDir = path.dirname(outputPath);
20
- try {
21
- if (!fs.existsSync(outputDir)) {
22
- fs.mkdirSync(outputDir, { recursive: true });
23
- }
24
-
25
- // Check if the directory is writable
26
- fs.accessSync(outputDir, fs.constants.W_OK);
27
-
28
- // Check if file already exists and is writable
29
- if (fs.existsSync(outputPath)) {
30
- fs.accessSync(outputPath, fs.constants.W_OK);
31
- // File exists and is writable, so we'll overwrite it
32
- console.warn(
33
- chalk.yellow(
34
- `Warning: File ${outputPath} already exists and will be overwritten`
35
- )
36
- );
37
- }
38
-
39
- return true;
40
- } catch (error) {
41
- if (error.code === "EACCES") {
42
- throw new Error(`Permission denied: Cannot write to ${outputPath}`);
43
- }
44
- throw new Error(`Invalid output path: ${error.message}`);
45
- }
46
- };
47
-
48
- /**
49
- * Creates a ZIP archive from a directory with standard compression
50
- *
51
- * @param {string} sourceDir - Source directory to archive
52
- * @param {string} outputPath - Path where the archive should be saved
53
- * @returns {Promise<string>} - Path to the created archive
54
- */
55
- export const createArchive = (sourceDir, outputPath) => {
56
- return new Promise((resolve, reject) => {
57
- try {
58
- // Fixed compression level of 5 (balanced between speed and size)
59
- const compressionLevel = 5;
60
-
61
- // Validate source directory
62
- if (!fs.existsSync(sourceDir)) {
63
- return reject(
64
- new Error(`Source directory does not exist: ${sourceDir}`)
65
- );
66
- }
67
-
68
- const stats = fs.statSync(sourceDir);
69
- if (!stats.isDirectory()) {
70
- return reject(
71
- new Error(`Source path is not a directory: ${sourceDir}`)
72
- );
73
- }
74
-
75
- // Validate output path
76
- validateArchivePath(outputPath);
77
-
78
- // Create output stream
79
- const output = fs.createWriteStream(outputPath);
80
-
81
- // Create ZIP archive with standard compression
82
- const archive = archiver("zip", {
83
- zlib: { level: compressionLevel },
84
- });
85
-
86
- // Listen for archive events
87
- output.on("close", () => {
88
- const size = archive.pointer();
89
- console.log(
90
- chalk.green(
91
- `Archive created: ${outputPath} (${(size / 1024 / 1024).toFixed(
92
- 2
93
- )} MB)`
94
- )
95
- );
96
- resolve(outputPath);
97
- });
98
-
99
- archive.on("error", (err) => {
100
- reject(err);
101
- });
102
-
103
- archive.on("warning", (err) => {
104
- if (err.code === "ENOENT") {
105
- console.warn(chalk.yellow(`Warning: ${err.message}`));
106
- } else {
107
- reject(err);
108
- }
109
- });
110
-
111
- // Pipe archive data to the output file
112
- archive.pipe(output);
113
-
114
- // Add the directory contents to the archive
115
- archive.directory(sourceDir, false);
116
-
117
- // Finalize the archive
118
- archive.finalize();
119
- } catch (error) {
120
- reject(error);
121
- }
122
- });
123
- };
124
-
125
- /**
126
- * Downloads folder contents and creates a ZIP archive
127
- *
128
- * @param {object} repoInfo - Repository information object
129
- * @param {string} outputDir - Directory where files should be downloaded
130
- * @param {string} archiveName - Custom name for the archive file (optional)
131
- * @param {object} [options] - Download options including token
132
- * @returns {Promise<string>} - Path to the created archive
133
- */
134
- export const downloadAndArchive = async (
135
- repoInfo,
136
- outputDir,
137
- archiveName = null,
138
- options = {}
139
- ) => {
140
- const { downloadFolder } = await import("./downloader.js");
141
-
142
- console.log(
143
- chalk.cyan(`Downloading folder and preparing to create ZIP archive...`)
144
- );
145
-
146
- // Create a temporary directory for the download
147
- const tempDir = path.join(outputDir, `.temp-${Date.now()}`);
148
- fs.mkdirSync(tempDir, { recursive: true });
149
- try {
150
- // Download the folder contents
151
- const downloadResult = await downloadFolder(repoInfo, tempDir, options);
152
-
153
- // Check if download failed
154
- if (downloadResult && !downloadResult.success) {
155
- throw new Error(
156
- `Download failed: ${
157
- downloadResult.failedFiles || 0
158
- } files could not be downloaded`
159
- );
160
- }
161
-
162
- // Check if there's anything to archive
163
- const files = fs
164
- .readdirSync(tempDir, { recursive: true })
165
- .filter((file) => {
166
- const fullPath = path.join(tempDir, file);
167
- return fs.statSync(fullPath).isFile();
168
- });
169
-
170
- if (files.length === 0) {
171
- throw new Error(
172
- `No files to archive - download may have failed or repository is empty`
173
- );
174
- }
175
-
176
- // Determine archive filename
177
- let archiveFileName = archiveName;
178
- if (!archiveFileName) {
179
- const { owner, repo, folderPath } = repoInfo;
180
- const folderName = folderPath ? folderPath.split("/").pop() : repo;
181
- archiveFileName = `${folderName || repo}-${owner}`;
182
- }
183
-
184
- // Add extension if not present
185
- if (!archiveFileName.endsWith(`.zip`)) {
186
- archiveFileName += `.zip`;
187
- }
188
-
189
- const archivePath = path.join(outputDir, archiveFileName);
190
-
191
- // Create the archive
192
- console.log(chalk.cyan(`Creating ZIP archive...`));
193
- await createArchive(tempDir, archivePath);
194
-
195
- return archivePath;
196
- } catch (error) {
197
- throw new Error(`Failed to create archive: ${error.message}`);
198
- } finally {
199
- // Clean up temporary directory
200
- try {
201
- fs.rmSync(tempDir, { recursive: true, force: true });
202
- } catch (err) {
203
- console.warn(
204
- chalk.yellow(
205
- `Warning: Failed to clean up temporary directory: ${err.message}`
206
- )
207
- );
208
- }
209
- }
210
- };
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import archiver from "archiver";
4
+ import chalk from "chalk";
5
+
6
+ /**
7
+ * Validates the output path for an archive file
8
+ * @param {string} outputPath - Path where the archive should be saved
9
+ * @returns {boolean} - True if the path is valid, throws an error otherwise
10
+ * @throws {Error} - If the output path is invalid
11
+ */
12
+ const validateArchivePath = (outputPath) => {
13
+ // Check if path is provided
14
+ if (!outputPath) {
15
+ throw new Error("Output path is required");
16
+ }
17
+
18
+ // Check if the output directory exists or can be created
19
+ const outputDir = path.dirname(outputPath);
20
+ try {
21
+ if (!fs.existsSync(outputDir)) {
22
+ fs.mkdirSync(outputDir, { recursive: true });
23
+ }
24
+
25
+ // Check if the directory is writable
26
+ fs.accessSync(outputDir, fs.constants.W_OK);
27
+
28
+ // Check if file already exists and is writable
29
+ if (fs.existsSync(outputPath)) {
30
+ fs.accessSync(outputPath, fs.constants.W_OK);
31
+ // File exists and is writable, so we'll overwrite it
32
+ console.warn(
33
+ chalk.yellow(
34
+ `Warning: File ${outputPath} already exists and will be overwritten`
35
+ )
36
+ );
37
+ }
38
+
39
+ return true;
40
+ } catch (error) {
41
+ if (error.code === "EACCES") {
42
+ throw new Error(`Permission denied: Cannot write to ${outputPath}`);
43
+ }
44
+ throw new Error(`Invalid output path: ${error.message}`);
45
+ }
46
+ };
47
+
48
+ /**
49
+ * Creates a ZIP archive from a directory with standard compression
50
+ *
51
+ * @param {string} sourceDir - Source directory to archive
52
+ * @param {string} outputPath - Path where the archive should be saved
53
+ * @returns {Promise<string>} - Path to the created archive
54
+ */
55
+ export const createArchive = (sourceDir, outputPath) => {
56
+ return new Promise((resolve, reject) => {
57
+ try {
58
+ // Fixed compression level of 5 (balanced between speed and size)
59
+ const compressionLevel = 5;
60
+
61
+ // Validate source directory
62
+ if (!fs.existsSync(sourceDir)) {
63
+ return reject(
64
+ new Error(`Source directory does not exist: ${sourceDir}`)
65
+ );
66
+ }
67
+
68
+ const stats = fs.statSync(sourceDir);
69
+ if (!stats.isDirectory()) {
70
+ return reject(
71
+ new Error(`Source path is not a directory: ${sourceDir}`)
72
+ );
73
+ }
74
+
75
+ // Validate output path
76
+ validateArchivePath(outputPath);
77
+
78
+ // Create output stream
79
+ const output = fs.createWriteStream(outputPath);
80
+
81
+ // Create ZIP archive with standard compression
82
+ const archive = archiver("zip", {
83
+ zlib: { level: compressionLevel },
84
+ });
85
+
86
+ // Listen for archive events
87
+ output.on("close", () => {
88
+ const size = archive.pointer();
89
+ console.log(
90
+ chalk.green(
91
+ `Archive created: ${outputPath} (${(size / 1024 / 1024).toFixed(
92
+ 2
93
+ )} MB)`
94
+ )
95
+ );
96
+ resolve(outputPath);
97
+ });
98
+
99
+ archive.on("error", (err) => {
100
+ reject(err);
101
+ });
102
+
103
+ archive.on("warning", (err) => {
104
+ if (err.code === "ENOENT") {
105
+ console.warn(chalk.yellow(`Warning: ${err.message}`));
106
+ } else {
107
+ reject(err);
108
+ }
109
+ });
110
+
111
+ // Pipe archive data to the output file
112
+ archive.pipe(output);
113
+
114
+ // Add the directory contents to the archive
115
+ archive.directory(sourceDir, false);
116
+
117
+ // Finalize the archive
118
+ archive.finalize();
119
+ } catch (error) {
120
+ reject(error);
121
+ }
122
+ });
123
+ };
124
+
125
+ /**
126
+ * Downloads folder contents and creates a ZIP archive
127
+ *
128
+ * @param {object} repoInfo - Repository information object
129
+ * @param {string} outputDir - Directory where files should be downloaded
130
+ * @param {string} archiveName - Custom name for the archive file (optional)
131
+ * @param {object} [options] - Download options including token
132
+ * @returns {Promise<string>} - Path to the created archive
133
+ */
134
+ export const downloadAndArchive = async (
135
+ repoInfo,
136
+ outputDir,
137
+ archiveName = null,
138
+ options = {}
139
+ ) => {
140
+ const { downloadFolder } = await import("./downloader.js");
141
+
142
+ console.log(
143
+ chalk.cyan(`Downloading folder and preparing to create ZIP archive...`)
144
+ );
145
+
146
+ // Create a temporary directory for the download
147
+ const tempDir = path.join(outputDir, `.temp-${Date.now()}`);
148
+ fs.mkdirSync(tempDir, { recursive: true });
149
+ try {
150
+ // Download the folder contents
151
+ const downloadResult = await downloadFolder(repoInfo, tempDir, options);
152
+
153
+ // Check if download failed
154
+ if (downloadResult && !downloadResult.success) {
155
+ throw new Error(
156
+ `Download failed: ${
157
+ downloadResult.failedFiles || 0
158
+ } files could not be downloaded`
159
+ );
160
+ }
161
+
162
+ // Check if there's anything to archive
163
+ const files = fs
164
+ .readdirSync(tempDir, { recursive: true })
165
+ .filter((file) => {
166
+ const fullPath = path.join(tempDir, file);
167
+ return fs.statSync(fullPath).isFile();
168
+ });
169
+
170
+ if (files.length === 0) {
171
+ throw new Error(
172
+ `No files to archive - download may have failed or repository is empty`
173
+ );
174
+ }
175
+
176
+ // Determine archive filename
177
+ let archiveFileName = archiveName;
178
+ if (!archiveFileName) {
179
+ const { owner, repo, folderPath } = repoInfo;
180
+ const folderName = folderPath ? folderPath.split("/").pop() : repo;
181
+ archiveFileName = `${folderName || repo}-${owner}`;
182
+ }
183
+
184
+ // Add extension if not present
185
+ if (!archiveFileName.endsWith(`.zip`)) {
186
+ archiveFileName += `.zip`;
187
+ }
188
+
189
+ const archivePath = path.join(outputDir, archiveFileName);
190
+
191
+ // Create the archive
192
+ console.log(chalk.cyan(`Creating ZIP archive...`));
193
+ await createArchive(tempDir, archivePath);
194
+
195
+ return archivePath;
196
+ } catch (error) {
197
+ throw new Error(`Failed to create archive: ${error.message}`);
198
+ } finally {
199
+ // Clean up temporary directory
200
+ try {
201
+ fs.rmSync(tempDir, { recursive: true, force: true });
202
+ } catch (err) {
203
+ console.warn(
204
+ chalk.yellow(
205
+ `Warning: Failed to clean up temporary directory: ${err.message}`
206
+ )
207
+ );
208
+ }
209
+ }
210
+ };