1use crate::download::{
2 create_client, download_binary, download_dist_manfiest, download_extract, download_json,
3 read_dist_manfiest,
4};
5use crate::manfiest::{self, Artifact, Asset, DistManifest};
6use crate::tool::{display_output, get_bin_name, get_filename, get_meta};
7use crate::{artifact::Artifacts, env::get_install_dir};
8use binstalk::manifests::cargo_toml_binstall::PkgFmt;
9use binstalk_registry::Registry;
10use detect_targets::detect_targets;
11use regex::Regex;
12use semver::VersionReq;
13use std::collections::HashMap;
14#[cfg(unix)]
15use std::os::unix::prelude::PermissionsExt;
16use std::path::PathBuf;
17use std::str::FromStr;
18use std::{fmt::Display, path::Path};
19use tracing::trace;
20
21#[derive(Debug, Clone, PartialEq, PartialOrd, Default)]
22pub struct OutputFile {
23 pub install_path: String,
24 pub mode: u32,
25 pub size: u32,
26 pub origin_path: String,
27 pub is_dir: bool,
28}
29#[derive(Debug, Clone, PartialEq, Default)]
30pub struct OutputItem {
31 pub install_dir: String,
32 pub bin_dir: String,
33 pub files: Vec<OutputFile>,
34}
35
36pub type Output = HashMap<String, OutputItem>;
37
38pub fn atomic_install(src: &Path, dst: &Path) -> std::io::Result<u64> {
39 std::fs::copy(src, dst)
40}
41
42pub fn write_to_file(src: &str, buffer: &[u8], mode: Option<u32>) {
43 let Ok(d) = std::path::PathBuf::from_str(src);
44 if let Some(p) = d.parent() {
45 std::fs::create_dir_all(p).expect("failed to create_dir_all");
46 }
47
48 std::fs::write(src, buffer).expect("failed to write file");
49
50 #[cfg(unix)]
51 if let Some(mode) = mode {
52 std::fs::set_permissions(src, PermissionsExt::from_mode(mode)).expect("failed to set_permissions");
53 }
54
55 #[cfg(windows)]
56 {
57 _ = mode;
58 }
59}
60
61pub async fn install(url: &str, dir: Option<String>) -> Output {
62 trace!("install {}", url);
63 if is_dist_manfiest(url) {
64 return install_from_manfiest(url, dir).await;
65 }
66 if is_url(url) {
67 if is_archive_file(url) {
68 return install_from_artifact_url(url, None, dir).await;
69 }
70
71 if is_exe_file(url) {
72 return install_from_single_file(url, None, dir).await;
73 }
74 }
75
76 if let Ok(repo) = Repo::try_from(url) {
77 return install_from_github(&repo, dir).await;
78 }
79
80 install_from_crate_name(url, dir).await
81}
82
83async fn install_from_crate_name(crate_name: &str, dir: Option<String>) -> Output {
84 trace!("install_from_crate_name {}", crate_name);
85 let client = create_client().await;
86 let version_req = &VersionReq::STAR;
87 let sparse_registry: Registry = Registry::crates_io_sparse_registry();
88 let manifest_from_sparse = sparse_registry
89 .fetch_crate_matched(client, crate_name, version_req)
90 .await
91 .unwrap();
92 let mut v = Output::new();
93 if let Some(pkg) = manifest_from_sparse.package {
94 if let Some(repository) = pkg.repository() {
95 if let Ok(repo) = Repo::try_from(repository) {
96 v.extend(install_from_github(&repo, dir).await);
97 }
98 }
99 }
100 v
101}
102async fn get_artifact_download_url(art_url: &str) -> Vec<String> {
103 if !art_url.contains("*") {
104 return vec![art_url.to_string()];
105 }
106
107 if let Ok(repo) = Repo::try_from(art_url) {
108 return repo.match_artifact_url(art_url).await;
109 }
110 vec![]
111}
112
113fn path_to_str(p: &Path) -> String {
114 p.to_str().unwrap().replace("\\", "/")
115}
116
117async fn install_from_single_file(
118 url: &str,
119 manfiest: Option<DistManifest>,
120 dir: Option<String>,
121) -> Output {
122 let mut install_dir = get_install_dir();
124 let mut output = Output::new();
125 if let Some(target_dir) = dir {
126 if target_dir.contains("/") || target_dir.contains("\\") {
127 install_dir = target_dir.into();
128 } else {
129 install_dir.push(target_dir);
130 }
131 }
132
133 if let Some(bin) = download_binary(url).await {
134 let artifact = manfiest.and_then(|i| i.get_artifact_by_key(url));
135
136 let art_name = url
137 .split("/")
138 .last()
139 .map(|i| i.to_string())
140 .expect("can't get artifact name");
141 let name = artifact.and_then(|i| i.name).unwrap_or(art_name);
142 let mut install_path = install_dir.clone();
143 install_path.push(get_bin_name(&name));
144
145 if let Some(dir) = install_path.parent() {
146 std::fs::create_dir_all(dir).expect("Failed to create_dir dir");
147 }
148 std::fs::write(&install_path, &bin).expect("write file failed");
149 let (mode, size, is_dir) = get_meta(&install_path);
150 let install_path = path_to_str(&install_path);
151 println!("Installation Successful");
152 let origin_path = url.split("/").last().unwrap_or(name.as_str()).to_string();
153
154 let files = vec![OutputFile {
155 mode,
156 size,
157 origin_path,
158 is_dir,
159 install_path,
160 }];
161
162 let bin_dir_str = path_to_str(&install_dir);
163 let item = OutputItem {
164 install_dir: bin_dir_str.clone(),
165 bin_dir: bin_dir_str.clone(),
166 files,
167 };
168
169 output.insert(url.to_string(), item);
170 println!("{}", display_output(&output));
171 } else {
172 println!("not found/download artifact for {url}")
173 }
174 output
175}
176
177async fn install_from_artifact_url(
178 art_url: &str,
179 manfiest: Option<DistManifest>,
180 dir: Option<String>,
181) -> Output {
182 trace!("install_from_artifact_url {}", art_url);
183 let urls = get_artifact_download_url(art_url).await;
184 let mut v = Output::new();
185 if urls.is_empty() {
186 println!("not found download_url for {art_url}");
187 return v;
188 }
189 if urls.len() == 1 && !is_archive_file(&urls[0]) {
190 println!("download {}", urls[0]);
191 let output = install_from_single_file(&urls[0], manfiest.clone(), dir.clone()).await;
192 return output;
193 }
194 for url in urls {
195 println!("download {}", url);
196 let output = install_from_download_file(&url, manfiest.clone(), dir.clone()).await;
197 v.extend(output);
199 }
200 v
201}
202
203fn replace_filename(base_url: &str, name: &str) -> String {
204 if let Some(pos) = base_url.rfind('/') {
205 format!("{}{}", &base_url[..pos + 1], name)
206 } else {
207 name.to_string()
208 }
209}
210
211async fn get_artifact_url_from_manfiest(url: &str, manfiest: &DistManifest) -> Vec<String> {
212 let targets = detect_targets().await;
213 let mut v = vec![];
214 for (name, art) in manfiest.artifacts.iter() {
215 if art.match_targets(&targets)
216 && art.kind.clone().unwrap_or("executable-zip".to_owned()) == "executable-zip"
218 {
219 if !is_url(name) {
220 v.push(replace_filename(url, name));
221 } else {
222 v.push(name.clone());
223 }
224 }
225 }
226 v
227}
228
229async fn install_from_manfiest(url: &str, dir: Option<String>) -> Output {
230 trace!("install_from_manfiest {}", url);
231 let manfiest = if is_url(url) {
232 download_dist_manfiest(url).await
233 } else {
234 read_dist_manfiest(url)
235 };
236
237 let mut v = Output::new();
238 if let Some(manfiest) = manfiest {
239 let art_url_list = get_artifact_url_from_manfiest(url, &manfiest).await;
240 if art_url_list.is_empty() {
241 println!("install_from_manfiest {} failed", url);
242 return v;
243 }
244 for art_url in art_url_list {
245 trace!("install_from_manfiest art_url {}", art_url);
246 v.extend(
247 install_from_artifact_url(&art_url, Some(manfiest.clone()), dir.clone()).await,
248 );
249 }
250 }
251 v
252}
253
254fn remove_postfix(s: &str) -> String {
255 use PkgFmt::*;
256 for i in [Tar, Tbz2, Tgz, Txz, Tzstd, Zip, Bin] {
257 for ext in i.extensions(IS_WINDOWS) {
258 if !ext.is_empty() && s.ends_with(ext) {
259 return s[0..s.len() - ext.len()].to_string();
260 }
261 }
262 }
263 s.to_string()
264}
265
266impl Artifact {
267 fn has_file(&self, p: &str) -> bool {
268 let mut p = p.to_string().replace("\\", "/");
269 if let Some(name) = &(self.name) {
272 let prefix = remove_postfix(name) + "/";
273 if p.starts_with(&prefix) {
274 p = p[prefix.len()..].to_string();
275 }
276 }
277
278 for i in &self.assets {
279 let name = PathBuf::from_str(&p).unwrap().to_str().unwrap().to_string();
280 if i.path.clone().unwrap_or_default() == "*" {
281 return true;
282 }
283 if Some(name.as_str()) == i.path.as_deref() {
284 return match &i.kind {
285 manfiest::AssetKind::Executable(_) => true,
286 manfiest::AssetKind::ExecutableDir(_) => false,
287 manfiest::AssetKind::CDynamicLibrary(_) => true,
288 manfiest::AssetKind::CStaticLibrary(_) => true,
289 manfiest::AssetKind::Readme => false,
290 manfiest::AssetKind::License => false,
291 manfiest::AssetKind::Changelog => false,
292 manfiest::AssetKind::Unknown => false,
293 };
294 }
295 }
296 false
297 }
298
299 fn match_targets(&self, targets: &Vec<String>) -> bool {
300 for i in targets {
301 if self.target_triples.contains(i) {
302 return true;
303 }
304 }
305 false
306 }
307
308 fn get_assets_executable_dir(&self) -> Option<Asset> {
309 for i in self.assets.clone() {
310 if let manfiest::AssetKind::ExecutableDir(_) = i.kind {
311 return Some(i);
312 }
313 }
314 None
315 }
316
317 fn get_asset(&self, path: &str) -> Option<Asset> {
318 self.assets.clone().into_iter().find_map(|i| {
319 if i.path == Some(path.to_owned()) {
320 return Some(i);
321 }
322 None
323 })
324 }
325}
326
327impl DistManifest {
328 fn get_artifact(&self, targets: &Vec<String>) -> Option<Artifact> {
329 self.artifacts.clone().into_iter().find_map(|(_, art)| {
330 if art.match_targets(targets)
331 && art.kind.clone().unwrap_or("executable-zip".to_owned()) == "executable-zip"
333 {
334 return Some(art);
335 }
336 None
337 })
338 }
339
340 fn get_artifact_by_key(&self, key: &str) -> Option<Artifact> {
341 self.artifacts.get(key).cloned()
342 }
343}
344
345#[cfg(unix)]
346pub(crate) fn add_execute_permission(file_path: &str) -> std::io::Result<()> {
347 use std::os::unix::fs::PermissionsExt;
348 let metadata = std::fs::metadata(file_path)?;
349 if metadata.is_dir() {
350 return Ok(());
351 }
352
353 let mut permissions = metadata.permissions();
354 let current_mode = permissions.mode();
355
356 let new_mode = current_mode | 0o111;
357 permissions.set_mode(new_mode);
358
359 std::fs::set_permissions(file_path, permissions)?;
360
361 Ok(())
362}
363
364async fn install_from_download_file(
365 url: &str,
366 manfiest: Option<DistManifest>,
367 dir: Option<String>,
368) -> Output {
369 trace!("install_from_download_file");
370 let mut install_dir = get_install_dir();
371 let mut v: OutputItem = Default::default();
372 let mut files: Vec<OutputFile> = vec![];
373 let targets = detect_targets().await;
374 let artifact = manfiest.and_then(|i| i.get_artifact(&targets));
375 let mut output = Output::new();
376 if let Some(asset) = artifact.clone().and_then(|a| a.get_assets_executable_dir()) {
377 if let Some(target_dir) = dir.clone().or(asset.name) {
378 if target_dir.contains("/") || target_dir.contains("\\") {
379 install_dir = target_dir.into();
380 } else {
381 install_dir.push(target_dir);
382 }
383
384 let prefix = asset.path.unwrap_or("".to_string());
385
386 let install_dir_str = path_to_str(&install_dir);
387
388 let mut bin_dir = install_dir.clone();
389 if let Some(ref dir) = asset.executable_dir {
390 bin_dir.push(dir);
391 }
392 let bin_dir_str = path_to_str(&bin_dir);
393 v.bin_dir = bin_dir_str;
394 v.install_dir = install_dir_str;
395
396 if let Some(download_files) = download_extract(url).await {
397 for (entry_path, entry) in download_files {
398 let size = entry.buffer.len() as u32;
399 let is_dir = entry.is_dir;
400 if is_dir {
401 continue;
402 }
403 let mut dst = install_dir.clone();
404 dst.push(entry_path.replace(&(prefix.clone() + "/"), ""));
405
406 write_to_file(dst.to_string_lossy().as_ref(), &entry.buffer, entry.mode);
424 let mode = entry.mode.unwrap_or(get_meta(&dst).0);
425
426 files.push(OutputFile {
427 install_path: path_to_str(&dst),
428 mode,
429 size,
430 origin_path: entry_path,
431 is_dir,
432 });
433 }
434
435 v.files = files;
436 if !v.files.is_empty() {
437 println!("Installation Successful");
438 output.insert(url.to_string(), v);
439 println!("{}", display_output(&output));
440 }
441 }
442 } else {
443 println!("Maybe you should use -d to set the folder");
444 }
445 } else {
446 if let Some(ref target_dir) = dir {
447 if target_dir.contains("/") || target_dir.contains("\\") {
448 install_dir = target_dir.into();
449 } else {
450 install_dir.push(target_dir);
451 }
452 }
453 let install_dir_str = path_to_str(&install_dir);
454
455 v.bin_dir = install_dir_str.clone();
456 v.install_dir = install_dir_str;
457
458 let allow = |p: &str| -> bool {
459 match artifact.clone() {
460 None => true,
461 Some(art) => art.has_file(p),
462 }
463 };
464 if let Some(download_files) = download_extract(url).await {
465 for (entry_path, entry) in download_files {
466 let size = entry.buffer.len() as u32;
467 let is_dir = entry.is_dir;
468 if is_dir || !allow(&entry_path) {
469 continue;
470 }
471
472 let mut dst = install_dir.clone();
473
474 let file_name = get_filename(&entry_path).expect("failed to get filename");
475 let name = artifact
476 .clone()
477 .and_then(|a| a.get_asset(&entry_path).and_then(|i| i.executable_name))
478 .unwrap_or(file_name.clone());
479
480 dst.push(get_bin_name(&name));
481 write_to_file(dst.to_string_lossy().as_ref(), &entry.buffer, entry.mode);
482 let mode = entry.mode.unwrap_or(get_meta(&dst).0);
483 files.push(OutputFile {
484 install_path: path_to_str(&dst),
485 mode,
486 size,
487 origin_path: entry_path,
488 is_dir,
489 });
490 }
491 v.files = files;
492 if !v.files.is_empty() {
493 println!("Installation Successful");
494 output.insert(url.to_string(), v);
495 println!("{}", display_output(&output));
496 }
497 }
498 }
499
500 output
501}
502
503#[derive(Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]
504struct Repo {
505 pub owner: String,
506 pub name: String,
507 pub tag: Option<String>,
508}
509
510impl TryFrom<&str> for Repo {
511 type Error = ();
512
513 fn try_from(value: &str) -> Result<Self, Self::Error> {
514 trace!("get_artifact_api {}", value);
515 let re_gh_tag = Regex::new(
516 r"https?://github\.com/(?P<owner>[^/]+)/(?P<repo>[^/]+)/releases/tag/(?P<tag>[^/]+)",
517 )
518 .unwrap();
519
520 let re_gh_download_tag = Regex::new(r"https?://github\.com/(?P<owner>[^/]+)/(?P<repo>[^/]+)/releases/download/(?P<tag>[^/]+)/(?P<filename>.+)").unwrap();
521
522 let re_gh_releases =
523 Regex::new(r"http?s://github\.com/(?P<owner>[^/]+)/(?P<repo>[^/]+)").unwrap();
524
525 if let Some(captures) = re_gh_tag.captures(value) {
526 if let (Some(owner), Some(name), Some(tag)) = (
527 captures.name("owner"),
528 captures.name("repo"),
529 captures.name("tag"),
530 ) {
531 return Ok(Repo {
532 owner: owner.as_str().to_string(),
533 name: name.as_str().to_string(),
534 tag: Some(tag.as_str().to_string()),
535 });
536 }
537 }
538
539 if let Some(captures) = re_gh_download_tag.captures(value) {
540 if let (Some(owner), Some(name), Some(tag)) = (
541 captures.name("owner"),
542 captures.name("repo"),
543 captures.name("tag"),
544 ) {
545 return Ok(Repo {
546 owner: owner.as_str().to_string(),
547 name: name.as_str().to_string(),
548 tag: Some(tag.as_str().to_string()),
549 });
550 }
551 }
552
553 if let Some(captures) = re_gh_releases.captures(value) {
554 if let (Some(owner), Some(name)) = (captures.name("owner"), captures.name("repo")) {
555 return Ok(Repo {
556 owner: owner.as_str().to_string(),
557 name: name.as_str().to_string(),
558 tag: None,
559 });
560 }
561 }
562 Err(())
563 }
564}
565
566impl Repo {
567 fn get_gh_url(&self) -> String {
568 format!("https://github.com/{}/{}", self.owner, self.name)
569 }
570
571 fn get_artifact_api(&self) -> String {
572 trace!("get_artifact_api {}/{}", self.owner, self.name);
573 if let Some(tag) = &self.tag {
574 return format!(
575 "https://api.github.com/repos/{}/{}/releases/tags/{}",
576 self.owner, self.name, tag
577 );
578 }
579
580 format!(
581 "https://api.github.com/repos/{}/{}/releases/latest",
582 self.owner, self.name,
583 )
584 }
585
586 fn get_manfiest_url(&self) -> String {
587 match &self.tag {
588 Some(t) => format!(
589 "https://github.com/{}/{}/releases/download/{}/dist-manifest.json",
590 self.owner, self.name, t
591 ),
592 None => format!(
593 "https://github.com/{}/{}/releases/latest/download/dist-manifest.json",
594 self.owner, self.name
595 ),
596 }
597 }
598
599 async fn get_manfiest(&self) -> Option<DistManifest> {
600 download_dist_manfiest(&self.get_manfiest_url()).await
601 }
602
603 async fn get_artifact_url(&self) -> Vec<String> {
604 trace!("get_artifact_url {}/{}", self.owner, self.name);
605 let api = self.get_artifact_api();
606 trace!("get_artifact_url api {}", api);
607 let mut v = vec![];
608 if let Some(artifacts) = download_json::<Artifacts>(&api).await {
609 let targets = detect_targets().await;
610 let mut filter = vec![];
611 for i in artifacts.assets {
612 for pat in &targets {
613 let remove_target = i.name.replace(pat, "");
614 if i.name.contains(pat)
615 && is_archive_file(&i.name)
616 && !filter.contains(&remove_target)
617 {
618 v.push(i.browser_download_url.clone());
619 filter.push(remove_target)
620 }
621 }
622 }
623 }
624
625 v
626 }
627
628 async fn match_artifact_url(&self, pattern: &str) -> Vec<String> {
629 trace!("get_artifact_url {}/{}", self.owner, self.name);
630 let api = self.get_artifact_api();
631 trace!("get_artifact_url api {}", api);
632
633 let mut v = vec![];
634 let re = Regex::new(pattern).unwrap();
635 let pattern_name = pattern.split("/").last();
636 let name_re = pattern_name.map(|i| Regex::new(i).unwrap());
637 if let Some(artifacts) = download_json::<Artifacts>(&api).await {
638 for art in artifacts.assets {
639 if !is_hash_file(&art.browser_download_url)
640 && !is_msi_file(&art.browser_download_url)
641 && (re.is_match(&art.browser_download_url)
642 || name_re.clone().map(|r| r.is_match(&art.name)) == Some(true))
643 {
644 v.push(art.browser_download_url);
645 }
646 }
647 }
648 v
649 }
650}
651
652impl Display for Repo {
653 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
654 match &self.tag {
655 Some(t) => f.write_str(&format!("{}/{}@{}", self.owner, self.name, t)),
656 None => f.write_str(&format!("{}/{}", self.owner, self.name)),
657 }
658 }
659}
660
661async fn install_from_github(repo: &Repo, dir: Option<String>) -> Output {
662 trace!("install_from_git {}", repo);
663 let artifact_url = repo.get_artifact_url().await;
664 let mut v = Output::new();
665 if !artifact_url.is_empty() {
666 for i in artifact_url {
667 trace!("install_from_git artifact_url {}", i);
668 let manfiest = repo.get_manfiest().await;
669 v.extend(install_from_artifact_url(&i, manfiest, dir.clone()).await);
670 }
671 } else {
672 println!(
673 "not found asset for {} on {}",
674 detect_targets().await.join(","),
675 repo.get_gh_url()
676 );
677 }
678 v
679}
680
681const IS_WINDOWS: bool = cfg!(target_os = "windows");
682
683fn is_archive_file(s: &str) -> bool {
684 use PkgFmt::*;
685
686 for i in [
687 Tar, Tbz2, Tgz, Txz, Tzstd, Zip,
688 ] {
690 for ext in i.extensions(IS_WINDOWS) {
691 if !ext.is_empty() && s.ends_with(ext) {
692 return true;
693 }
694 }
695 }
696
697 false
698}
699
700pub fn is_exe_file(s: &str) -> bool {
701 if s.ends_with(".exe") {
702 return true;
703 }
704 let re_latest =
705 Regex::new(r"^https://github\.com/([^/]+)/([^/]+)/releases/latest/download/([^/]+)$")
706 .expect("failed to build github latest release regex");
707 let re_tag =
708 Regex::new(r"^https://github\.com/([^/]+)/([^/]+)/releases/download/([^/]+)/([^/]+)$")
709 .expect("failed to build github release regex");
710
711 for (re, n) in [(re_latest, 3), (re_tag, 4)] {
712 if let Some(cap) = re.captures(s) {
713 if let Some(name) = cap.get(n) {
714 if is_archive_file(name.as_str()) {
715 return false;
716 }
717 if !name.as_str().contains(".") {
718 return true;
719 }
720 }
721 }
722 }
723
724 false
725}
726
727fn is_url(s: &str) -> bool {
728 s.starts_with("http://") || s.starts_with("https://")
729}
730
731fn is_dist_manfiest(s: &str) -> bool {
732 s.ends_with(".json")
733}
734
735fn is_hash_file(s: &str) -> bool {
736 s.ends_with(".sha256")
737}
738
739fn is_msi_file(s: &str) -> bool {
740 s.ends_with(".msi")
741}
742
743#[cfg(test)]
744mod test {
745 use crate::{
746 download::{download_dist_manfiest, download_extract, read_dist_manfiest},
747 env::IS_WINDOWS,
748 install::{
749 get_artifact_download_url, get_artifact_url_from_manfiest, is_archive_file,
750 is_exe_file, is_url, Repo,
751 },
752 };
753
754 #[test]
755 fn test_is_file() {
756 assert!(!is_archive_file("https://github.com/ahaoboy/ansi2"));
757
758 assert!(!is_archive_file(
759 "https://api.github.com/repos/ahaoboy/ansi2/releases/latest"
760 ));
761 assert!(!is_archive_file(
762 "https://github.com/ahaoboy/ansi2/releases/tag/v0.2.11"
763 ));
764 assert!(is_archive_file("https://github.com/ahaoboy/ansi2/releases/download/v0.2.11/ansi2-x86_64-unknown-linux-musl.tar.gz"));
765 assert!(is_archive_file("https://github.com/ahaoboy/ansi2/releases/download/v0.2.11/ansi2-x86_64-pc-windows-msvc.zip"));
766 }
767
768 #[test]
769 fn test_is_github() {
770 let repo = Repo {
771 owner: "ahaoboy".to_string(),
772 name: "ansi2".to_string(),
773 tag: None,
774 };
775 assert_eq!(
776 Repo::try_from("https://github.com/ahaoboy/ansi2").unwrap(),
777 repo
778 );
779
780 assert!(
781 Repo::try_from("https://api.github.com/repos/ahaoboy/ansi2/releases/latest").is_err()
782 );
783
784 let repo = Repo {
785 owner: "ahaoboy".to_string(),
786 name: "ansi2".to_string(),
787 tag: Some("v0.2.11".to_string()),
788 };
789
790 assert_eq!(
791 Repo::try_from("https://github.com/ahaoboy/ansi2/releases/tag/v0.2.11").unwrap(),
792 repo
793 );
794
795 assert_eq!(
796 Repo::try_from("https://github.com/ahaoboy/ansi2/releases/download/v0.2.11/ansi2-x86_64-unknown-linux-musl.tar.gz").unwrap(),
797 repo
798 );
799
800 assert_eq!(
801 Repo::try_from("https://github.com/ahaoboy/ansi2/releases/download/v0.2.11/ansi2-x86_64-pc-windows-msvc.zip").unwrap(),
802 repo
803 );
804
805 let repo = Repo {
806 owner: "Ryubing".to_string(),
807 name: "Ryujinx".to_string(),
808 tag: Some("1.2.78".to_string()),
809 };
810 assert_eq!(
811 Repo::try_from("https://github.com/Ryubing/Ryujinx/releases/download/1.2.78/ryujinx-*.*.*-win_x64.zip").unwrap(),
812 repo
813 );
814 }
815
816 #[test]
817 fn test_is_url() {
818 assert!(is_url("https://github.com/ahaoboy/ansi2"));
819 assert!(!is_url("ansi2"));
820 }
821
822 #[tokio::test]
823 async fn test_get_artifact_url() {
824 let repo = Repo::try_from("https://github.com/ahaoboy/mujs-build").unwrap();
825 let url = repo.get_artifact_url().await[0].clone();
826 let files = download_extract(&url).await.unwrap();
827 assert!(files
828 .get(if IS_WINDOWS { "mujs.exe" } else { "mujs" })
829 .is_some());
830 }
831
832 #[tokio::test]
833 async fn test_get_artifact_api() {
834 let repo = Repo::try_from("https://github.com/axodotdev/cargo-dist").unwrap();
835 let url = repo.get_artifact_api();
836 assert_eq!(
837 url,
838 "https://api.github.com/repos/axodotdev/cargo-dist/releases/latest"
839 )
840 }
841 #[tokio::test]
842 async fn test_get_manfiest() {
843 let repo = Repo::try_from("https://github.com/axodotdev/cargo-dist/releases").unwrap();
844 let url = repo.get_manfiest_url();
845 assert_eq!(
846 url,
847 "https://github.com/axodotdev/cargo-dist/releases/latest/download/dist-manifest.json"
848 );
849 assert!(repo.get_manfiest().await.is_some());
850
851 let repo =
852 Repo::try_from("https://github.com/axodotdev/cargo-dist/releases/tag/v0.25.1").unwrap();
853 let url = repo.get_manfiest_url();
854 assert_eq!(
855 url,
856 "https://github.com/axodotdev/cargo-dist/releases/download/v0.25.1/dist-manifest.json"
857 );
858
859 let manfiest = repo.get_manfiest().await.unwrap();
860 assert!(!manfiest.artifacts.is_empty());
861
862 let repo =
863 Repo::try_from("https://github.com/ahaoboy/mujs-build/releases/tag/v0.0.2").unwrap();
864 let url = repo.get_manfiest_url();
865 assert_eq!(
866 url,
867 "https://github.com/ahaoboy/mujs-build/releases/download/v0.0.2/dist-manifest.json"
868 );
869
870 let manfiest = repo.get_manfiest().await.unwrap();
871 assert!(!manfiest.artifacts.is_empty())
872 }
873
874 #[tokio::test]
875 async fn test_manifest_jsc() {
876 let repo = Repo {
877 owner: "ahaoboy".to_string(),
878 name: "jsc-build".to_string(),
879 tag: None,
880 };
881
882 let manifest = repo.get_manfiest().await.unwrap();
883 let art = manifest
884 .get_artifact(&vec!["x86_64-unknown-linux-gnu".to_string()])
885 .unwrap();
886
887 assert!(art.has_file("bin/jsc"));
888 assert!(art.has_file("lib/libJavaScriptCore.a"));
889 assert!(!art.has_file("lib/jsc"));
890 }
891
892 #[tokio::test]
893 async fn test_manifest_mujs() {
894 let repo = Repo {
895 owner: "ahaoboy".to_string(),
896 name: "mujs-build".to_string(),
897 tag: None,
898 };
899
900 let manifest = repo.get_manfiest().await.unwrap();
901 let art = manifest
902 .get_artifact(&vec!["x86_64-unknown-linux-gnu".to_string()])
903 .unwrap();
904
905 assert!(art.has_file("mujs"));
906 assert!(!art.has_file("mujs.exe"));
907
908 let manifest = repo.get_manfiest().await.unwrap();
909 let art = manifest
910 .get_artifact(&vec!["x86_64-pc-windows-gnu".to_string()])
911 .unwrap();
912
913 assert!(!art.has_file("mujs"));
914 assert!(art.has_file("mujs.exe"));
915 }
916
917 #[tokio::test]
918 async fn test_install_from_manfiest() {
919 let url =
920 "https://github.com/ahaoboy/mujs-build/releases/latest/download/dist-manifest.json";
921 let manfiest = download_dist_manfiest(url).await.unwrap();
922 let art_url = get_artifact_url_from_manfiest(url, &manfiest).await;
923 assert!(!art_url.is_empty())
924 }
925
926 #[tokio::test]
927 async fn test_cargo_dist() {
928 let url =
929 "https://github.com/axodotdev/cargo-dist/releases/download/v1.0.0-rc.1/dist-manifest.json";
930 let manfiest = download_dist_manfiest(url).await.unwrap();
931 let art_url = get_artifact_url_from_manfiest(url, &manfiest).await;
932 assert!(!art_url.is_empty())
933 }
934
935 #[tokio::test]
936 async fn test_deno() {
937 let url = "https://github.com/denoland/deno";
938 let repo = Repo::try_from(url).unwrap();
939 let artifact_url = repo.get_artifact_url().await;
940 assert_eq!(artifact_url.len(), 2);
941 }
942
943 #[tokio::test]
944 async fn test_get_artifact_download_url() {
945 for url in [
946 "https://github.com/Ryubing/Ryujinx/releases/latest/download/^ryujinx-*.*.*-win_x64.zip",
947 "https://github.com/Ryubing/Ryujinx/releases/download/1.2.80/ryujinx-*.*.*-win_x64.zip",
948 "https://github.com/Ryubing/Ryujinx/releases/download/1.2.78/ryujinx-*.*.*-win_x64.zip",
949 "https://github.com/shinchiro/mpv-winbuild-cmake/releases/latest/download/^mpv-x86_64-v3-.*?-git-.*?",
950 "https://github.com/NickeManarin/ScreenToGif/releases/latest/download/ScreenToGif.[0-9]*.[0-9]*.[0-9]*.Portable.x64.zip",
951 "https://github.com/ip7z/7zip/releases/latest/download/7z.*?-linux-x64.tar.xz",
952 "https://github.com/mpv-easy/mpv-winbuild/releases/latest/download/mpv-x86_64-v3-.*?-git-.*?.zip",
953 ]{
954 let art_url = get_artifact_download_url(url).await;
955 assert_eq!(art_url.len(), 1);
956 }
957 }
958
959 #[tokio::test]
960 async fn test_starship() {
961 let repo = Repo::try_from("https://github.com/starship/starship").unwrap();
962 let artifact_url = repo.get_artifact_url().await;
963 assert_eq!(artifact_url.len(), 1);
964 }
965
966 #[tokio::test]
967 async fn test_quickjs_ng() {
968 let json = "./dist-manifest/quickjs-ng.json";
969 let manifest = read_dist_manfiest(json).unwrap();
970 let urls = get_artifact_url_from_manfiest(json, &manifest).await;
971 assert_eq!(urls.len(), 2);
972
973 for i in urls {
974 let download_urls = get_artifact_download_url(&i).await;
975 assert_eq!(download_urls.len(), 1);
976 }
977 }
978
979 #[tokio::test]
980 async fn test_graaljs() {
981 let json = "./dist-manifest/graaljs.json";
982 let manifest = read_dist_manfiest(json).unwrap();
983 let urls = get_artifact_url_from_manfiest(json, &manifest).await;
984 assert_eq!(urls.len(), 1);
985
986 for i in urls {
987 let download_urls = get_artifact_download_url(&i).await;
988 assert_eq!(download_urls.len(), 1);
989 }
990 }
991
992 #[test]
993 fn test_is_exe_file() {
994 for (a,b) in [
995 ("https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp.exe", true),
996 ("https://github.com/pnpm/pnpm/releases/latest/download/pnpm-win-x64.exe", true),
997 ("https://github.com/pnpm/pnpm/releases/latest/download/pnpm-win-x64", true),
998 ("https://github.com/easy-install/easy-install/releases/download/v0.1.5/ei-x86_64-apple-darwin.tar.gz", false),
999 ("https://github.com/easy-install/easy-install", false),
1000 ("https://github.com/easy-install/easy-install/releases/tag/v0.1.5", false)
1001 ]{
1002 assert_eq!(is_exe_file(a),b);
1003 }
1004 }
1005}