mirror of
https://github.com/pesde-pkg/pesde.git
synced 2024-12-12 11:00:36 +00:00
feat: implement utility commands
This commit is contained in:
parent
97b6a69688
commit
ea887e56ef
9 changed files with 264 additions and 20 deletions
150
src/cli/commands/add.rs
Normal file
150
src/cli/commands/add.rs
Normal 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(())
|
||||
}
|
||||
}
|
|
@ -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")?
|
||||
{
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
68
src/cli/commands/outdated.rs
Normal file
68
src/cli/commands/outdated.rs
Normal 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(())
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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<()> {
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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::{
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in a new issue