feat: implement utility commands

This commit is contained in:
daimond113 2024-07-28 21:08:35 +02:00
parent 97b6a69688
commit ea887e56ef
No known key found for this signature in database
GPG key ID: 3A8ECE51328B513C
9 changed files with 264 additions and 20 deletions

150
src/cli/commands/add.rs Normal file
View file

@ -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<VersionReq>,
/// The index in which to search for the package
#[arg(short, long)]
index: Option<String>,
/// The target environment of the package
#[arg(short, long)]
target: Option<TargetKind>,
/// The alias to use for the package
#[arg(short, long)]
alias: Option<String>,
/// 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(())
}
}

View file

@ -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")?
{

View file

@ -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),
}
}
}

View file

@ -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(())
}
}

View file

@ -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,

View file

@ -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<()> {

View file

@ -85,15 +85,19 @@ impl IsUpToDate for Project {
}
#[derive(Debug, Clone)]
struct VersionedPackageName(PackageNames, Option<VersionId>);
struct VersionedPackageName<T: FromStr = VersionId>(PackageNames, Option<T>);
impl FromStr for VersionedPackageName {
impl<T: FromStr<Err = E>, E: Into<anyhow::Error>> FromStr for VersionedPackageName<T> {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
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))
}

View file

@ -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::{

View file

@ -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,