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::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}