From ea887e56ef4ef595e2d717a65e0d651a8473ab69 Mon Sep 17 00:00:00 2001 From: daimond113 <72147841+daimond113@users.noreply.github.com> Date: Sun, 28 Jul 2024 21:08:35 +0200 Subject: [PATCH] feat: implement utility commands --- src/cli/commands/add.rs | 150 +++++++++++++++++++++++++++++++ src/cli/commands/install.rs | 21 +++-- src/cli/commands/mod.rs | 17 ++++ src/cli/commands/outdated.rs | 68 ++++++++++++++ src/cli/commands/self_install.rs | 7 +- src/cli/commands/self_upgrade.rs | 6 +- src/cli/mod.rs | 10 ++- src/linking/mod.rs | 1 - src/source/pesde/mod.rs | 4 + 9 files changed, 264 insertions(+), 20 deletions(-) create mode 100644 src/cli/commands/add.rs create mode 100644 src/cli/commands/outdated.rs diff --git a/src/cli/commands/add.rs b/src/cli/commands/add.rs new file mode 100644 index 0000000..806afcb --- /dev/null +++ b/src/cli/commands/add.rs @@ -0,0 +1,150 @@ +use std::str::FromStr; + +use anyhow::Context; +use clap::Args; +use semver::VersionReq; + +use pesde::{ + manifest::target::TargetKind, + names::PackageNames, + source::{ + pesde::{specifier::PesdeDependencySpecifier, PesdePackageSource}, + specifiers::DependencySpecifiers, + traits::PackageSource, + PackageSources, + }, + Project, DEFAULT_INDEX_NAME, +}; + +use crate::cli::{config::read_config, VersionedPackageName}; + +#[derive(Debug, Args)] +pub struct AddCommand { + /// The package name to add + #[arg(index = 1)] + name: VersionedPackageName, + + /// The index in which to search for the package + #[arg(short, long)] + index: Option, + + /// The target environment of the package + #[arg(short, long)] + target: Option, + + /// The alias to use for the package + #[arg(short, long)] + alias: Option, + + /// Whether to add the package as a peer dependency + #[arg(short, long)] + peer: bool, + + /// Whether to add the package as a dev dependency + #[arg(short, long, conflicts_with = "peer")] + dev: bool, +} + +impl AddCommand { + pub fn run(self, project: Project) -> anyhow::Result<()> { + let manifest = project + .deser_manifest() + .context("failed to read manifest")?; + + let source = match &self.name.0 { + PackageNames::Pesde(_) => { + let index = manifest + .indices + .get(self.index.as_deref().unwrap_or(DEFAULT_INDEX_NAME)) + .cloned(); + + if let Some(index) = self.index.as_ref().filter(|_| index.is_none()) { + log::error!("index {index} not found"); + return Ok(()); + } + + let index = index.unwrap_or(read_config()?.default_index); + + PackageSources::Pesde(PesdePackageSource::new(index)) + } + }; + source + .refresh(&project) + .context("failed to refresh package source")?; + + let specifier = match &self.name.0 { + PackageNames::Pesde(name) => DependencySpecifiers::Pesde(PesdeDependencySpecifier { + name: name.clone(), + version: self.name.1.unwrap_or(VersionReq::STAR), + index: self.index, + target: self.target, + }), + }; + + let Some(version_id) = source + .resolve(&specifier, &project, manifest.target.kind()) + .context("failed to resolve package")? + .1 + .pop_last() + .map(|(v_id, _)| v_id) + else { + log::error!( + "no versions found for package: {} (current target: {}, try a different one)", + self.name.0, + manifest.target.kind() + ); + + return Ok(()); + }; + + let project_target = manifest.target.kind(); + let mut manifest = toml_edit::DocumentMut::from_str( + &project.read_manifest().context("failed to read manifest")?, + ) + .context("failed to parse manifest")?; + let dependency_key = if self.peer { + "peer_dependencies" + } else if self.dev { + "dev_dependencies" + } else { + "dependencies" + }; + + let alias = self + .alias + .as_deref() + .unwrap_or_else(|| self.name.0.as_str().1); + + match specifier { + DependencySpecifiers::Pesde(spec) => { + manifest[dependency_key][alias]["name"] = + toml_edit::value(spec.name.clone().to_string()); + manifest[dependency_key][alias]["version"] = + toml_edit::value(format!("^{}", version_id.version())); + + if *version_id.target() != project_target { + manifest[dependency_key][alias]["target"] = + toml_edit::value(version_id.target().to_string()); + } + + if let Some(index) = spec.index.filter(|i| i != DEFAULT_INDEX_NAME) { + manifest[dependency_key][alias]["index"] = toml_edit::value(index); + } + + println!( + "added {}@{} {} to {}", + spec.name, + version_id.version(), + version_id.target(), + dependency_key + ); + } + } + + project + .write_manifest(manifest.to_string()) + .context("failed to write manifest")?; + + Ok(()) + } +} diff --git a/src/cli/commands/install.rs b/src/cli/commands/install.rs index 8ab35b7..f07d1f2 100644 --- a/src/cli/commands/install.rs +++ b/src/cli/commands/install.rs @@ -1,19 +1,26 @@ -use crate::cli::{bin_dir, files::make_executable, home_dir, IsUpToDate}; -use anyhow::Context; -use clap::Args; -use indicatif::MultiProgress; -use pesde::{lockfile::Lockfile, manifest::target::TargetKind, Project}; use std::{ collections::{BTreeSet, HashSet}, sync::Arc, time::Duration, }; +use anyhow::Context; +use clap::Args; +use indicatif::MultiProgress; + +use pesde::{lockfile::Lockfile, manifest::target::TargetKind, Project}; + +use crate::cli::{bin_dir, files::make_executable, IsUpToDate}; + #[derive(Debug, Args)] pub struct InstallCommand { /// The amount of threads to use for downloading #[arg(short, long, default_value_t = 6, value_parser = clap::value_parser!(u64).range(1..=128))] threads: u64, + + /// Whether to ignore the lockfile, refreshing it + #[arg(short, long)] + pub unlocked: bool, } fn bin_link_file(alias: &str) -> String { @@ -67,7 +74,9 @@ impl InstallCommand { .deser_manifest() .context("failed to read manifest")?; - let lockfile = if project + let lockfile = if self.unlocked { + None + } else if project .is_up_to_date(false) .context("failed to check if project is up to date")? { diff --git a/src/cli/commands/mod.rs b/src/cli/commands/mod.rs index b10c3ad..b3a095f 100644 --- a/src/cli/commands/mod.rs +++ b/src/cli/commands/mod.rs @@ -1,10 +1,12 @@ use indicatif::MultiProgress; use pesde::Project; +mod add; mod auth; mod config; mod init; mod install; +mod outdated; #[cfg(feature = "patches")] mod patch; #[cfg(feature = "patches")] @@ -49,6 +51,15 @@ pub enum Subcommand { /// Installs the latest version of pesde SelfUpgrade(self_upgrade::SelfUpgradeCommand), + + /// Adds a dependency to the project + Add(add::AddCommand), + + /// Updates the project's lockfile. note: this command is just an alias for `install --unlocked` + Update(install::InstallCommand), + + /// Checks for outdated dependencies + Outdated(outdated::OutdatedCommand), } impl Subcommand { @@ -71,6 +82,12 @@ impl Subcommand { #[cfg(feature = "patches")] Subcommand::PatchCommit(patch_commit) => patch_commit.run(project), Subcommand::SelfUpgrade(self_upgrade) => self_upgrade.run(reqwest), + Subcommand::Add(add) => add.run(project), + Subcommand::Update(mut update) => { + update.unlocked = true; + update.run(project, multi, reqwest) + } + Subcommand::Outdated(outdated) => outdated.run(project), } } } diff --git a/src/cli/commands/outdated.rs b/src/cli/commands/outdated.rs new file mode 100644 index 0000000..9979cdc --- /dev/null +++ b/src/cli/commands/outdated.rs @@ -0,0 +1,68 @@ +use std::collections::HashSet; + +use anyhow::Context; +use clap::Args; +use semver::VersionReq; + +use pesde::{ + source::{ + specifiers::DependencySpecifiers, + traits::{PackageRef, PackageSource}, + }, + Project, +}; + +#[derive(Debug, Args)] +pub struct OutdatedCommand { + /// Whether to check within version requirements + #[arg(short, long)] + strict: bool, +} + +impl OutdatedCommand { + pub fn run(self, project: Project) -> anyhow::Result<()> { + let graph = project.deser_lockfile()?.graph; + + let manifest = project + .deser_manifest() + .context("failed to read manifest")?; + + let mut refreshed_sources = HashSet::new(); + + for (name, versions) in graph { + for (current_version_id, node) in versions { + let Some((alias, mut specifier)) = node.node.direct else { + continue; + }; + + let source = node.node.pkg_ref.source(); + + if refreshed_sources.insert(source.clone()) { + source.refresh(&project)?; + } + + if !self.strict { + match specifier { + DependencySpecifiers::Pesde(ref mut spec) => { + spec.version = VersionReq::STAR; + } + }; + } + + let version_id = source + .resolve(&specifier, &project, manifest.target.kind()) + .context("failed to resolve package versions")? + .1 + .pop_last() + .map(|(v_id, _)| v_id) + .context(format!("no versions of {specifier} found"))?; + + if version_id != current_version_id { + println!("{name} ({alias}) {current_version_id} -> {version_id}"); + } + } + } + + Ok(()) + } +} diff --git a/src/cli/commands/self_install.rs b/src/cli/commands/self_install.rs index fead2cc..98b373a 100644 --- a/src/cli/commands/self_install.rs +++ b/src/cli/commands/self_install.rs @@ -1,15 +1,12 @@ -use crate::cli::{ - bin_dir, files::make_executable, home_dir, scripts::update_scripts_folder, - version::update_bin_exe, HOME_DIR, -}; +use crate::cli::{bin_dir, scripts::update_scripts_folder, version::update_bin_exe, HOME_DIR}; use anyhow::Context; use clap::Args; use colored::Colorize; use pesde::Project; -use std::fs::create_dir_all; #[derive(Debug, Args)] pub struct SelfInstallCommand { + /// Skip adding the bin directory to the PATH #[cfg(windows)] #[arg(short, long)] skip_add_to_path: bool, diff --git a/src/cli/commands/self_upgrade.rs b/src/cli/commands/self_upgrade.rs index 155bc44..922a698 100644 --- a/src/cli/commands/self_upgrade.rs +++ b/src/cli/commands/self_upgrade.rs @@ -5,11 +5,7 @@ use crate::cli::{ use clap::Args; #[derive(Debug, Args)] -pub struct SelfUpgradeCommand { - #[cfg(windows)] - #[arg(short, long)] - skip_add_to_path: bool, -} +pub struct SelfUpgradeCommand {} impl SelfUpgradeCommand { pub fn run(self, reqwest: reqwest::blocking::Client) -> anyhow::Result<()> { diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 8d6e6cb..4d24956 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -85,15 +85,19 @@ impl IsUpToDate for Project { } #[derive(Debug, Clone)] -struct VersionedPackageName(PackageNames, Option); +struct VersionedPackageName(PackageNames, Option); -impl FromStr for VersionedPackageName { +impl, E: Into> FromStr for VersionedPackageName { type Err = anyhow::Error; fn from_str(s: &str) -> Result { let mut parts = s.splitn(2, '@'); let name = parts.next().unwrap(); - let version = parts.next().map(VersionId::from_str).transpose()?; + let version = parts + .next() + .map(FromStr::from_str) + .transpose() + .map_err(Into::into)?; Ok(VersionedPackageName(name.parse()?, version)) } diff --git a/src/linking/mod.rs b/src/linking/mod.rs index 833db9f..45e35b2 100644 --- a/src/linking/mod.rs +++ b/src/linking/mod.rs @@ -5,7 +5,6 @@ use crate::{ names::PackageNames, scripts::{execute_script, ScriptName}, source::{fs::store_in_cas, traits::PackageRef, version_id::VersionId}, - util::hash, Project, PACKAGES_CONTAINER_NAME, }; use std::{ diff --git a/src/source/pesde/mod.rs b/src/source/pesde/mod.rs index 28d4328..af028b2 100644 --- a/src/source/pesde/mod.rs +++ b/src/source/pesde/mod.rs @@ -43,6 +43,10 @@ impl PesdePackageSource { project.data_dir.join("indices").join(hash(self.as_bytes())) } + pub fn repo_url(&self) -> &gix::Url { + &self.repo_url + } + pub(crate) fn tree<'a>( &'a self, repo: &'a gix::Repository,