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