feat(cli): add init, add, remove, and outdated commands

This commit is contained in:
daimond113 2024-03-10 19:03:12 +01:00
parent f2758c6351
commit 0be8a520c3
No known key found for this signature in database
GPG key ID: 3A8ECE51328B513C
12 changed files with 476 additions and 117 deletions

131
Cargo.lock generated
View file

@ -509,9 +509,9 @@ dependencies = [
[[package]]
name = "async-io"
version = "2.3.1"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f97ab0c5b00a7cdbe5a371b9a782ee7be1316095885c8a4ea1daf490eb0ef65"
checksum = "dcccb0f599cfa2f8ace422d3555572f47424da5648a4382a9dd0310ff8210884"
dependencies = [
"async-lock 3.3.0",
"cfg-if",
@ -580,7 +580,7 @@ version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e47d90f65a225c4527103a8d747001fc56e375203592b25ad103e1ca13124c5"
dependencies = [
"async-io 2.3.1",
"async-io 2.3.2",
"async-lock 2.8.0",
"atomic-waker",
"cfg-if",
@ -790,9 +790,9 @@ dependencies = [
[[package]]
name = "bumpalo"
version = "3.15.3"
version = "3.15.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea184aa71bb362a1157c896979544cc23974e08fd265f29ea96b59f0b4a555b"
checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa"
[[package]]
name = "bytecount"
@ -829,9 +829,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.0.89"
version = "1.0.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0ba8f7aaa012f30d5b2861462f6708eccd49c3c39863fe083a308035f63d723"
checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5"
dependencies = [
"jobserver",
"libc",
@ -1077,6 +1077,31 @@ version = "0.8.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
[[package]]
name = "crossterm"
version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67"
dependencies = [
"bitflags 1.3.2",
"crossterm_winapi",
"libc",
"mio",
"parking_lot",
"signal-hook",
"signal-hook-mio",
"winapi",
]
[[package]]
name = "crossterm_winapi"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
dependencies = [
"winapi",
]
[[package]]
name = "crunchy"
version = "0.2.2"
@ -1280,6 +1305,12 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b"
[[package]]
name = "dyn-clone"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125"
[[package]]
name = "either"
version = "1.10.0"
@ -1349,9 +1380,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "erased-serde"
version = "0.4.3"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "388979d208a049ffdfb22fa33b9c81942215b940910bccfe258caeb25d125cb3"
checksum = "2b73807008a3c7f171cc40312f37d95ef0396e048b5848d775f54b1a4dd4a0d3"
dependencies = [
"serde",
]
@ -1689,6 +1720,24 @@ dependencies = [
"slab",
]
[[package]]
name = "fuzzy-matcher"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94"
dependencies = [
"thread_local",
]
[[package]]
name = "fxhash"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
dependencies = [
"byteorder 1.5.0",
]
[[package]]
name = "generator"
version = "0.7.5"
@ -2119,6 +2168,23 @@ dependencies = [
"log",
]
[[package]]
name = "inquire"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd05e4e63529f3c9c5f5c668c398217f72756ffe48c85266b49692c55accd1f7"
dependencies = [
"bitflags 2.4.2",
"crossterm",
"dyn-clone",
"fuzzy-matcher",
"fxhash",
"newline-converter",
"once_cell",
"unicode-segmentation",
"unicode-width",
]
[[package]]
name = "instant"
version = "0.1.12"
@ -2664,6 +2730,15 @@ dependencies = [
"tempfile",
]
[[package]]
name = "newline-converter"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47b6b097ecb1cbfed438542d16e84fd7ad9b0c76c8a65b7f9039212a3d14dc7f"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "nibble_vec"
version = "0.1.0"
@ -3034,6 +3109,7 @@ dependencies = [
"ignore",
"indicatif",
"indicatif-log-bridge",
"inquire",
"keyring",
"log",
"lune",
@ -3053,7 +3129,7 @@ dependencies = [
[[package]]
name = "pesde-registry"
version = "0.2.0"
version = "0.3.0"
dependencies = [
"actix-cors",
"actix-governor",
@ -3551,9 +3627,9 @@ dependencies = [
[[package]]
name = "reqwest"
version = "0.11.24"
version = "0.11.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6920094eb85afde5e4a138be3f2de8bbdf28000f0029e72c45025a56b042251"
checksum = "0eea5a9eb898d3783f17c6407670e3592fd174cb81a10e51d4c37f49450b9946"
dependencies = [
"base64 0.21.7",
"bytes",
@ -4183,6 +4259,27 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde"
[[package]]
name = "signal-hook"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801"
dependencies = [
"libc",
"signal-hook-registry",
]
[[package]]
name = "signal-hook-mio"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af"
dependencies = [
"libc",
"mio",
"signal-hook",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.1"
@ -4391,20 +4488,20 @@ checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
[[package]]
name = "system-configuration"
version = "0.5.1"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7"
checksum = "658bc6ee10a9b4fcf576e9b0819d95ec16f4d2c02d39fd83ac1c8789785c4a42"
dependencies = [
"bitflags 1.3.2",
"bitflags 2.4.2",
"core-foundation",
"system-configuration-sys",
]
[[package]]
name = "system-configuration-sys"
version = "0.5.0"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9"
checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
dependencies = [
"core-foundation-sys",
"libc",

View file

@ -9,7 +9,7 @@ homepage = "https://pesde.daimond113.com"
include = ["src/**/*", "Cargo.toml", "Cargo.lock", "README.md", "LICENSE", "CHANGELOG.md"]
[features]
bin = ["clap", "directories", "keyring", "anyhow", "ignore", "pretty_env_logger", "serde_json", "reqwest/json", "reqwest/multipart", "lune", "futures-executor", "indicatif", "auth-git2", "indicatif-log-bridge"]
bin = ["clap", "directories", "keyring", "anyhow", "ignore", "pretty_env_logger", "serde_json", "reqwest/json", "reqwest/multipart", "lune", "futures-executor", "indicatif", "auth-git2", "indicatif-log-bridge", "inquire"]
[[bin]]
name = "pesde"
@ -46,6 +46,7 @@ futures-executor = { version = "0.3.30", optional = true }
indicatif = { version = "0.17.8", optional = true }
auth-git2 = { version = "0.5.3", optional = true }
indicatif-log-bridge = { version = "0.2.2", optional = true }
inquire = { version = "0.7.0", optional = true }
[dev-dependencies]
tempfile = "3.10.1"

View file

@ -17,10 +17,10 @@ pub fn auth_command(cmd: AuthCommand, params: CliParams) -> anyhow::Result<()> {
match cmd {
AuthCommand::Login => {
let response = send_request!(params.reqwest_client.post(Url::parse_with_params(
let response = send_request(params.reqwest_client.post(Url::parse_with_params(
"https://github.com/login/device/code",
&[("client_id", index_config.github_oauth_client_id.as_str())],
)?))
)?))?
.json::<serde_json::Value>()?;
println!(
@ -43,14 +43,14 @@ pub fn auth_command(cmd: AuthCommand, params: CliParams) -> anyhow::Result<()> {
while time_left > 0 {
std::thread::sleep(interval);
time_left -= interval.as_secs() as i64;
let response = send_request!(params.reqwest_client.post(Url::parse_with_params(
let response = send_request(params.reqwest_client.post(Url::parse_with_params(
"https://github.com/login/oauth/access_token",
&[
("client_id", index_config.github_oauth_client_id.as_str()),
("device_code", device_code),
("grant_type", "urn:ietf:params:oauth:grant-type:device_code")
("grant_type", "urn:ietf:params:oauth:grant-type:device_code"),
],
)?))
)?))?
.json::<serde_json::Value>()?;
match response
@ -82,10 +82,12 @@ pub fn auth_command(cmd: AuthCommand, params: CliParams) -> anyhow::Result<()> {
params.api_token_entry.set_password(access_token)?;
let response = send_request!(params
.reqwest_client
.get("https://api.github.com/user")
.header(AUTHORIZATION, format!("Bearer {access_token}")))
let response = send_request(
params
.reqwest_client
.get("https://api.github.com/user")
.header(AUTHORIZATION, format!("Bearer {access_token}")),
)?
.json::<serde_json::Value>()?;
let login = response["login"]

View file

@ -8,6 +8,7 @@ use crate::{CliConfig, CliParams};
pub enum ConfigCommand {
/// Sets the index repository URL
SetIndexRepo {
/// The URL of the index repository
#[clap(value_name = "URL")]
url: String,
},
@ -16,6 +17,7 @@ pub enum ConfigCommand {
/// Sets the cache directory
SetCacheDir {
/// The directory to use as the cache directory
#[clap(value_name = "DIRECTORY")]
directory: Option<PathBuf>,
},

View file

@ -1,11 +1,13 @@
use std::{
fs::{create_dir_all, read, remove_dir_all, write},
fs::{create_dir_all, read, remove_dir_all, write, File},
str::FromStr,
time::Duration,
};
use flate2::{write::GzEncoder, Compression};
use futures_executor::block_on;
use ignore::{overrides::OverrideBuilder, WalkBuilder};
use inquire::{validator::Validation, Select, Text};
use log::debug;
use lune::Runtime;
use reqwest::{header::AUTHORIZATION, Url};
@ -14,13 +16,15 @@ use serde_json::Value;
use tar::Builder as TarBuilder;
use pesde::{
dependencies::PackageRef,
dependencies::{registry::RegistryDependencySpecifier, DependencySpecifier, PackageRef},
index::{GitIndex, Index},
manifest::Manifest,
manifest::{Manifest, PathStyle, Realm},
multithread::MultithreadedJob,
package_name::PackageName,
patches::{create_patch, setup_patches_repo},
project::{InstallOptions, Project},
DEV_PACKAGES_FOLDER, IGNORED_FOLDERS, PACKAGES_FOLDER, PATCHES_FOLDER, SERVER_PACKAGES_FOLDER,
DEV_PACKAGES_FOLDER, IGNORED_FOLDERS, MANIFEST_FILE_NAME, PACKAGES_FOLDER, PATCHES_FOLDER,
SERVER_PACKAGES_FOLDER,
};
use crate::{send_request, CliParams, Command};
@ -37,6 +41,32 @@ fn get_project(params: &CliParams) -> anyhow::Result<Project<GitIndex>> {
.map_err(Into::into)
}
fn multithreaded_bar<E: Send + Sync + Into<anyhow::Error> + 'static>(
params: &CliParams,
job: MultithreadedJob<E>,
len: u64,
message: String,
) -> Result<(), anyhow::Error> {
let bar = params.multi.add(
indicatif::ProgressBar::new(len)
.with_style(
indicatif::ProgressStyle::default_bar()
.template("{msg} {bar:40.208/166} {pos}/{len} {percent}% {elapsed_precise}")?,
)
.with_message(message),
);
bar.enable_steady_tick(Duration::from_millis(100));
while let Ok(result) = job.progress().recv() {
result.map_err(Into::into)?;
bar.inc(1);
}
bar.finish_with_message("done");
Ok(())
}
pub fn root_command(cmd: Command, params: CliParams) -> anyhow::Result<()> {
match cmd {
Command::Install { locked } => {
@ -55,21 +85,13 @@ pub fn root_command(cmd: Command, params: CliParams) -> anyhow::Result<()> {
let resolved_versions_map = project.manifest().dependency_tree(&project, locked)?;
let download_job = project.download(&resolved_versions_map)?;
let bar = params.multi.add(
indicatif::ProgressBar::new(resolved_versions_map.len() as u64)
.with_style(indicatif::ProgressStyle::default_bar().template(
"{msg} {bar:40.208/166} {pos}/{len} {percent}% {elapsed_precise}",
)?)
.with_message("Downloading packages"),
);
bar.enable_steady_tick(Duration::from_millis(100));
while let Ok(result) = download_job.progress().recv() {
result?;
bar.inc(1);
}
bar.finish_with_message("done");
multithreaded_bar(
&params,
download_job,
resolved_versions_map.len() as u64,
"Downloading packages".to_string(),
)?;
project.install(
InstallOptions::new()
@ -81,16 +103,16 @@ pub fn root_command(cmd: Command, params: CliParams) -> anyhow::Result<()> {
Command::Run { package, args } => {
let project = get_project(&params)?;
let name: PackageName = package.parse()?;
let lockfile = project
.lockfile()?
.ok_or(anyhow::anyhow!("lockfile not found"))?;
let (_, resolved_pkg) = lockfile
.get(&name)
.get(&package)
.and_then(|versions| versions.iter().find(|(_, pkg_ref)| pkg_ref.is_root))
.ok_or(anyhow::anyhow!("package not found in lockfile"))?;
.ok_or(anyhow::anyhow!(
"package not found in lockfile (or isn't root)"
))?;
if !resolved_pkg.is_root {
anyhow::bail!("package is not a root package");
@ -116,10 +138,10 @@ pub fn root_command(cmd: Command, params: CliParams) -> anyhow::Result<()> {
let config = params.index.config()?;
let api_url = config.api();
let response = send_request!(params.reqwest_client.get(Url::parse_with_params(
let response = send_request(params.reqwest_client.get(Url::parse_with_params(
&format!("{}/v0/search", api_url),
&query.map_or_else(Vec::new, |q| vec![("query", q)])
)?))
&query.map_or_else(Vec::new, |q| vec![("query", q)]),
)?))?
.json::<Value>()?;
for package in response.as_array().unwrap() {
@ -201,30 +223,24 @@ pub fn root_command(cmd: Command, params: CliParams) -> anyhow::Result<()> {
request = request.header(AUTHORIZATION, "");
}
println!("{}", send_request!(request).text()?);
println!("{}", send_request(request)?.text()?);
}
Command::Patch { package } => {
let project = get_project(&params)?;
let (name, version) = package
.split_once('@')
.ok_or(anyhow::anyhow!("Malformed package name"))?;
let name: PackageName = name.parse()?;
let version = Version::parse(version)?;
let lockfile = project
.lockfile()?
.ok_or(anyhow::anyhow!("lockfile not found"))?;
let resolved_pkg = lockfile
.get(&name)
.and_then(|versions| versions.get(&version))
.get(&package.0)
.and_then(|versions| versions.get(&package.1))
.ok_or(anyhow::anyhow!("package not found in lockfile"))?;
let dir = params.directories.data_dir().join("patches").join(format!(
"{}_{}",
name.escaped(),
version
package.0.escaped(),
package.1
));
if dir.exists() {
@ -274,6 +290,158 @@ pub fn root_command(cmd: Command, params: CliParams) -> anyhow::Result<()> {
env!("CARGO_BIN_NAME")
);
}
Command::Init => {
let manifest_path = params.cwd.join(MANIFEST_FILE_NAME);
if manifest_path.exists() {
anyhow::bail!("manifest already exists");
}
let default_name = params.cwd.file_name().unwrap().to_str().unwrap();
let name = Text::new("What is the name of the package?")
.with_initial_value(default_name)
.with_validator(|name: &str| {
Ok(match PackageName::from_str(name) {
Ok(_) => Validation::Valid,
Err(e) => Validation::Invalid(e.into()),
})
})
.prompt()?;
let path_style =
Select::new("What style of paths do you want to use?", vec!["roblox"]).prompt()?;
let path_style = match path_style {
"roblox" => PathStyle::Roblox {
place: Default::default(),
},
_ => unreachable!(),
};
let description = Text::new("What is the description of the package?").prompt()?;
let license = Text::new("What is the license of the package?").prompt()?;
let authors = Text::new("Who are the authors of the package? (split using ;)")
.prompt()?
.split(';')
.map(|s| s.trim().to_string())
.collect::<Vec<String>>();
let private = Select::new("Is this package private?", vec!["yes", "no"]).prompt()?;
let private = private == "yes";
let realm = Select::new(
"What is the realm of the package?",
vec!["shared", "server", "dev"],
)
.prompt()?;
let realm = match realm {
"shared" => Realm::Shared,
"server" => Realm::Server,
"dev" => Realm::Development,
_ => unreachable!(),
};
let manifest = Manifest {
name: name.parse()?,
version: Version::parse("0.1.0")?,
exports: Default::default(),
path_style,
private,
realm: Some(realm),
dependencies: Default::default(),
peer_dependencies: Default::default(),
description: Some(description),
license: Some(license),
authors: Some(authors),
};
serde_yaml::to_writer(File::create(manifest_path)?, &manifest)?;
}
Command::Add {
package,
realm,
peer,
} => {
let project = get_project(&params)?;
let mut manifest = project.manifest().clone();
let specifier = DependencySpecifier::Registry(RegistryDependencySpecifier {
name: package.0,
version: package.1,
realm,
});
if peer {
manifest.peer_dependencies.push(specifier);
} else {
manifest.dependencies.push(specifier);
}
serde_yaml::to_writer(
File::create(project.path().join(MANIFEST_FILE_NAME))?,
&manifest,
)?;
}
Command::Remove { package } => {
let project = get_project(&params)?;
let mut manifest = project.manifest().clone();
for dependencies in [&mut manifest.dependencies, &mut manifest.peer_dependencies] {
dependencies.retain(|d| {
if let DependencySpecifier::Registry(registry) = d {
registry.name != package
} else {
true
}
});
}
serde_yaml::to_writer(
File::create(project.path().join(MANIFEST_FILE_NAME))?,
&manifest,
)?;
}
Command::Outdated => {
let project = get_project(&params)?;
let manifest = project.manifest();
let dependency_tree = manifest.dependency_tree(&project, false)?;
for (name, versions) in dependency_tree {
for (version, resolved_pkg) in versions {
if !resolved_pkg.is_root {
continue;
}
if let PackageRef::Registry(registry) = resolved_pkg.pkg_ref {
let latest_version = send_request(params.reqwest_client.get(format!(
"{}/v0/packages/{}/{}/versions",
project.index().config()?.api(),
registry.name.scope(),
registry.name.name()
)))?
.json::<Value>()?
.as_array()
.unwrap()
.iter()
.map(|v| Version::parse(v.as_str().unwrap()))
.collect::<Result<Vec<Version>, semver::Error>>()?
.into_iter()
.max()
.unwrap();
if latest_version > version {
println!(
"{name}@{version} is outdated. latest version: {latest_version}"
);
}
}
}
}
}
_ => unreachable!(),
}

View file

@ -22,6 +22,7 @@ pub struct GitDependencySpecifier {
/// The revision of the git repository to use
pub rev: String,
/// The realm of the package
#[serde(skip_serializing_if = "Option::is_none")]
pub realm: Option<Realm>,
}
@ -65,17 +66,15 @@ impl GitDependencySpecifier {
// should also work with ssh urls
let is_url = self.repo.contains(':');
let repo_name = {
if !is_url {
self.repo.to_string()
} else {
let parts: Vec<&str> = self.repo.split('/').collect();
format!(
"{}/{}",
parts[parts.len() - 2],
parts[parts.len() - 1].trim_end_matches(".git")
)
}
let repo_name = if !is_url {
self.repo.to_string()
} else {
let parts: Vec<&str> = self.repo.split('/').collect();
format!(
"{}/{}",
parts[parts.len() - 2],
parts[parts.len() - 1].trim_end_matches(".git")
)
};
if is_url {
@ -84,12 +83,10 @@ impl GitDependencySpecifier {
debug!("assuming git repository is a name: {}", &repo_name);
}
let repo_url = {
if !is_url {
format!("https://github.com/{}.git", &self.repo)
} else {
self.repo.to_string()
}
let repo_url = if !is_url {
format!("https://github.com/{}.git", &self.repo)
} else {
self.repo.to_string()
};
if is_url {

View file

@ -143,7 +143,7 @@ impl<I: Index> Project<I> {
&self,
map: &ResolvedVersionsMap,
) -> Result<MultithreadedJob<DownloadError>, InstallProjectError> {
let (job, tx) = MultithreadedJob::new();
let job = MultithreadedJob::new();
for (name, versions) in map.clone() {
for (version, resolved_package) in versions {
@ -162,12 +162,8 @@ impl<I: Index> Project<I> {
create_dir_all(&source)?;
let project = self.clone();
let tx = tx.clone();
job.pool.execute(move || {
let result = resolved_package.pkg_ref.download(&project, source);
tx.send(result).unwrap();
});
job.execute(move || resolved_package.pkg_ref.download(&project, source));
}
}

View file

@ -22,6 +22,7 @@ pub struct RegistryDependencySpecifier {
// #[serde(skip_serializing_if = "Option::is_none")]
// pub registry: Option<String>,
/// The realm of the package
#[serde(skip_serializing_if = "Option::is_none")]
pub realm: Option<Realm>,
}

View file

@ -515,7 +515,6 @@ impl IndexConfig {
/// An entry in the index file
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(deny_unknown_fields)]
pub struct IndexFileEntry {
/// The version of the package
pub version: Version,
@ -525,12 +524,6 @@ pub struct IndexFileEntry {
/// A description of the package
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
/// The license of the package
#[serde(default, skip_serializing_if = "Option::is_none")]
pub license: Option<String>,
/// The authors of the package
#[serde(default, skip_serializing_if = "Option::is_none")]
pub authors: Option<Vec<String>>,
/// The dependencies of the package
#[serde(default, skip_serializing_if = "Vec::is_empty")]
@ -546,8 +539,6 @@ impl From<Manifest> for IndexFileEntry {
realm: manifest.realm,
description: manifest.description,
license: manifest.license,
authors: manifest.authors,
dependencies,
}

View file

@ -3,6 +3,7 @@ use std::{
fs::{create_dir_all, read},
hash::{DefaultHasher, Hash, Hasher},
path::PathBuf,
str::FromStr,
};
use auth_git2::GitAuthenticator;
@ -11,8 +12,13 @@ use directories::ProjectDirs;
use indicatif::MultiProgress;
use indicatif_log_bridge::LogWrapper;
use keyring::Entry;
use log::error;
use pretty_env_logger::env_logger::Env;
use reqwest::header::{ACCEPT, AUTHORIZATION};
use reqwest::{
blocking::{RequestBuilder, Response},
header::{ACCEPT, AUTHORIZATION},
};
use semver::{Version, VersionReq};
use serde::{Deserialize, Serialize};
use cli::{
@ -20,29 +26,79 @@ use cli::{
config::{config_command, ConfigCommand},
root::root_command,
};
use pesde::index::GitIndex;
use pesde::{index::GitIndex, manifest::Realm, package_name::PackageName};
mod cli;
#[derive(Debug, Clone)]
pub struct VersionedPackageName<V: FromStr<Err = semver::Error>>(PackageName, V);
impl<V: FromStr<Err = semver::Error>> FromStr for VersionedPackageName<V> {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (name, version) = s.split_once('@').ok_or_else(|| {
anyhow::anyhow!("invalid package name: {s}; expected format: name@version")
})?;
Ok(VersionedPackageName(
name.to_string().parse()?,
version.parse()?,
))
}
}
#[derive(Subcommand)]
pub enum Command {
/// Initializes a manifest file
Init,
/// Adds a package to the manifest
Add {
/// The package to add
#[clap(value_name = "PACKAGE")]
package: VersionedPackageName<VersionReq>,
/// Whether the package is a peer dependency
#[clap(long, short)]
peer: bool,
/// The realm of the package
#[clap(long, short)]
realm: Option<Realm>,
},
/// Removes a package from the manifest
Remove {
/// The package to remove
#[clap(value_name = "PACKAGE")]
package: PackageName,
},
/// Lists outdated packages
Outdated,
/// Installs the dependencies of the project
Install {
/// Whether to use the lockfile for resolving dependencies
#[clap(long, short)]
locked: bool,
},
/// Runs the `bin` export of the specified package
Run {
/// The package to run
#[clap(value_name = "PACKAGE")]
package: String,
package: PackageName,
/// The arguments to pass to the package
#[clap(last = true)]
args: Vec<String>,
},
/// Searches for a package on the registry
Search {
/// The query to search for
#[clap(value_name = "QUERY")]
query: Option<String>,
},
@ -51,10 +107,15 @@ pub enum Command {
Publish,
/// Begins a new patch
Patch { package: String },
Patch {
/// The package to patch
#[clap(value_name = "PACKAGE")]
package: VersionedPackageName<Version>,
},
/// Commits (finished) the patch
/// Commits (finishes) the patch
PatchCommit {
/// The package's changed directory
#[clap(value_name = "DIRECTORY")]
dir: PathBuf,
},
@ -77,6 +138,7 @@ struct Cli {
#[clap(subcommand)]
command: Command,
/// The directory to run the command in
#[arg(short, long, value_name = "DIRECTORY")]
directory: Option<PathBuf>,
}
@ -117,18 +179,16 @@ impl CliConfig {
}
}
#[macro_export]
macro_rules! send_request {
($req:expr) => {{
let res = $req.send()?;
pub fn send_request(request_builder: RequestBuilder) -> anyhow::Result<Response> {
let res = request_builder.send()?;
match res.error_for_status_ref() {
Ok(_) => res,
Err(e) => {
panic!("request failed: {e}\nbody: {}", res.text()?);
}
match res.error_for_status_ref() {
Ok(_) => Ok(res),
Err(e) => {
error!("request failed: {e}\nbody: {}", res.text()?);
Err(e.into())
}
}};
}
}
fn main() -> anyhow::Result<()> {

View file

@ -1,3 +1,4 @@
use std::str::FromStr;
use std::{collections::BTreeMap, fmt::Display, fs::read};
use relative_path::RelativePathBuf;
@ -87,6 +88,24 @@ impl Display for Realm {
}
}
/// An error that occurred while parsing a realm from a string
#[derive(Debug, Error)]
#[error("invalid realm {0}")]
pub struct FromStrRealmError(String);
impl FromStr for Realm {
type Err = FromStrRealmError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"shared" => Ok(Realm::Shared),
"server" => Ok(Realm::Server),
"development" => Ok(Realm::Development),
_ => Err(FromStrRealmError(s.to_string())),
}
}
}
/// The manifest of a package
#[derive(Serialize, Deserialize, Debug, Clone)]
// #[serde(deny_unknown_fields)]

View file

@ -1,18 +1,30 @@
use std::sync::mpsc::Receiver;
use std::sync::mpsc::{Receiver, Sender};
use threadpool::ThreadPool;
/// A multithreaded job
pub struct MultithreadedJob<E> {
pub(crate) progress: Receiver<Result<(), E>>,
pub(crate) pool: ThreadPool,
pub struct MultithreadedJob<E: Send + Sync + 'static> {
progress: Receiver<Result<(), E>>,
sender: Sender<Result<(), E>>,
pool: ThreadPool,
}
impl<E> MultithreadedJob<E> {
pub(crate) fn new() -> (Self, std::sync::mpsc::Sender<Result<(), E>>) {
impl<E: Send + Sync + 'static> Default for MultithreadedJob<E> {
fn default() -> Self {
let (tx, rx) = std::sync::mpsc::channel();
let pool = ThreadPool::new(6);
(Self { progress: rx, pool }, tx)
Self {
progress: rx,
pool,
sender: tx.clone(),
}
}
}
impl<E: Send + Sync + 'static> MultithreadedJob<E> {
/// Creates a new multithreaded job
pub fn new() -> Self {
Self::default()
}
/// Returns the progress of the job
@ -30,4 +42,17 @@ impl<E> MultithreadedJob<E> {
Ok(())
}
/// Executes a function on the thread pool
pub fn execute<F>(&self, f: F)
where
F: (FnOnce() -> Result<(), E>) + Send + 'static,
{
let sender = self.sender.clone();
self.pool.execute(move || {
let result = f();
sender.send(result).unwrap();
});
}
}