1mod cache;
2mod config;
3mod npm;
4mod package;
5mod plan;
6mod progress;
7mod resolve;
8mod scoped_path;
9mod util;
10
11use color_eyre::eyre::Result;
12use color_eyre::owo_colors::OwoColorize;
13use compact_str::ToCompactString;
14use itertools::Itertools;
15use package::PackageMetadata;
16use plan::tree_size;
17use progress::{log_progress, log_verbose};
18use resolve::Lockfile;
19use std::collections::HashMap;
20use std::time::Instant;
21use tokio::fs::create_dir_all;
22use tokio::fs::read_to_string;
23use util::{read_package, write_json};
24
25use crate::util::load_graph_from_lockfile;
26use crate::{
27 plan::{execute_plan, Plan},
28 progress::PROGRESS_BAR,
29};
30
31pub const STORE_PATH: &str = "./target/.cotton/store";
32pub const NM_COTTON_PATH: &str = "./node_modules/.cotton";
33pub const NM_COTTON_PLAN_PATH: &str = "./node_modules/.cotton/plan.json";
34
35#[tokio::main]
36pub async fn run() -> Result<HashMap<String, String>> {
37 let package = read_package().await?;
38
39 init_storage().await?;
40
41 let start = Instant::now();
42
43 let plan = prepare_plan(&package).await?;
44 let size = tree_size(&plan.trees);
45
46 if matches!(verify_installation(&package, &plan).await, Ok(true)) {
47 log_verbose("Packages already installed")
48 } else {
49 execute_plan(plan.clone()).await?;
50
51 PROGRESS_BAR.suspend(|| {
52 if size > 0 {
53 println!(
54 "Installed {} packages in {}ms",
55 size.yellow(),
56 start.elapsed().as_millis().yellow()
57 )
58 }
59 });
60 write_json(NM_COTTON_PLAN_PATH, &plan).await?;
61 }
62
63 PROGRESS_BAR.finish_and_clear();
64
65 Ok(package.exports)
66}
67
68async fn prepare_plan(package: &PackageMetadata) -> Result<Plan> {
69 log_progress("Preparing");
70
71 let mut graph = load_graph_from_lockfile().await;
72
73 graph.append(package.iter_all(), true).await?;
74 write_json("cotton.lock", Lockfile::new(graph.clone())).await?;
75
76 log_progress("Retrieved dependency graph");
77
78 let trees = graph.build_trees(&package.iter_all().collect_vec())?;
79 log_progress(&format!("Fetched {} root deps", trees.len().yellow()));
80
81 let plan = Plan::new(
82 trees
83 .iter()
84 .map(|x| (x.root.name.to_compact_string(), x.clone()))
85 .collect(),
86 );
87
88 log_progress(&format!(
89 "Planned {} dependencies",
90 plan.trees.len().yellow()
91 ));
92
93 Ok(plan)
94}
95
96async fn read_plan(path: &str) -> Result<Plan> {
97 let plan = read_to_string(path).await?;
98 Ok(serde_json::from_str(&plan)?)
99}
100
101async fn verify_installation(package: &PackageMetadata, plan: &Plan) -> Result<bool> {
102 let installed = read_plan(NM_COTTON_PLAN_PATH).await?;
103
104 if &installed != plan {
105 return Ok(false);
106 }
107
108 Ok(installed.satisfies(package))
109}
110
111async fn init_storage() -> Result<()> {
112 create_dir_all(STORE_PATH).await?;
113 create_dir_all(NM_COTTON_PATH).await?;
114 Ok(())
115}