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";
34pub const NM_COTTON_PATH: &str = "./node_modules/.cotton";
35pub const NM_COTTON_PLAN_PATH: &str = "./node_modules/.cotton/plan.json";
36
37pub struct NpmConfig {
39 pub base_path: String,
40 pub store_path: String,
41 pub nm_cotton_path: String,
42 pub nm_cotton_plan_path: String,
43}
44
45impl Default for NpmConfig {
46 fn default() -> Self {
47 Self {
48 base_path: ".".to_string(),
49 store_path: STORE_PATH.to_string(),
50 nm_cotton_path: NM_COTTON_PATH.to_string(),
51 nm_cotton_plan_path: NM_COTTON_PLAN_PATH.to_string(),
52 }
53 }
54}
55
56impl NpmConfig {
57 pub fn with_base_path(base_path: &str) -> Self {
58 Self {
59 base_path: base_path.to_string(),
60 store_path: format!("{}/target/.cotton/store", base_path),
61 nm_cotton_path: format!("{}/node_modules/.cotton", base_path),
62 nm_cotton_plan_path: format!("{}/node_modules/.cotton/plan.json", base_path),
63 }
64 }
65}
66
67#[tokio::main]
68pub async fn run() -> Result<HashMap<String, String>> {
69 run_with_config(NpmConfig::default()).await
70}
71
72pub async fn run_with_path(base_path: &str) -> Result<HashMap<String, String>> {
73 run_with_config(NpmConfig::with_base_path(base_path)).await
74}
75
76async fn run_with_config(config: NpmConfig) -> Result<HashMap<String, String>> {
77 let package = read_package().await?;
78
79 init_storage(&config).await?;
80
81 let start = Instant::now();
82
83 let plan = prepare_plan(&package, &config).await?;
84 let size = tree_size(&plan.trees);
85
86 if matches!(verify_installation(&package, &plan, &config).await, Ok(true)) {
87 log_verbose("Packages already installed")
88 } else {
89 log_warning("Executing plan");
90 execute_plan(plan.clone()).await?;
91
92 PROGRESS_BAR.suspend(|| {
93 if size > 0 {
94 println!(
95 "Installed {} packages in {}ms",
96 size.yellow(),
97 start.elapsed().as_millis().yellow()
98 )
99 }
100 });
101 write_json(&config.nm_cotton_plan_path, &plan).await?;
102 }
103
104 PROGRESS_BAR.finish_and_clear();
105
106 Ok(package.exports.to_string_map())
107}
108
109async fn prepare_plan(package: &PackageMetadata, config: &NpmConfig) -> Result<Plan> {
110 log_progress("Preparing");
111
112 let mut graph = load_graph_from_lockfile().await;
113
114 graph.append(package.iter_all(), true).await?;
115 let lockfile_path = format!("{}/cotton.lock", config.base_path);
116 write_json(&lockfile_path, Lockfile::new(graph.clone())).await?;
117
118 log_progress("Retrieved dependency graph");
119
120 let trees = graph.build_trees(&package.iter_all().collect_vec())?;
121 log_progress(&format!("Fetched {} root deps", trees.len().yellow()));
122
123 let plan = Plan::new(
124 trees
125 .iter()
126 .map(|x| (x.root.name.to_compact_string(), x.clone()))
127 .collect(),
128 );
129
130 log_progress(&format!(
131 "Planned {} dependencies",
132 plan.trees.len().yellow()
133 ));
134
135 Ok(plan)
136}
137
138async fn read_plan(path: &str) -> Result<Plan> {
139 let plan = read_to_string(path).await?;
140 Ok(serde_json::from_str(&plan)?)
141}
142
143async fn verify_installation(package: &PackageMetadata, plan: &Plan, config: &NpmConfig) -> Result<bool> {
144 let installed = read_plan(&config.nm_cotton_plan_path).await?;
145
146 if &installed != plan {
147 return Ok(false);
148 }
149
150 Ok(installed.satisfies(package))
151}
152
153async fn init_storage(config: &NpmConfig) -> Result<()> {
154 create_dir_all(&config.store_path).await?;
155 create_dir_all(&config.nm_cotton_path).await?;
156 Ok(())
157}