mirror of
https://github.com/pesde-pkg/pesde.git
synced 2024-12-12 11:00:36 +00:00
feat(cli): ✨ add init, add, remove, and outdated commands
This commit is contained in:
parent
f2758c6351
commit
0be8a520c3
12 changed files with 476 additions and 117 deletions
131
Cargo.lock
generated
131
Cargo.lock
generated
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
let response = send_request(
|
||||
params
|
||||
.reqwest_client
|
||||
.get("https://api.github.com/user")
|
||||
.header(AUTHORIZATION, format!("Bearer {access_token}")))
|
||||
.header(AUTHORIZATION, format!("Bearer {access_token}")),
|
||||
)?
|
||||
.json::<serde_json::Value>()?;
|
||||
|
||||
let login = response["login"]
|
||||
|
|
|
@ -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>,
|
||||
},
|
||||
|
|
240
src/cli/root.rs
240
src/cli/root.rs
|
@ -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(
|
||||
¶ms,
|
||||
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(¶ms)?;
|
||||
|
||||
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(¶ms)?;
|
||||
|
||||
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(¶ms)?;
|
||||
|
||||
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(¶ms)?;
|
||||
|
||||
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(¶ms)?;
|
||||
|
||||
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!(),
|
||||
}
|
||||
|
||||
|
|
|
@ -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,8 +66,7 @@ impl GitDependencySpecifier {
|
|||
// should also work with ssh urls
|
||||
let is_url = self.repo.contains(':');
|
||||
|
||||
let repo_name = {
|
||||
if !is_url {
|
||||
let repo_name = if !is_url {
|
||||
self.repo.to_string()
|
||||
} else {
|
||||
let parts: Vec<&str> = self.repo.split('/').collect();
|
||||
|
@ -75,7 +75,6 @@ impl GitDependencySpecifier {
|
|||
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 {
|
||||
let repo_url = if !is_url {
|
||||
format!("https://github.com/{}.git", &self.repo)
|
||||
} else {
|
||||
self.repo.to_string()
|
||||
}
|
||||
};
|
||||
|
||||
if is_url {
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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>,
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
84
src/main.rs
84
src/main.rs
|
@ -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,
|
||||
Ok(_) => Ok(res),
|
||||
Err(e) => {
|
||||
panic!("request failed: {e}\nbody: {}", res.text()?);
|
||||
error!("request failed: {e}\nbody: {}", res.text()?);
|
||||
Err(e.into())
|
||||
}
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue