1use std::path::Path;
4
5use crate::lockfile;
6use crate::registry;
7use crate::utils;
8
9fn base_name(package: &str) -> &str {
11 if let Some(idx) = package.rfind('@') {
12 if idx > 0 && !package[idx + 1..].contains('/') {
13 return &package[..idx];
14 }
15 }
16 package
17}
18
19pub fn prefetch_from_lockfile(quiet: bool) -> Result<(), String> {
22 let specs = crate::install::resolve_install_from_package_json(true)?;
23 if specs.is_empty() {
24 if !quiet {
25 println!("No dependencies to prefetch.");
26 }
27 return Ok(());
28 }
29 let (resolved_urls, resolved_integrity) = lockfile::read_resolved_urls_and_integrity_from_dir(Path::new("."))
30 .ok_or("No package-lock.json or bun.lock found.")?;
31 let cache_dir = std::path::PathBuf::from(utils::get_cache_dir());
32 let store_dir = cache_dir.join("store");
33 std::fs::create_dir_all(&store_dir).map_err(|e| e.to_string())?;
34
35 let mut work: Vec<(String, String, Option<String>)> = Vec::new();
36 for spec in &specs {
37 if utils::get_cached_tarball(spec).is_some() {
38 continue;
39 }
40 let url = resolved_urls.get(spec).cloned().or_else(|| {
41 let base = base_name(spec);
42 let version = spec.rfind('@').map(|i| &spec[i + 1..]).unwrap_or("latest");
43 Some(lockfile::tarball_url_from_registry(base, version))
44 });
45 if let Some(url) = url {
46 let integrity = resolved_integrity.get(spec).cloned();
47 work.push((spec.clone(), url, integrity));
48 }
49 }
50
51 const PREFETCH_CONCURRENCY: usize = 8;
52 let mut fetched = 0usize;
53 let mut index_batch: std::collections::HashMap<String, String> = std::collections::HashMap::new();
54 for chunk in work.chunks(PREFETCH_CONCURRENCY) {
55 use std::sync::mpsc;
56 use std::thread;
57 let (tx, rx) = mpsc::channel();
58 for (spec, url, integrity) in chunk {
59 let spec = spec.clone();
60 let url = url.clone();
61 let integrity = integrity.clone();
62 let cache_dir = cache_dir.clone();
63 let tx = tx.clone();
64 if !quiet {
65 println!("Prefetching {}...", spec);
66 }
67 thread::spawn(move || {
68 let res = registry::download_tarball_to_store_hash_only(
69 &url,
70 &cache_dir,
71 &spec,
72 integrity.as_deref(),
73 );
74 let _ = tx.send((spec, res));
75 });
76 }
77 drop(tx);
78 for (spec, res) in rx {
79 let hash = res?;
80 index_batch.insert(spec, hash);
81 fetched += 1;
82 }
83 }
84
85 if !index_batch.is_empty() {
86 let mut index = utils::read_store_index();
87 index.extend(index_batch);
88 utils::write_store_index(&index).map_err(|e| e.to_string())?;
89 }
90
91 if !quiet && fetched > 0 {
92 println!("Prefetched {} package(s).", fetched);
93 }
94 Ok(())
95}