diff --git a/src/cli/commands/add.rs b/src/cli/commands/add.rs index eed8b33..658b2f8 100644 --- a/src/cli/commands/add.rs +++ b/src/cli/commands/add.rs @@ -4,7 +4,7 @@ use anyhow::Context; use clap::Args; use semver::VersionReq; -use crate::cli::{config::read_config, NamedVersionable, VersionedPackageName}; +use crate::cli::{config::read_config, AnyPackageIdentifier, VersionedPackageName}; use pesde::{ manifest::target::TargetKind, names::PackageNames, @@ -13,6 +13,7 @@ use pesde::{ pesde::{specifier::PesdeDependencySpecifier, PesdePackageSource}, specifiers::DependencySpecifiers, traits::PackageSource, + workspace::WorkspacePackageSource, PackageSources, }, Project, DEFAULT_INDEX_NAME, @@ -22,7 +23,7 @@ use pesde::{ pub struct AddCommand { /// The package name to add #[arg(index = 1)] - name: NamedVersionable, + name: AnyPackageIdentifier, /// The index in which to search for the package #[arg(short, long)] @@ -52,7 +53,7 @@ impl AddCommand { .context("failed to read manifest")?; let (source, specifier) = match &self.name { - NamedVersionable::PackageName(versioned) => match &versioned { + AnyPackageIdentifier::PackageName(versioned) => match &versioned { VersionedPackageName(PackageNames::Pesde(name), version) => { let index = manifest .indices @@ -103,7 +104,7 @@ impl AddCommand { (source, specifier) } }, - NamedVersionable::Url((url, rev)) => ( + AnyPackageIdentifier::Url((url, rev)) => ( PackageSources::Git(GitPackageSource::new(url.clone())), DependencySpecifiers::Git(GitDependencySpecifier { repo: url.clone(), @@ -111,6 +112,15 @@ impl AddCommand { path: None, }), ), + AnyPackageIdentifier::Workspace(VersionedPackageName(name, version)) => ( + PackageSources::Workspace(WorkspacePackageSource), + DependencySpecifiers::Workspace( + pesde::source::workspace::specifier::WorkspaceDependencySpecifier { + name: name.clone(), + version_type: version.unwrap_or_default(), + }, + ), + ), }; source .refresh(&project) @@ -141,15 +151,16 @@ impl AddCommand { "dependencies" }; - let alias = self.alias.unwrap_or_else(|| match self.name { - NamedVersionable::PackageName(versioned) => versioned.0.as_str().1.to_string(), - NamedVersionable::Url((url, _)) => url + let alias = self.alias.unwrap_or_else(|| match self.name.clone() { + AnyPackageIdentifier::PackageName(versioned) => versioned.0.as_str().1.to_string(), + AnyPackageIdentifier::Url((url, _)) => url .path .to_string() .split('/') .last() .map(|s| s.to_string()) .unwrap_or(url.path.to_string()), + AnyPackageIdentifier::Workspace(versioned) => versioned.0.as_str().1.to_string(), }); let field = &mut manifest[dependency_key] @@ -198,7 +209,19 @@ impl AddCommand { println!("added git {}#{} to {}", spec.repo, spec.rev, dependency_key); } - DependencySpecifiers::Workspace(_) => todo!(), + DependencySpecifiers::Workspace(spec) => { + field["workspace"] = toml_edit::value(spec.name.clone().to_string()); + if let AnyPackageIdentifier::Workspace(versioned) = self.name { + if let Some(version) = versioned.1 { + field["version"] = toml_edit::value(version.to_string()); + } + } + + println!( + "added workspace {}@{} to {}", + spec.name, spec.version_type, dependency_key + ); + } } project diff --git a/src/cli/commands/install.rs b/src/cli/commands/install.rs index 9fbe61c..474ce90 100644 --- a/src/cli/commands/install.rs +++ b/src/cli/commands/install.rs @@ -1,6 +1,7 @@ -use crate::cli::{bin_dir, files::make_executable, IsUpToDate}; +use crate::cli::{bin_dir, files::make_executable}; use anyhow::Context; use clap::Args; +use colored::{ColoredString, Colorize}; use indicatif::MultiProgress; use pesde::{lockfile::Lockfile, manifest::target::TargetKind, Project, MANIFEST_FILE_NAME}; use relative_path::RelativePathBuf; @@ -70,6 +71,15 @@ end ) } +#[cfg(feature = "patches")] +const JOBS: u8 = 6; +#[cfg(not(feature = "patches"))] +const JOBS: u8 = 5; + +fn job(n: u8) -> ColoredString { + format!("[{n}/{JOBS}]").dimmed().bold() +} + impl InstallCommand { pub fn run( self, @@ -85,12 +95,19 @@ impl InstallCommand { let lockfile = if self.unlocked { None - } else if project - .is_up_to_date(false) - .context("failed to check if project is up to date")? - { + } else { match project.deser_lockfile() { - Ok(lockfile) => Some(lockfile), + Ok(lockfile) => { + if lockfile.overrides != manifest.overrides { + log::debug!("overrides are different"); + None + } else if lockfile.target != manifest.target.kind() { + log::debug!("target kind is different"); + None + } else { + Some(lockfile) + } + } Err(pesde::errors::LockfileReadError::Io(e)) if e.kind() == std::io::ErrorKind::NotFound => { @@ -98,10 +115,17 @@ impl InstallCommand { } Err(e) => return Err(e.into()), } - } else { - None }; + println!( + "\n{}\n", + format!("[now installing {}]", manifest.name) + .bold() + .on_bright_black() + ); + + println!("{} ❌ removing current package folders", job(1)); + { let mut deleted_folders = HashSet::new(); @@ -137,6 +161,8 @@ impl InstallCommand { .collect() }); + println!("{} πŸ“¦ building dependency graph", job(2)); + let graph = project .dependency_graph(old_graph.as_ref(), &mut refreshed_sources) .context("failed to build dependency graph")?; @@ -148,7 +174,7 @@ impl InstallCommand { "{msg} {bar:40.208/166} {pos}/{len} {percent}% {elapsed_precise}", )?, ) - .with_message(format!("downloading dependencies of {}", manifest.name)), + .with_message(format!("{} πŸ“₯ downloading dependencies", job(3))), ); bar.enable_steady_tick(Duration::from_millis(100)); @@ -170,25 +196,19 @@ impl InstallCommand { } } - bar.finish_with_message(format!( - "finished downloading dependencies of {}", - manifest.name - )); + bar.finish_with_message(format!("{} πŸ“₯ downloaded dependencies", job(3),)); let downloaded_graph = Arc::into_inner(downloaded_graph) .unwrap() .into_inner() .unwrap(); + println!("{} πŸ—ΊοΈ linking dependencies", job(4)); + project .link_dependencies(&downloaded_graph) .context("failed to link dependencies")?; - #[cfg(feature = "patches")] - project - .apply_patches(&downloaded_graph) - .context("failed to apply patches")?; - let bin_folder = bin_dir()?; for versions in downloaded_graph.values() { @@ -224,6 +244,17 @@ impl InstallCommand { } } + #[cfg(feature = "patches")] + { + println!("{} 🩹 applying patches", job(5)); + + project + .apply_patches(&downloaded_graph) + .context("failed to apply patches")?; + } + + println!("{} 🧹 finishing up", job(JOBS)); + project .write_lockfile(Lockfile { name: manifest.name, diff --git a/src/cli/commands/patch.rs b/src/cli/commands/patch.rs index 6bde635..a43b797 100644 --- a/src/cli/commands/patch.rs +++ b/src/cli/commands/patch.rs @@ -1,10 +1,13 @@ -use crate::cli::{IsUpToDate, VersionedPackageName}; +use crate::cli::{up_to_date_lockfile, VersionedPackageName}; use anyhow::Context; use clap::Args; use colored::Colorize; use pesde::{ patches::setup_patches_repo, - source::traits::{PackageRef, PackageSource}, + source::{ + refs::PackageRefs, + traits::{PackageRef, PackageSource}, + }, Project, MANIFEST_FILE_NAME, }; @@ -17,8 +20,8 @@ pub struct PatchCommand { impl PatchCommand { pub fn run(self, project: Project, reqwest: reqwest::blocking::Client) -> anyhow::Result<()> { - let graph = if project.is_up_to_date(true)? { - project.deser_lockfile()?.graph + let graph = if let Some(lockfile) = up_to_date_lockfile(&project)? { + lockfile.graph } else { anyhow::bail!("outdated lockfile, please run the install command first") }; @@ -29,6 +32,11 @@ impl PatchCommand { .get(&name) .and_then(|versions| versions.get(&version_id)) .context("package not found in graph")?; + + if matches!(node.node.pkg_ref, PackageRefs::Workspace(_)) { + anyhow::bail!("cannot patch a workspace package") + } + let source = node.node.pkg_ref.source(); let directory = project diff --git a/src/cli/commands/patch_commit.rs b/src/cli/commands/patch_commit.rs index 83e1fc0..d5ef6b7 100644 --- a/src/cli/commands/patch_commit.rs +++ b/src/cli/commands/patch_commit.rs @@ -1,4 +1,4 @@ -use crate::cli::IsUpToDate; +use crate::cli::up_to_date_lockfile; use anyhow::Context; use clap::Args; use pesde::{names::PackageNames, patches::create_patch, source::version_id::VersionId, Project}; @@ -13,8 +13,8 @@ pub struct PatchCommitCommand { impl PatchCommitCommand { pub fn run(self, project: Project) -> anyhow::Result<()> { - let graph = if project.is_up_to_date(true)? { - project.deser_lockfile()?.graph + let graph = if let Some(lockfile) = up_to_date_lockfile(&project)? { + lockfile.graph } else { anyhow::bail!("outdated lockfile, please run the install command first") }; diff --git a/src/cli/commands/run.rs b/src/cli/commands/run.rs index e8688fd..d096ce6 100644 --- a/src/cli/commands/run.rs +++ b/src/cli/commands/run.rs @@ -4,7 +4,7 @@ use anyhow::Context; use clap::Args; use relative_path::RelativePathBuf; -use crate::cli::IsUpToDate; +use crate::cli::up_to_date_lockfile; use pesde::{ names::{PackageName, PackageNames}, source::traits::PackageRef, @@ -49,8 +49,8 @@ impl RunCommand { }; if let Ok(pkg_name) = package_or_script.parse::() { - let graph = if project.is_up_to_date(true)? { - project.deser_lockfile()?.graph + let graph = if let Some(lockfile) = up_to_date_lockfile(&project)? { + lockfile.graph } else { anyhow::bail!("outdated lockfile, please run the install command first") }; diff --git a/src/cli/mod.rs b/src/cli/mod.rs index e2e9882..518750b 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -1,7 +1,11 @@ +use crate::cli::auth::get_token; use anyhow::Context; use gix::bstr::BStr; use pesde::{ - lockfile::DownloadedGraph, names::PackageNames, source::version_id::VersionId, Project, + lockfile::{DownloadedGraph, Lockfile}, + names::{PackageName, PackageNames}, + source::{version_id::VersionId, workspace::specifier::VersionType}, + Project, }; use serde::{ser::SerializeMap, Deserialize, Deserializer, Serializer}; use std::{ @@ -10,8 +14,6 @@ use std::{ str::FromStr, }; -use crate::cli::auth::get_token; - pub mod auth; pub mod commands; pub mod config; @@ -33,62 +35,58 @@ pub fn bin_dir() -> anyhow::Result { Ok(bin_dir) } -pub trait IsUpToDate { - fn is_up_to_date(&self, strict: bool) -> anyhow::Result; -} - -impl IsUpToDate for Project { - fn is_up_to_date(&self, strict: bool) -> anyhow::Result { - let manifest = self.deser_manifest()?; - let lockfile = match self.deser_lockfile() { - Ok(lockfile) => lockfile, - Err(pesde::errors::LockfileReadError::Io(e)) - if e.kind() == std::io::ErrorKind::NotFound => - { - return Ok(false); - } - Err(e) => return Err(e.into()), - }; - - if manifest.overrides != lockfile.overrides { - log::debug!("overrides are different"); - return Ok(false); +pub fn up_to_date_lockfile(project: &Project) -> anyhow::Result> { + let manifest = project.deser_manifest()?; + let lockfile = match project.deser_lockfile() { + Ok(lockfile) => lockfile, + Err(pesde::errors::LockfileReadError::Io(e)) + if e.kind() == std::io::ErrorKind::NotFound => + { + return Ok(None); } + Err(e) => return Err(e.into()), + }; - if manifest.target.kind() != lockfile.target { - log::debug!("target kind is different"); - return Ok(false); - } - - if !strict { - return Ok(true); - } - - if manifest.name != lockfile.name || manifest.version != lockfile.version { - log::debug!("name or version is different"); - return Ok(false); - } - - let specs = lockfile - .graph - .into_iter() - .flat_map(|(_, versions)| versions) - .filter_map(|(_, node)| match node.node.direct { - Some((_, spec)) => Some((spec, node.node.ty)), - None => None, - }) - .collect::>(); - - let same_dependencies = manifest - .all_dependencies() - .context("failed to get all dependencies")? - .iter() - .all(|(_, (spec, ty))| specs.contains(&(spec.clone(), *ty))); - - log::debug!("dependencies are the same: {same_dependencies}"); - - Ok(same_dependencies) + if manifest.overrides != lockfile.overrides { + log::debug!("overrides are different"); + return Ok(None); } + + if manifest.target.kind() != lockfile.target { + log::debug!("target kind is different"); + return Ok(None); + } + + if manifest.name != lockfile.name || manifest.version != lockfile.version { + log::debug!("name or version is different"); + return Ok(None); + } + + let specs = lockfile + .graph + .iter() + .flat_map(|(_, versions)| versions) + .filter_map(|(_, node)| { + node.node + .direct + .as_ref() + .map(|(_, spec)| (spec, node.node.ty)) + }) + .collect::>(); + + let same_dependencies = manifest + .all_dependencies() + .context("failed to get all dependencies")? + .iter() + .all(|(_, (spec, ty))| specs.contains(&(spec, *ty))); + + log::debug!("dependencies are the same: {same_dependencies}"); + + Ok(if same_dependencies { + Some(lockfile) + } else { + None + }) } #[derive(Debug, Clone)] @@ -143,28 +141,37 @@ impl VersionedPackageName { } #[derive(Debug, Clone)] -enum NamedVersionable { +enum AnyPackageIdentifier { PackageName(VersionedPackageName), Url((gix::Url, String)), + Workspace(VersionedPackageName), } impl, E: Into, N: FromStr, F: Into> - FromStr for NamedVersionable + FromStr for AnyPackageIdentifier { type Err = anyhow::Error; fn from_str(s: &str) -> Result { - if s.contains("gh#") { - let s = s.replacen("gh#", "https://github.com/", 1); + if let Some(s) = s.strip_prefix("gh#") { + let s = format!("https://github.com/{s}"); let (repo, rev) = s.split_once('#').unwrap(); - Ok(NamedVersionable::Url((repo.try_into()?, rev.to_string()))) + Ok(AnyPackageIdentifier::Url(( + repo.try_into()?, + rev.to_string(), + ))) + } else if let Some(rest) = s.strip_prefix("workspace:") { + Ok(AnyPackageIdentifier::Workspace(rest.parse()?)) } else if s.contains(':') { let (url, rev) = s.split_once('#').unwrap(); - Ok(NamedVersionable::Url((url.try_into()?, rev.to_string()))) + Ok(AnyPackageIdentifier::Url(( + url.try_into()?, + rev.to_string(), + ))) } else { - Ok(NamedVersionable::PackageName(s.parse()?)) + Ok(AnyPackageIdentifier::PackageName(s.parse()?)) } } }