Skip to content

Commit d628e0a

Browse files
committed
fix: skip intermediate folder for zip files coming from github
1 parent 73c3eae commit d628e0a

2 files changed

Lines changed: 70 additions & 6 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ This project consists of a CLI for creating, viewing and serving robopages as a
8282
Install robopages:
8383

8484
```bash
85-
# install https://github.com/dreadnode/robopages to ~/.robopages/robopages-main
85+
# install https://github.com/dreadnode/robopages to ~/.robopages/
8686
robopages install
8787

8888
# install a custom repository

src/cli/install.rs

Lines changed: 69 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,76 @@
1-
use std::io::Write;
1+
use std::{
2+
fs::File,
3+
io::{self, Write},
4+
path::{Path, PathBuf},
5+
};
26

37
use camino::Utf8PathBuf;
48

59
use super::InstallArgs;
610

11+
fn extract_archive_without_intermediate_folder(
12+
mut archive: zip::ZipArchive<File>,
13+
target_path: &Path,
14+
) -> io::Result<()> {
15+
// Iterate through each entry in the ZIP archive
16+
for i in 0..archive.len() {
17+
let mut file_in_zip = archive.by_index(i)?;
18+
let file_path = file_in_zip.mangled_name();
19+
20+
// Skip directories by default
21+
if file_in_zip.is_dir() {
22+
continue;
23+
}
24+
25+
// Strip the first component of the file path (e.g., intermediate-folder-name)
26+
let stripped_path = file_path.iter().skip(1).collect::<PathBuf>();
27+
let target_file_path = target_path.join(stripped_path);
28+
29+
// Create parent directories as needed
30+
if let Some(parent) = target_file_path.parent() {
31+
std::fs::create_dir_all(parent)?;
32+
}
33+
34+
// Write the file to the target path
35+
let mut outfile = File::create(&target_file_path)?;
36+
io::copy(&mut file_in_zip, &mut outfile)?;
37+
}
38+
39+
Ok(())
40+
}
41+
42+
fn extract_archive(archive_path: &Path, target_path: &Path) -> io::Result<()> {
43+
log::info!("extracting to {:?}", target_path);
44+
45+
let file = File::open(archive_path)?;
46+
let mut archive = zip::ZipArchive::new(file)?;
47+
48+
// check if all files share the same prefix
49+
let file_names: Vec<_> = archive.file_names().collect();
50+
let mut single_root_folder = false;
51+
52+
if !file_names.is_empty() {
53+
if let Some(first_name) = file_names.first() {
54+
if let Some(first_prefix) = first_name.split('/').next() {
55+
single_root_folder = file_names
56+
.iter()
57+
.all(|name| name.split('/').next() == Some(first_prefix))
58+
}
59+
}
60+
}
61+
62+
if single_root_folder {
63+
// if the archive comes from a github repository, it will have a single root folder
64+
// so we can extract it without the intermediate folder
65+
extract_archive_without_intermediate_folder(archive, target_path)?;
66+
} else {
67+
// otherwise, we extract the archive as it is
68+
archive.extract(target_path)?;
69+
}
70+
71+
Ok(())
72+
}
73+
774
pub(crate) async fn install(args: InstallArgs) -> anyhow::Result<()> {
875
let path = Utf8PathBuf::from(
976
shellexpand::full(args.path.as_str())
@@ -40,10 +107,7 @@ pub(crate) async fn install(args: InstallArgs) -> anyhow::Result<()> {
40107
file.write_all(&chunk)?;
41108
}
42109

43-
log::info!("extracting to {:?}", &path);
44-
45-
let mut archive = zip::ZipArchive::new(std::fs::File::open(temp_file.path())?)?;
46-
archive.extract(&path)?;
110+
extract_archive(temp_file.path(), path.as_std_path())?;
47111
}
48112

49113
Ok(())

0 commit comments

Comments
 (0)