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::{
|
use std::{
|
||||||
collections::{BTreeSet, HashSet},
|
collections::{BTreeSet, HashSet},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
time::Duration,
|
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)]
|
#[derive(Debug, Args)]
|
||||||
pub struct InstallCommand {
|
pub struct InstallCommand {
|
||||||
/// The amount of threads to use for downloading
|
/// The amount of threads to use for downloading
|
||||||
#[arg(short, long, default_value_t = 6, value_parser = clap::value_parser!(u64).range(1..=128))]
|
#[arg(short, long, default_value_t = 6, value_parser = clap::value_parser!(u64).range(1..=128))]
|
||||||
threads: u64,
|
threads: u64,
|
||||||
|
|
||||||
|
/// Whether to ignore the lockfile, refreshing it
|
||||||
|
#[arg(short, long)]
|
||||||
|
pub unlocked: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bin_link_file(alias: &str) -> String {
|
fn bin_link_file(alias: &str) -> String {
|
||||||
|
@ -67,7 +74,9 @@ impl InstallCommand {
|
||||||
.deser_manifest()
|
.deser_manifest()
|
||||||
.context("failed to read manifest")?;
|
.context("failed to read manifest")?;
|
||||||
|
|
||||||
let lockfile = if project
|
let lockfile = if self.unlocked {
|
||||||
|
None
|
||||||
|
} else if project
|
||||||
.is_up_to_date(false)
|
.is_up_to_date(false)
|
||||||
.context("failed to check if project is up to date")?
|
.context("failed to check if project is up to date")?
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
use indicatif::MultiProgress;
|
use indicatif::MultiProgress;
|
||||||
use pesde::Project;
|
use pesde::Project;
|
||||||
|
|
||||||
|
mod add;
|
||||||
mod auth;
|
mod auth;
|
||||||
mod config;
|
mod config;
|
||||||
mod init;
|
mod init;
|
||||||
mod install;
|
mod install;
|
||||||
|
mod outdated;
|
||||||
#[cfg(feature = "patches")]
|
#[cfg(feature = "patches")]
|
||||||
mod patch;
|
mod patch;
|
||||||
#[cfg(feature = "patches")]
|
#[cfg(feature = "patches")]
|
||||||
|
@ -49,6 +51,15 @@ pub enum Subcommand {
|
||||||
|
|
||||||
/// Installs the latest version of pesde
|
/// Installs the latest version of pesde
|
||||||
SelfUpgrade(self_upgrade::SelfUpgradeCommand),
|
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 {
|
impl Subcommand {
|
||||||
|
@ -71,6 +82,12 @@ impl Subcommand {
|
||||||
#[cfg(feature = "patches")]
|
#[cfg(feature = "patches")]
|
||||||
Subcommand::PatchCommit(patch_commit) => patch_commit.run(project),
|
Subcommand::PatchCommit(patch_commit) => patch_commit.run(project),
|
||||||
Subcommand::SelfUpgrade(self_upgrade) => self_upgrade.run(reqwest),
|
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::{
|
use crate::cli::{bin_dir, scripts::update_scripts_folder, version::update_bin_exe, HOME_DIR};
|
||||||
bin_dir, files::make_executable, home_dir, scripts::update_scripts_folder,
|
|
||||||
version::update_bin_exe, HOME_DIR,
|
|
||||||
};
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use clap::Args;
|
use clap::Args;
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use pesde::Project;
|
use pesde::Project;
|
||||||
use std::fs::create_dir_all;
|
|
||||||
|
|
||||||
#[derive(Debug, Args)]
|
#[derive(Debug, Args)]
|
||||||
pub struct SelfInstallCommand {
|
pub struct SelfInstallCommand {
|
||||||
|
/// Skip adding the bin directory to the PATH
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
skip_add_to_path: bool,
|
skip_add_to_path: bool,
|
||||||
|
|
|
@ -5,11 +5,7 @@ use crate::cli::{
|
||||||
use clap::Args;
|
use clap::Args;
|
||||||
|
|
||||||
#[derive(Debug, Args)]
|
#[derive(Debug, Args)]
|
||||||
pub struct SelfUpgradeCommand {
|
pub struct SelfUpgradeCommand {}
|
||||||
#[cfg(windows)]
|
|
||||||
#[arg(short, long)]
|
|
||||||
skip_add_to_path: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SelfUpgradeCommand {
|
impl SelfUpgradeCommand {
|
||||||
pub fn run(self, reqwest: reqwest::blocking::Client) -> anyhow::Result<()> {
|
pub fn run(self, reqwest: reqwest::blocking::Client) -> anyhow::Result<()> {
|
||||||
|
|
|
@ -85,15 +85,19 @@ impl IsUpToDate for Project {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[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;
|
type Err = anyhow::Error;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
let mut parts = s.splitn(2, '@');
|
let mut parts = s.splitn(2, '@');
|
||||||
let name = parts.next().unwrap();
|
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))
|
Ok(VersionedPackageName(name.parse()?, version))
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,6 @@ use crate::{
|
||||||
names::PackageNames,
|
names::PackageNames,
|
||||||
scripts::{execute_script, ScriptName},
|
scripts::{execute_script, ScriptName},
|
||||||
source::{fs::store_in_cas, traits::PackageRef, version_id::VersionId},
|
source::{fs::store_in_cas, traits::PackageRef, version_id::VersionId},
|
||||||
util::hash,
|
|
||||||
Project, PACKAGES_CONTAINER_NAME,
|
Project, PACKAGES_CONTAINER_NAME,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
|
|
|
@ -43,6 +43,10 @@ impl PesdePackageSource {
|
||||||
project.data_dir.join("indices").join(hash(self.as_bytes()))
|
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>(
|
pub(crate) fn tree<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
repo: &'a gix::Repository,
|
repo: &'a gix::Repository,
|
||||||
|
|
Loading…
Reference in a new issue