git-ripper 1.4.5 → 1.4.7
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/package.json +1 -1
- package/src/archiver.js +25 -3
- package/src/downloader.js +177 -102
- package/src/index.js +36 -14
- package/src/parser.js +4 -4
package/package.json
CHANGED
package/src/archiver.js
CHANGED
|
@@ -88,7 +88,7 @@ export const createArchive = (sourceDir, outputPath) => {
|
|
|
88
88
|
const size = archive.pointer();
|
|
89
89
|
console.log(
|
|
90
90
|
chalk.green(
|
|
91
|
-
|
|
91
|
+
`Archive created: ${outputPath} (${(size / 1024 / 1024).toFixed(
|
|
92
92
|
2
|
|
93
93
|
)} MB)`
|
|
94
94
|
)
|
|
@@ -144,10 +144,32 @@ export const downloadAndArchive = async (
|
|
|
144
144
|
// Create a temporary directory for the download
|
|
145
145
|
const tempDir = path.join(outputDir, `.temp-${Date.now()}`);
|
|
146
146
|
fs.mkdirSync(tempDir, { recursive: true });
|
|
147
|
-
|
|
148
147
|
try {
|
|
149
148
|
// Download the folder contents
|
|
150
|
-
await downloadFolder(repoInfo, tempDir);
|
|
149
|
+
const downloadResult = await downloadFolder(repoInfo, tempDir);
|
|
150
|
+
|
|
151
|
+
// Check if download failed
|
|
152
|
+
if (downloadResult && !downloadResult.success) {
|
|
153
|
+
throw new Error(
|
|
154
|
+
`Download failed: ${
|
|
155
|
+
downloadResult.failedFiles || 0
|
|
156
|
+
} files could not be downloaded`
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Check if there's anything to archive
|
|
161
|
+
const files = fs
|
|
162
|
+
.readdirSync(tempDir, { recursive: true })
|
|
163
|
+
.filter((file) => {
|
|
164
|
+
const fullPath = path.join(tempDir, file);
|
|
165
|
+
return fs.statSync(fullPath).isFile();
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
if (files.length === 0) {
|
|
169
|
+
throw new Error(
|
|
170
|
+
`No files to archive - download may have failed or repository is empty`
|
|
171
|
+
);
|
|
172
|
+
}
|
|
151
173
|
|
|
152
174
|
// Determine archive filename
|
|
153
175
|
let archiveFileName = archiveName;
|
package/src/downloader.js
CHANGED
|
@@ -45,22 +45,22 @@ const getSpinnerFrame = () => {
|
|
|
45
45
|
* @param {string} branch - Branch name
|
|
46
46
|
* @param {string} folderPath - Path to the folder
|
|
47
47
|
* @returns {Promise<Array>} - Promise resolving to an array of file objects
|
|
48
|
+
* @throws {Error} - Throws error on API failures instead of returning empty array
|
|
48
49
|
*/
|
|
49
50
|
const fetchFolderContents = async (owner, repo, branch, folderPath) => {
|
|
50
51
|
let effectiveBranch = branch;
|
|
51
52
|
if (!effectiveBranch) {
|
|
52
53
|
// If no branch is specified, fetch the default branch for the repository
|
|
53
54
|
try {
|
|
54
|
-
const repoInfoUrl = `https://api.github.com/repos/${
|
|
55
|
+
const repoInfoUrl = `https://api.github.com/repos/${encodeURIComponent(
|
|
56
|
+
owner
|
|
57
|
+
)}/${encodeURIComponent(repo)}`;
|
|
55
58
|
const repoInfoResponse = await axios.get(repoInfoUrl);
|
|
56
59
|
effectiveBranch = repoInfoResponse.data.default_branch;
|
|
57
60
|
if (!effectiveBranch) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
`Could not determine default branch for ${owner}/${repo}. Please specify a branch in the URL.`
|
|
61
|
-
)
|
|
61
|
+
throw new Error(
|
|
62
|
+
`Could not determine default branch for ${owner}/${repo}. Please specify a branch in the URL.`
|
|
62
63
|
);
|
|
63
|
-
return [];
|
|
64
64
|
}
|
|
65
65
|
console.log(
|
|
66
66
|
chalk.blue(
|
|
@@ -68,16 +68,20 @@ const fetchFolderContents = async (owner, repo, branch, folderPath) => {
|
|
|
68
68
|
)
|
|
69
69
|
);
|
|
70
70
|
} catch (error) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
71
|
+
if (error.message.includes("Could not determine default branch")) {
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
throw new Error(
|
|
75
|
+
`Failed to fetch default branch for ${owner}/${repo}: ${error.message}`
|
|
75
76
|
);
|
|
76
|
-
return [];
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
const apiUrl = `https://api.github.com/repos/${
|
|
80
|
+
const apiUrl = `https://api.github.com/repos/${encodeURIComponent(
|
|
81
|
+
owner
|
|
82
|
+
)}/${encodeURIComponent(repo)}/git/trees/${encodeURIComponent(
|
|
83
|
+
effectiveBranch
|
|
84
|
+
)}?recursive=1`;
|
|
81
85
|
|
|
82
86
|
try {
|
|
83
87
|
const response = await axios.get(apiUrl);
|
|
@@ -109,54 +113,44 @@ const fetchFolderContents = async (owner, repo, branch, folderPath) => {
|
|
|
109
113
|
return response.data.tree.filter((item) => item.path.startsWith(prefix));
|
|
110
114
|
}
|
|
111
115
|
} catch (error) {
|
|
116
|
+
let errorMessage = "";
|
|
117
|
+
let isRateLimit = false;
|
|
118
|
+
|
|
112
119
|
if (error.response) {
|
|
113
120
|
// Handle specific HTTP error codes
|
|
114
121
|
switch (error.response.status) {
|
|
115
122
|
case 403:
|
|
116
123
|
if (error.response.headers["x-ratelimit-remaining"] === "0") {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
).toLocaleTimeString()} or add a GitHub token (feature coming soon).`
|
|
122
|
-
)
|
|
123
|
-
);
|
|
124
|
+
isRateLimit = true;
|
|
125
|
+
errorMessage = `GitHub API rate limit exceeded. Please wait until ${new Date(
|
|
126
|
+
parseInt(error.response.headers["x-ratelimit-reset"]) * 1000
|
|
127
|
+
).toLocaleTimeString()} or add a GitHub token (feature coming soon).`;
|
|
124
128
|
} else {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
}`
|
|
130
|
-
)
|
|
131
|
-
);
|
|
129
|
+
errorMessage = `Access forbidden: ${
|
|
130
|
+
error.response.data.message ||
|
|
131
|
+
"Repository may be private or you may not have access"
|
|
132
|
+
}`;
|
|
132
133
|
}
|
|
133
134
|
break;
|
|
134
135
|
case 404:
|
|
135
|
-
|
|
136
|
-
chalk.red(
|
|
137
|
-
`Repository, branch, or folder not found: ${owner}/${repo}/${branch}/${folderPath}`
|
|
138
|
-
)
|
|
139
|
-
);
|
|
136
|
+
errorMessage = `Repository, branch, or folder not found: ${owner}/${repo}/${branch}/${folderPath}`;
|
|
140
137
|
break;
|
|
141
138
|
default:
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
error.response.data.message || error.message
|
|
146
|
-
}`
|
|
147
|
-
)
|
|
148
|
-
);
|
|
139
|
+
errorMessage = `API error (${error.response.status}): ${
|
|
140
|
+
error.response.data.message || error.message
|
|
141
|
+
}`;
|
|
149
142
|
}
|
|
150
143
|
} else if (error.request) {
|
|
151
|
-
|
|
152
|
-
chalk.red(
|
|
153
|
-
`Network error: No response received from GitHub. Please check your internet connection.`
|
|
154
|
-
)
|
|
155
|
-
);
|
|
144
|
+
errorMessage = `Network error: No response received from GitHub. Please check your internet connection.`;
|
|
156
145
|
} else {
|
|
157
|
-
|
|
146
|
+
errorMessage = `Error preparing request: ${error.message}`;
|
|
158
147
|
}
|
|
159
|
-
|
|
148
|
+
|
|
149
|
+
// Always throw the error instead of returning empty array
|
|
150
|
+
const enrichedError = new Error(errorMessage);
|
|
151
|
+
enrichedError.isRateLimit = isRateLimit;
|
|
152
|
+
enrichedError.statusCode = error.response?.status;
|
|
153
|
+
throw enrichedError;
|
|
160
154
|
}
|
|
161
155
|
};
|
|
162
156
|
|
|
@@ -176,7 +170,9 @@ const downloadFile = async (owner, repo, branch, filePath, outputPath) => {
|
|
|
176
170
|
// This check might be redundant if fetchFolderContents already resolved it,
|
|
177
171
|
// but it's a good fallback for direct downloadFile calls if any.
|
|
178
172
|
try {
|
|
179
|
-
const repoInfoUrl = `https://api.github.com/repos/${
|
|
173
|
+
const repoInfoUrl = `https://api.github.com/repos/${encodeURIComponent(
|
|
174
|
+
owner
|
|
175
|
+
)}/${encodeURIComponent(repo)}`;
|
|
180
176
|
const repoInfoResponse = await axios.get(repoInfoUrl);
|
|
181
177
|
effectiveBranch = repoInfoResponse.data.default_branch;
|
|
182
178
|
if (!effectiveBranch) {
|
|
@@ -203,10 +199,16 @@ const downloadFile = async (owner, repo, branch, filePath, outputPath) => {
|
|
|
203
199
|
}
|
|
204
200
|
}
|
|
205
201
|
|
|
206
|
-
const baseUrl = `https://raw.githubusercontent.com/${
|
|
202
|
+
const baseUrl = `https://raw.githubusercontent.com/${encodeURIComponent(
|
|
203
|
+
owner
|
|
204
|
+
)}/${encodeURIComponent(repo)}`;
|
|
205
|
+
const encodedFilePath = filePath
|
|
206
|
+
.split("/")
|
|
207
|
+
.map((part) => encodeURIComponent(part))
|
|
208
|
+
.join("/");
|
|
207
209
|
const fileUrlPath = effectiveBranch
|
|
208
|
-
? `/${effectiveBranch}/${
|
|
209
|
-
: `/${
|
|
210
|
+
? `/${encodeURIComponent(effectiveBranch)}/${encodedFilePath}`
|
|
211
|
+
: `/${encodedFilePath}`; // filePath might be at root
|
|
210
212
|
const url = `${baseUrl}${fileUrlPath}`;
|
|
211
213
|
|
|
212
214
|
try {
|
|
@@ -349,11 +351,15 @@ const downloadFolder = async (
|
|
|
349
351
|
const contents = await fetchFolderContents(owner, repo, branch, folderPath);
|
|
350
352
|
|
|
351
353
|
if (!contents || contents.length === 0) {
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
354
|
+
const message = `No files found in ${folderPath || "repository root"}`;
|
|
355
|
+
console.log(chalk.yellow(message));
|
|
356
|
+
// Don't print success message when no files are found - this might indicate an error
|
|
357
|
+
return {
|
|
358
|
+
success: true,
|
|
359
|
+
filesDownloaded: 0,
|
|
360
|
+
failedFiles: 0,
|
|
361
|
+
isEmpty: true,
|
|
362
|
+
};
|
|
357
363
|
}
|
|
358
364
|
|
|
359
365
|
// Filter for blob type (files)
|
|
@@ -361,15 +367,18 @@ const downloadFolder = async (
|
|
|
361
367
|
const totalFiles = files.length;
|
|
362
368
|
|
|
363
369
|
if (totalFiles === 0) {
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
370
|
+
const message = `No files found in ${
|
|
371
|
+
folderPath || "repository root"
|
|
372
|
+
} (only directories)`;
|
|
373
|
+
console.log(chalk.yellow(message));
|
|
374
|
+
// This is a legitimate case - directory exists but contains only subdirectories
|
|
375
|
+
console.log(chalk.green(`Directory structure downloaded successfully!`));
|
|
376
|
+
return {
|
|
377
|
+
success: true,
|
|
378
|
+
filesDownloaded: 0,
|
|
379
|
+
failedFiles: 0,
|
|
380
|
+
isEmpty: true,
|
|
381
|
+
};
|
|
373
382
|
}
|
|
374
383
|
|
|
375
384
|
console.log(
|
|
@@ -464,7 +473,6 @@ const downloadFolder = async (
|
|
|
464
473
|
// Count successful and failed downloads
|
|
465
474
|
const succeeded = results.filter((r) => r.success).length;
|
|
466
475
|
const failed = failedFiles.length;
|
|
467
|
-
|
|
468
476
|
if (failed > 0) {
|
|
469
477
|
console.log(
|
|
470
478
|
chalk.yellow(
|
|
@@ -485,15 +493,45 @@ const downloadFolder = async (
|
|
|
485
493
|
)
|
|
486
494
|
);
|
|
487
495
|
}
|
|
496
|
+
|
|
497
|
+
// Don't claim success if files failed to download
|
|
498
|
+
if (succeeded === 0) {
|
|
499
|
+
console.log(
|
|
500
|
+
chalk.red(`Download failed: No files were downloaded successfully`)
|
|
501
|
+
);
|
|
502
|
+
return {
|
|
503
|
+
success: false,
|
|
504
|
+
filesDownloaded: succeeded,
|
|
505
|
+
failedFiles: failed,
|
|
506
|
+
isEmpty: false,
|
|
507
|
+
};
|
|
508
|
+
} else {
|
|
509
|
+
console.log(chalk.yellow(`Download completed with errors`));
|
|
510
|
+
return {
|
|
511
|
+
success: false,
|
|
512
|
+
filesDownloaded: succeeded,
|
|
513
|
+
failedFiles: failed,
|
|
514
|
+
isEmpty: false,
|
|
515
|
+
};
|
|
516
|
+
}
|
|
488
517
|
} else {
|
|
489
518
|
console.log(
|
|
490
|
-
chalk.green(`
|
|
519
|
+
chalk.green(`All ${succeeded} files downloaded successfully!`)
|
|
491
520
|
);
|
|
521
|
+
console.log(chalk.green(`Folder cloned successfully!`));
|
|
522
|
+
return {
|
|
523
|
+
success: true,
|
|
524
|
+
filesDownloaded: succeeded,
|
|
525
|
+
failedFiles: failed,
|
|
526
|
+
isEmpty: false,
|
|
527
|
+
};
|
|
492
528
|
}
|
|
493
|
-
|
|
494
|
-
console.log(chalk.green(`Folder cloned successfully!`));
|
|
495
529
|
} catch (error) {
|
|
530
|
+
// Log the specific error details
|
|
496
531
|
console.error(chalk.red(`Error downloading folder: ${error.message}`));
|
|
532
|
+
|
|
533
|
+
// Re-throw the error so the main CLI can exit with proper error code
|
|
534
|
+
throw error;
|
|
497
535
|
}
|
|
498
536
|
};
|
|
499
537
|
|
|
@@ -515,9 +553,17 @@ const downloadFolderWithResume = async (
|
|
|
515
553
|
}
|
|
516
554
|
|
|
517
555
|
const resumeManager = new ResumeManager();
|
|
518
|
-
const
|
|
519
|
-
folderPath
|
|
520
|
-
|
|
556
|
+
const encodedFolderPath = folderPath
|
|
557
|
+
? folderPath
|
|
558
|
+
.split("/")
|
|
559
|
+
.map((part) => encodeURIComponent(part))
|
|
560
|
+
.join("/")
|
|
561
|
+
: "";
|
|
562
|
+
const url = `https://github.com/${encodeURIComponent(
|
|
563
|
+
owner
|
|
564
|
+
)}/${encodeURIComponent(repo)}/tree/${encodeURIComponent(
|
|
565
|
+
branch || "main"
|
|
566
|
+
)}/${encodedFolderPath}`;
|
|
521
567
|
|
|
522
568
|
// Clear checkpoint if force restart is requested
|
|
523
569
|
if (forceRestart) {
|
|
@@ -530,14 +576,14 @@ const downloadFolderWithResume = async (
|
|
|
530
576
|
if (checkpoint) {
|
|
531
577
|
console.log(
|
|
532
578
|
chalk.blue(
|
|
533
|
-
|
|
579
|
+
`Found previous download from ${new Date(
|
|
534
580
|
checkpoint.timestamp
|
|
535
581
|
).toLocaleString()}`
|
|
536
582
|
)
|
|
537
583
|
);
|
|
538
584
|
console.log(
|
|
539
585
|
chalk.blue(
|
|
540
|
-
|
|
586
|
+
`Progress: ${checkpoint.downloadedFiles.length}/${checkpoint.totalFiles} files completed`
|
|
541
587
|
)
|
|
542
588
|
);
|
|
543
589
|
|
|
@@ -563,11 +609,11 @@ const downloadFolderWithResume = async (
|
|
|
563
609
|
if (corruptedCount > 0) {
|
|
564
610
|
console.log(
|
|
565
611
|
chalk.yellow(
|
|
566
|
-
|
|
612
|
+
`Detected ${corruptedCount} corrupted files, will re-download`
|
|
567
613
|
)
|
|
568
614
|
);
|
|
569
615
|
}
|
|
570
|
-
console.log(chalk.green(
|
|
616
|
+
console.log(chalk.green(`Verified ${validFiles.length} existing files`));
|
|
571
617
|
}
|
|
572
618
|
|
|
573
619
|
console.log(
|
|
@@ -576,13 +622,16 @@ const downloadFolderWithResume = async (
|
|
|
576
622
|
|
|
577
623
|
try {
|
|
578
624
|
const contents = await fetchFolderContents(owner, repo, branch, folderPath);
|
|
579
|
-
|
|
580
625
|
if (!contents || contents.length === 0) {
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
626
|
+
const message = `No files found in ${folderPath || "repository root"}`;
|
|
627
|
+
console.log(chalk.yellow(message));
|
|
628
|
+
// Don't print success message when no files are found - this might indicate an error
|
|
629
|
+
return {
|
|
630
|
+
success: true,
|
|
631
|
+
filesDownloaded: 0,
|
|
632
|
+
failedFiles: 0,
|
|
633
|
+
isEmpty: true,
|
|
634
|
+
};
|
|
586
635
|
}
|
|
587
636
|
|
|
588
637
|
// Filter for blob type (files)
|
|
@@ -590,15 +639,18 @@ const downloadFolderWithResume = async (
|
|
|
590
639
|
const totalFiles = files.length;
|
|
591
640
|
|
|
592
641
|
if (totalFiles === 0) {
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
642
|
+
const message = `No files found in ${
|
|
643
|
+
folderPath || "repository root"
|
|
644
|
+
} (only directories)`;
|
|
645
|
+
console.log(chalk.yellow(message));
|
|
646
|
+
// This is a legitimate case - directory exists but contains only subdirectories
|
|
647
|
+
console.log(chalk.green(`Directory structure downloaded successfully!`));
|
|
648
|
+
return {
|
|
649
|
+
success: true,
|
|
650
|
+
filesDownloaded: 0,
|
|
651
|
+
failedFiles: 0,
|
|
652
|
+
isEmpty: true,
|
|
653
|
+
};
|
|
602
654
|
}
|
|
603
655
|
|
|
604
656
|
// Create new checkpoint if none exists
|
|
@@ -610,7 +662,7 @@ const downloadFolderWithResume = async (
|
|
|
610
662
|
);
|
|
611
663
|
console.log(
|
|
612
664
|
chalk.cyan(
|
|
613
|
-
|
|
665
|
+
`Starting download of ${totalFiles} files from ${chalk.white(
|
|
614
666
|
owner + "/" + repo
|
|
615
667
|
)}...`
|
|
616
668
|
)
|
|
@@ -618,7 +670,7 @@ const downloadFolderWithResume = async (
|
|
|
618
670
|
} else {
|
|
619
671
|
// Update total files in case repository changed
|
|
620
672
|
checkpoint.totalFiles = totalFiles;
|
|
621
|
-
console.log(chalk.cyan(
|
|
673
|
+
console.log(chalk.cyan(`Resuming download...`));
|
|
622
674
|
}
|
|
623
675
|
|
|
624
676
|
// Get remaining files to download
|
|
@@ -633,13 +685,13 @@ const downloadFolderWithResume = async (
|
|
|
633
685
|
});
|
|
634
686
|
|
|
635
687
|
if (remainingFiles.length === 0) {
|
|
636
|
-
console.log(chalk.green(
|
|
688
|
+
console.log(chalk.green(`All files already downloaded!`));
|
|
637
689
|
resumeManager.cleanupCheckpoint(url, outputDir);
|
|
638
690
|
return;
|
|
639
691
|
}
|
|
640
692
|
|
|
641
693
|
console.log(
|
|
642
|
-
chalk.cyan(
|
|
694
|
+
chalk.cyan(`Downloading ${remainingFiles.length} remaining files...`)
|
|
643
695
|
);
|
|
644
696
|
|
|
645
697
|
// Setup progress bar
|
|
@@ -723,10 +775,8 @@ const downloadFolderWithResume = async (
|
|
|
723
775
|
if (error.name === "SIGINT") {
|
|
724
776
|
resumeManager.saveCheckpoint(checkpoint);
|
|
725
777
|
progressBar.stop();
|
|
726
|
-
console.log(
|
|
727
|
-
|
|
728
|
-
);
|
|
729
|
-
console.log(chalk.blue(`💡 Run the same command again to resume.`));
|
|
778
|
+
console.log(chalk.blue(`\nDownload interrupted. Progress saved.`));
|
|
779
|
+
console.log(chalk.blue(`Run the same command again to resume.`));
|
|
730
780
|
return;
|
|
731
781
|
}
|
|
732
782
|
|
|
@@ -748,7 +798,6 @@ const downloadFolderWithResume = async (
|
|
|
748
798
|
// Count results
|
|
749
799
|
const succeeded = checkpoint.downloadedFiles.length;
|
|
750
800
|
const failed = failedFiles.length;
|
|
751
|
-
|
|
752
801
|
if (failed > 0) {
|
|
753
802
|
console.log(
|
|
754
803
|
chalk.yellow(
|
|
@@ -764,16 +813,42 @@ const downloadFolderWithResume = async (
|
|
|
764
813
|
}
|
|
765
814
|
|
|
766
815
|
console.log(
|
|
767
|
-
chalk.blue(
|
|
816
|
+
chalk.blue(`Run the same command again to retry failed downloads`)
|
|
768
817
|
);
|
|
818
|
+
|
|
819
|
+
// Don't claim success if files failed to download
|
|
820
|
+
if (succeeded === 0) {
|
|
821
|
+
console.log(
|
|
822
|
+
chalk.red(`Download failed: No files were downloaded successfully`)
|
|
823
|
+
);
|
|
824
|
+
return {
|
|
825
|
+
success: false,
|
|
826
|
+
filesDownloaded: succeeded,
|
|
827
|
+
failedFiles: failed,
|
|
828
|
+
isEmpty: false,
|
|
829
|
+
};
|
|
830
|
+
} else {
|
|
831
|
+
console.log(chalk.yellow(`Download completed with errors`));
|
|
832
|
+
return {
|
|
833
|
+
success: false,
|
|
834
|
+
filesDownloaded: succeeded,
|
|
835
|
+
failedFiles: failed,
|
|
836
|
+
isEmpty: false,
|
|
837
|
+
};
|
|
838
|
+
}
|
|
769
839
|
} else {
|
|
770
840
|
console.log(
|
|
771
|
-
chalk.green(
|
|
841
|
+
chalk.green(`All ${succeeded} files downloaded successfully!`)
|
|
772
842
|
);
|
|
773
843
|
resumeManager.cleanupCheckpoint(url, outputDir);
|
|
844
|
+
console.log(chalk.green(`Folder cloned successfully!`));
|
|
845
|
+
return {
|
|
846
|
+
success: true,
|
|
847
|
+
filesDownloaded: succeeded,
|
|
848
|
+
failedFiles: failed,
|
|
849
|
+
isEmpty: false,
|
|
850
|
+
};
|
|
774
851
|
}
|
|
775
|
-
|
|
776
|
-
console.log(chalk.green(`Folder cloned successfully!`));
|
|
777
852
|
} catch (error) {
|
|
778
853
|
// Save checkpoint on any error
|
|
779
854
|
if (checkpoint) {
|
package/src/index.js
CHANGED
|
@@ -76,7 +76,7 @@ const initializeCLI = () => {
|
|
|
76
76
|
return;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
console.log(chalk.cyan("\
|
|
79
|
+
console.log(chalk.cyan("\nDownload Checkpoints:"));
|
|
80
80
|
checkpoints.forEach((cp, index) => {
|
|
81
81
|
console.log(chalk.blue(`\n${index + 1}. ID: ${cp.id}`));
|
|
82
82
|
console.log(` URL: ${cp.url}`);
|
|
@@ -123,24 +123,46 @@ const initializeCLI = () => {
|
|
|
123
123
|
forceRestart: options.forceRestart || false,
|
|
124
124
|
};
|
|
125
125
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
} else {
|
|
130
|
-
console.log(`Downloading folder to: ${options.output}`);
|
|
126
|
+
let operationType = createArchive ? "archive" : "download";
|
|
127
|
+
let result = null;
|
|
128
|
+
let error = null;
|
|
131
129
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
downloadOptions
|
|
137
|
-
);
|
|
130
|
+
try {
|
|
131
|
+
if (createArchive) {
|
|
132
|
+
console.log(`Creating ZIP archive...`);
|
|
133
|
+
await downloadAndArchive(parsedUrl, options.output, archiveName);
|
|
138
134
|
} else {
|
|
139
|
-
|
|
135
|
+
console.log(`Downloading folder to: ${options.output}`);
|
|
136
|
+
if (downloadOptions.resume) {
|
|
137
|
+
result = await downloadFolderWithResume(
|
|
138
|
+
parsedUrl,
|
|
139
|
+
options.output,
|
|
140
|
+
downloadOptions
|
|
141
|
+
);
|
|
142
|
+
} else {
|
|
143
|
+
result = await downloadFolder(parsedUrl, options.output);
|
|
144
|
+
}
|
|
140
145
|
}
|
|
146
|
+
} catch (opError) {
|
|
147
|
+
error = opError;
|
|
141
148
|
}
|
|
142
149
|
|
|
143
|
-
|
|
150
|
+
// Consolidated result and error handling
|
|
151
|
+
if (error) {
|
|
152
|
+
const failMsg =
|
|
153
|
+
operationType === "archive"
|
|
154
|
+
? `Archive creation failed: ${error.message}`
|
|
155
|
+
: `Download failed: ${error.message}`;
|
|
156
|
+
console.error(chalk.red(failMsg));
|
|
157
|
+
process.exit(1);
|
|
158
|
+
} else if (!createArchive && result && !result.success) {
|
|
159
|
+
console.error(chalk.red(`Download failed`));
|
|
160
|
+
process.exit(1);
|
|
161
|
+
} else if (!createArchive && result && result.isEmpty) {
|
|
162
|
+
console.log("Operation completed - no files to download!");
|
|
163
|
+
} else {
|
|
164
|
+
console.log("Operation completed successfully!");
|
|
165
|
+
}
|
|
144
166
|
} catch (error) {
|
|
145
167
|
console.error("Error:", error.message);
|
|
146
168
|
process.exit(1);
|
package/src/parser.js
CHANGED
|
@@ -16,10 +16,10 @@ export function parseGitHubUrl(url) {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
// Extract components from the matched pattern
|
|
19
|
-
const owner = match[1];
|
|
20
|
-
const repo = match[2];
|
|
21
|
-
const branch = match[3]; // Branch
|
|
22
|
-
const folderPath = match[4]
|
|
19
|
+
const owner = decodeURIComponent(match[1]);
|
|
20
|
+
const repo = decodeURIComponent(match[2]);
|
|
21
|
+
const branch = match[3] ? decodeURIComponent(match[3]) : ""; // Branch is an empty string if not present
|
|
22
|
+
const folderPath = match[4] ? decodeURIComponent(match[4]) : ""; // Empty string if no folder path
|
|
23
23
|
|
|
24
24
|
// Additional validation
|
|
25
25
|
if (!owner || !repo) {
|