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/LICENSE +20 -20
- package/README.md +340 -330
- package/bin/git-ripper.js +3 -3
- package/package.json +62 -62
- package/src/archiver.js +210 -210
- package/src/downloader.js +904 -878
- package/src/index.js +195 -195
- package/src/parser.js +37 -37
- package/src/resumeManager.js +213 -213
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.
|
|
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
|
+
};
|