prest_npm/
lib.rs

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}