use std::path::{Path, PathBuf};
use std::sync::Arc;
#[cfg(not(target_arch = "wasm32"))]
use async_std::fs;
use nassun::client::{Nassun, NassunOpts};
use nassun::package::Package;
use oro_common::CorgiManifest;
use url::Url;
#[cfg(not(target_arch = "wasm32"))]
use crate::error::IoContext;
use crate::error::NodeMaintainerError;
use crate::graph::{Graph, Node};
use crate::linkers::Linker;
#[cfg(not(target_arch = "wasm32"))]
use crate::linkers::LinkerOptions;
use crate::resolver::Resolver;
use crate::{IntoKdl, Lockfile};
pub const DEFAULT_CONCURRENCY: usize = 50;
pub const DEFAULT_SCRIPT_CONCURRENCY: usize = 6;
pub const META_FILE_NAME: &str = ".orogene-meta.kdl";
pub const STORE_DIR_NAME: &str = ".oro-store";
pub type ProgressAdded = Arc<dyn Fn() + Send + Sync>;
pub type ProgressHandler = Arc<dyn Fn(&Package) + Send + Sync>;
pub type PruneProgress = Arc<dyn Fn(&Path) + Send + Sync>;
pub type ScriptStartHandler = Arc<dyn Fn(&Package, &str) + Send + Sync>;
pub type ScriptLineHandler = Arc<dyn Fn(&str) + Send + Sync>;
#[derive(Clone)]
pub struct NodeMaintainerOptions {
nassun_opts: NassunOpts,
concurrency: usize,
locked: bool,
kdl_lock: Option<Lockfile>,
npm_lock: Option<Lockfile>,
#[allow(dead_code)]
hoisted: bool,
#[allow(dead_code)]
script_concurrency: usize,
#[allow(dead_code)]
cache: Option<PathBuf>,
#[allow(dead_code)]
prefer_copy: bool,
#[allow(dead_code)]
validate: bool,
#[allow(dead_code)]
root: Option<PathBuf>,
on_resolution_added: Option<ProgressAdded>,
on_resolve_progress: Option<ProgressHandler>,
#[allow(dead_code)]
on_prune_progress: Option<PruneProgress>,
#[allow(dead_code)]
on_extract_progress: Option<ProgressHandler>,
#[allow(dead_code)]
on_script_start: Option<ScriptStartHandler>,
#[allow(dead_code)]
on_script_line: Option<ScriptLineHandler>,
}
impl NodeMaintainerOptions {
pub fn new() -> Self {
Self::default()
}
#[cfg(not(target_arch = "wasm32"))]
pub fn cache(mut self, cache: impl AsRef<Path>) -> Self {
self.nassun_opts = self.nassun_opts.cache(PathBuf::from(cache.as_ref()));
self.cache = Some(PathBuf::from(cache.as_ref()));
self
}
pub fn concurrency(mut self, concurrency: usize) -> Self {
self.concurrency = concurrency;
self
}
pub fn locked(mut self, locked: bool) -> Self {
self.locked = locked;
self
}
pub fn script_concurrency(mut self, concurrency: usize) -> Self {
self.script_concurrency = concurrency;
self
}
pub fn kdl_lock(mut self, kdl_lock: impl IntoKdl) -> Result<Self, NodeMaintainerError> {
let lock = Lockfile::from_kdl(kdl_lock)?;
self.kdl_lock = Some(lock);
Ok(self)
}
pub fn npm_lock(mut self, npm_lock: impl AsRef<str>) -> Result<Self, NodeMaintainerError> {
let lock = Lockfile::from_npm(npm_lock)?;
self.npm_lock = Some(lock);
Ok(self)
}
pub fn registry(mut self, registry: Url) -> Self {
self.nassun_opts = self.nassun_opts.registry(registry);
self
}
pub fn scope_registry(mut self, scope: impl AsRef<str>, registry: Url) -> Self {
self.nassun_opts = self.nassun_opts.scope_registry(scope, registry);
self
}
#[cfg(not(target_arch = "wasm32"))]
pub fn root(mut self, path: impl AsRef<Path>) -> Self {
self.nassun_opts = self.nassun_opts.base_dir(path.as_ref());
self.root = Some(PathBuf::from(path.as_ref()));
self
}
pub fn default_tag(mut self, tag: impl AsRef<str>) -> Self {
self.nassun_opts = self.nassun_opts.default_tag(tag);
self
}
#[cfg(not(target_arch = "wasm32"))]
pub fn prefer_copy(mut self, prefer_copy: bool) -> Self {
self.prefer_copy = prefer_copy;
self
}
pub fn hoisted(mut self, hoisted: bool) -> Self {
self.hoisted = hoisted;
self
}
pub fn on_resolution_added<F>(mut self, f: F) -> Self
where
F: Fn() + Send + Sync + 'static,
{
self.on_resolution_added = Some(Arc::new(f));
self
}
pub fn on_resolve_progress<F>(mut self, f: F) -> Self
where
F: Fn(&Package) + Send + Sync + 'static,
{
self.on_resolve_progress = Some(Arc::new(f));
self
}
#[cfg(not(target_arch = "wasm32"))]
pub fn on_prune_progress<F>(mut self, f: F) -> Self
where
F: Fn(&Path) + Send + Sync + 'static,
{
self.on_prune_progress = Some(Arc::new(f));
self
}
#[cfg(not(target_arch = "wasm32"))]
pub fn on_extract_progress<F>(mut self, f: F) -> Self
where
F: Fn(&Package) + Send + Sync + 'static,
{
self.on_extract_progress = Some(Arc::new(f));
self
}
#[cfg(not(target_arch = "wasm32"))]
pub fn on_script_start<F>(mut self, f: F) -> Self
where
F: Fn(&Package, &str) + Send + Sync + 'static,
{
self.on_script_start = Some(Arc::new(f));
self
}
#[cfg(not(target_arch = "wasm32"))]
pub fn on_script_line<F>(mut self, f: F) -> Self
where
F: Fn(&str) + Send + Sync + 'static,
{
self.on_script_line = Some(Arc::new(f));
self
}
async fn get_lockfile(&self) -> Result<Option<Lockfile>, NodeMaintainerError> {
if let Some(kdl_lock) = &self.kdl_lock {
return Ok(Some(kdl_lock.clone()));
}
if let Some(npm_lock) = &self.npm_lock {
return Ok(Some(npm_lock.clone()));
}
#[cfg(not(target_arch = "wasm32"))]
if let Some(root) = &self.root {
let kdl_lock = root.join("package-lock.kdl");
if kdl_lock.exists() {
match async_std::fs::read_to_string(&kdl_lock)
.await
.io_context(|| format!("Failed to read {}", kdl_lock.display()))
.and_then(Lockfile::from_kdl)
{
Ok(lock) => return Ok(Some(lock)),
Err(e) => tracing::debug!("Failed to parse existing package-lock.kdl: {}", e),
}
}
let npm_lock = root.join("package-lock.json");
if npm_lock.exists() {
match async_std::fs::read_to_string(&npm_lock)
.await
.io_context(|| format!("Failed to read {}", npm_lock.display()))
.and_then(Lockfile::from_npm)
{
Ok(lock) => return Ok(Some(lock)),
Err(e) => tracing::debug!("Failed to parse existing package-lock.json: {}", e),
}
}
let npm_lock = root.join("npm-shrinkwrap.json");
if npm_lock.exists() {
match async_std::fs::read_to_string(&npm_lock)
.await
.io_context(|| format!("Failed to read {}", npm_lock.display()))
.and_then(Lockfile::from_npm)
{
Ok(lock) => return Ok(Some(lock)),
Err(e) => {
tracing::debug!("Failed to parse existing npm-shrinkwrap.json: {}", e)
}
}
}
}
Ok(None)
}
pub async fn resolve_manifest(
self,
root: CorgiManifest,
) -> Result<NodeMaintainer, NodeMaintainerError> {
let lockfile = self.get_lockfile().await?;
let nassun = self.nassun_opts.build();
let root_pkg = Nassun::dummy_from_manifest(root.clone());
let proj_root = self.root.unwrap_or_else(|| PathBuf::from("."));
let mut resolver = Resolver {
nassun,
graph: Default::default(),
concurrency: self.concurrency,
locked: self.locked,
root: &proj_root,
actual_tree: None,
on_resolution_added: self.on_resolution_added,
on_resolve_progress: self.on_resolve_progress,
};
let node = resolver
.graph
.inner
.add_node(Node::new(root_pkg, root, true)?);
resolver.graph[node].root = node;
let (graph, _actual_tree) = resolver.run_resolver(lockfile).await?;
#[cfg(not(target_arch = "wasm32"))]
let linker_opts = LinkerOptions {
actual_tree: _actual_tree,
concurrency: self.concurrency,
script_concurrency: self.script_concurrency,
cache: self.cache,
prefer_copy: self.prefer_copy,
root: proj_root,
on_prune_progress: self.on_prune_progress,
on_extract_progress: self.on_extract_progress,
on_script_start: self.on_script_start,
on_script_line: self.on_script_line,
};
let nm = NodeMaintainer {
graph,
#[cfg(target_arch = "wasm32")]
linker: Linker::null(),
#[cfg(not(target_arch = "wasm32"))]
linker: if self.hoisted {
Linker::hoisted(linker_opts)
} else {
Linker::isolated(linker_opts)
},
};
#[cfg(debug_assertions)]
nm.graph.validate()?;
Ok(nm)
}
pub async fn resolve_spec(
self,
root_spec: impl AsRef<str>,
) -> Result<NodeMaintainer, NodeMaintainerError> {
let lockfile = self.get_lockfile().await?;
let nassun = self.nassun_opts.build();
let root_pkg = nassun.resolve(root_spec).await?;
let proj_root = self.root.unwrap_or_else(|| PathBuf::from("."));
let mut resolver = Resolver {
nassun,
graph: Default::default(),
concurrency: self.concurrency,
locked: self.locked,
root: &proj_root,
actual_tree: None,
on_resolution_added: self.on_resolution_added,
on_resolve_progress: self.on_resolve_progress,
};
let corgi = root_pkg.corgi_metadata().await?.manifest;
let node = resolver
.graph
.inner
.add_node(Node::new(root_pkg, corgi, true)?);
resolver.graph[node].root = node;
let (graph, _actual_tree) = resolver.run_resolver(lockfile).await?;
#[cfg(not(target_arch = "wasm32"))]
let linker_opts = LinkerOptions {
actual_tree: _actual_tree,
concurrency: self.concurrency,
script_concurrency: self.script_concurrency,
cache: self.cache,
prefer_copy: self.prefer_copy,
root: proj_root,
on_prune_progress: self.on_prune_progress,
on_extract_progress: self.on_extract_progress,
on_script_start: self.on_script_start,
on_script_line: self.on_script_line,
};
let nm = NodeMaintainer {
graph,
#[cfg(target_arch = "wasm32")]
linker: Linker::null(),
#[cfg(not(target_arch = "wasm32"))]
linker: if self.hoisted {
Linker::hoisted(linker_opts)
} else {
Linker::isolated(linker_opts)
},
};
#[cfg(debug_assertions)]
nm.graph.validate()?;
Ok(nm)
}
}
impl Default for NodeMaintainerOptions {
fn default() -> Self {
NodeMaintainerOptions {
nassun_opts: Default::default(),
concurrency: DEFAULT_CONCURRENCY,
kdl_lock: None,
npm_lock: None,
locked: false,
script_concurrency: DEFAULT_SCRIPT_CONCURRENCY,
cache: None,
hoisted: false,
prefer_copy: false,
validate: false,
root: None,
on_resolution_added: None,
on_resolve_progress: None,
on_prune_progress: None,
on_extract_progress: None,
on_script_start: None,
on_script_line: None,
}
}
}
pub struct NodeMaintainer {
pub(crate) graph: Graph,
#[allow(dead_code)]
linker: Linker,
}
impl NodeMaintainer {
pub fn builder() -> NodeMaintainerOptions {
NodeMaintainerOptions::new()
}
#[cfg(not(target_arch = "wasm32"))]
pub async fn resolve_manifest(
root: CorgiManifest,
) -> Result<NodeMaintainer, NodeMaintainerError> {
Self::builder().resolve_manifest(root).await
}
#[cfg(not(target_arch = "wasm32"))]
pub async fn resolve_spec(
root_spec: impl AsRef<str>,
) -> Result<NodeMaintainer, NodeMaintainerError> {
Self::builder().resolve_spec(root_spec).await
}
#[cfg(not(target_arch = "wasm32"))]
pub async fn write_lockfile(&self, path: impl AsRef<Path>) -> Result<(), NodeMaintainerError> {
let path = path.as_ref();
fs::write(path, self.graph.to_kdl()?.to_string())
.await
.io_context(|| format!("Failed to write lockfile to {}", path.display()))?;
Ok(())
}
pub fn to_lockfile(&self) -> Result<crate::Lockfile, NodeMaintainerError> {
self.graph.to_lockfile()
}
pub fn to_kdl(&self) -> Result<kdl::KdlDocument, NodeMaintainerError> {
self.graph.to_kdl()
}
pub fn package_at_path(&self, path: &Path) -> Option<Package> {
self.graph.package_at_path(path)
}
pub fn package_count(&self) -> usize {
self.graph.inner.node_count()
}
#[cfg(not(target_arch = "wasm32"))]
pub async fn prune(&self) -> Result<usize, NodeMaintainerError> {
self.linker.prune(&self.graph).await
}
#[cfg(not(target_arch = "wasm32"))]
pub async fn extract(&self) -> Result<usize, NodeMaintainerError> {
self.linker.extract(&self.graph).await
}
#[cfg(not(target_arch = "wasm32"))]
pub async fn rebuild(&self, ignore_scripts: bool) -> Result<(), NodeMaintainerError> {
self.linker.rebuild(&self.graph, ignore_scripts).await
}
}