mirror of
https://github.com/pesde-pkg/pesde.git
synced 2024-12-12 11:00:36 +00:00
refactor(cli): 🎨 use static variables
This commit is contained in:
parent
9e1ffc41b6
commit
5c77d8db76
12 changed files with 520 additions and 387 deletions
57
Cargo.lock
generated
57
Cargo.lock
generated
|
@ -92,7 +92,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
"syn 2.0.53",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -130,7 +130,7 @@ dependencies = [
|
|||
"parse-size",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
"syn 2.0.53",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -243,7 +243,7 @@ dependencies = [
|
|||
"actix-router",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
"syn 2.0.53",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -571,7 +571,7 @@ checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
"syn 2.0.53",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -600,13 +600,13 @@ checksum = "fbb36e985947064623dbd357f727af08ffd077f93d696782f3c56365fa2e2799"
|
|||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.77"
|
||||
version = "0.1.78"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9"
|
||||
checksum = "461abc97219de0eaaf81fe3ef974a540158f3d079c2ab200f891f1a2ef201e85"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
"syn 2.0.53",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -925,7 +925,7 @@ dependencies = [
|
|||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
"syn 2.0.53",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1146,7 +1146,7 @@ dependencies = [
|
|||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim 0.10.0",
|
||||
"syn 2.0.52",
|
||||
"syn 2.0.53",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1157,7 +1157,7 @@ checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f"
|
|||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
"syn 2.0.53",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1363,7 +1363,7 @@ checksum = "5c785274071b1b420972453b306eeca06acf4633829db4223b58a2a8c5953bc4"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
"syn 2.0.53",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1711,7 +1711,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
"syn 2.0.53",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2604,9 +2604,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "luau0-src"
|
||||
version = "0.8.4+luau616"
|
||||
version = "0.8.5+luau617"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74f34c6c8e52606273c5d689b722e03383e58fed85840868885301fe9038fbd9"
|
||||
checksum = "652e36b8c35d807ec76a4931fe7c62883c62cc93311fb49bf5b76084647078ea"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
@ -3103,7 +3103,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
"syn 2.0.53",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3263,6 +3263,7 @@ dependencies = [
|
|||
"keyring",
|
||||
"log",
|
||||
"lune",
|
||||
"once_cell",
|
||||
"pathdiff",
|
||||
"pretty_env_logger",
|
||||
"relative-path",
|
||||
|
@ -3280,7 +3281,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "pesde-registry"
|
||||
version = "0.4.0"
|
||||
version = "0.5.0"
|
||||
dependencies = [
|
||||
"actix-cors",
|
||||
"actix-governor",
|
||||
|
@ -3325,7 +3326,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
"syn 2.0.53",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3470,7 +3471,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
"syn 2.0.53",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -4320,7 +4321,7 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
"syn 2.0.53",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -4352,7 +4353,7 @@ checksum = "0b2e6b945e9d3df726b65d6ee24060aff8e3533d431f677a9695db04eff9dfdb"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
"syn 2.0.53",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -4653,9 +4654,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.52"
|
||||
version = "2.0.53"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07"
|
||||
checksum = "7383cd0e49fff4b6b90ca5670bfd3e9d6a733b3f90c686605aa7eec8c4996032"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -4888,7 +4889,7 @@ checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
"syn 2.0.53",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -5022,7 +5023,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
"syn 2.0.53",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -5180,7 +5181,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
"syn 2.0.53",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -5474,7 +5475,7 @@ dependencies = [
|
|||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
"syn 2.0.53",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
|
@ -5508,7 +5509,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
"syn 2.0.53",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
@ -5872,7 +5873,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
"syn 2.0.53",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -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", "inquire"]
|
||||
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", "once_cell"]
|
||||
|
||||
[[bin]]
|
||||
name = "pesde"
|
||||
|
@ -48,6 +48,7 @@ indicatif = { version = "0.17.8", optional = true }
|
|||
auth-git2 = { version = "0.5.4", optional = true }
|
||||
indicatif-log-bridge = { version = "0.2.2", optional = true }
|
||||
inquire = { version = "0.7.1", optional = true }
|
||||
once_cell = { version = "1.19.0", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.10.1"
|
||||
|
|
|
@ -174,10 +174,7 @@ pub async fn get_package_version(
|
|||
match index.package(&package_name)? {
|
||||
Some(package) => {
|
||||
if version == "latest" {
|
||||
version = package
|
||||
.last()
|
||||
.map(|v| v.version.to_string())
|
||||
.unwrap();
|
||||
version = package.last().map(|v| v.version.to_string()).unwrap();
|
||||
} else if !package.iter().any(|v| v.version.to_string() == version) {
|
||||
return Ok(HttpResponse::NotFound().finish());
|
||||
}
|
||||
|
|
147
src/cli/api_token.rs
Normal file
147
src/cli/api_token.rs
Normal file
|
@ -0,0 +1,147 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use keyring::Entry;
|
||||
use once_cell::sync::Lazy;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::cli::INDEX_DIR;
|
||||
|
||||
pub trait ApiTokenSource: Send + Sync {
|
||||
fn get_api_token(&self) -> anyhow::Result<Option<String>>;
|
||||
fn set_api_token(&self, api_token: &str) -> anyhow::Result<()>;
|
||||
fn delete_api_token(&self) -> anyhow::Result<()>;
|
||||
fn persists(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EnvVarApiTokenSource;
|
||||
|
||||
const API_TOKEN_ENV_VAR: &str = "PESDE_API_TOKEN";
|
||||
|
||||
impl ApiTokenSource for EnvVarApiTokenSource {
|
||||
fn get_api_token(&self) -> anyhow::Result<Option<String>> {
|
||||
match std::env::var(API_TOKEN_ENV_VAR) {
|
||||
Ok(token) => Ok(Some(token)),
|
||||
Err(std::env::VarError::NotPresent) => Ok(None),
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
}
|
||||
|
||||
// don't need to implement set_api_token or delete_api_token
|
||||
fn set_api_token(&self, _api_token: &str) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn delete_api_token(&self) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn persists(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
static KEYRING_ENTRY: Lazy<Entry> =
|
||||
Lazy::new(|| Entry::new(env!("CARGO_BIN_NAME"), "api_token").unwrap());
|
||||
|
||||
pub struct KeyringApiTokenSource;
|
||||
|
||||
impl ApiTokenSource for KeyringApiTokenSource {
|
||||
fn get_api_token(&self) -> anyhow::Result<Option<String>> {
|
||||
match KEYRING_ENTRY.get_password() {
|
||||
Ok(api_token) => Ok(Some(api_token)),
|
||||
Err(err) => match err {
|
||||
keyring::Error::NoEntry | keyring::Error::PlatformFailure(_) => Ok(None),
|
||||
_ => Err(err.into()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn set_api_token(&self, api_token: &str) -> anyhow::Result<()> {
|
||||
KEYRING_ENTRY.set_password(api_token)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn delete_api_token(&self) -> anyhow::Result<()> {
|
||||
KEYRING_ENTRY.delete_password()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
static AUTH_FILE_PATH: Lazy<PathBuf> = Lazy::new(|| INDEX_DIR.join("auth.yaml"));
|
||||
static AUTH_FILE: Lazy<AuthFile> =
|
||||
Lazy::new(
|
||||
|| match std::fs::read_to_string(AUTH_FILE_PATH.to_path_buf()) {
|
||||
Ok(config) => serde_yaml::from_str(&config).unwrap(),
|
||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => AuthFile::default(),
|
||||
Err(e) => panic!("{:?}", e),
|
||||
},
|
||||
);
|
||||
|
||||
#[derive(Serialize, Deserialize, Default, Clone)]
|
||||
struct AuthFile {
|
||||
#[serde(default)]
|
||||
api_token: Option<String>,
|
||||
}
|
||||
|
||||
pub struct ConfigFileApiTokenSource;
|
||||
|
||||
impl ApiTokenSource for ConfigFileApiTokenSource {
|
||||
fn get_api_token(&self) -> anyhow::Result<Option<String>> {
|
||||
Ok(AUTH_FILE.api_token.clone())
|
||||
}
|
||||
|
||||
fn set_api_token(&self, api_token: &str) -> anyhow::Result<()> {
|
||||
let mut config = AUTH_FILE.clone();
|
||||
config.api_token = Some(api_token.to_string());
|
||||
|
||||
serde_yaml::to_writer(
|
||||
&mut std::fs::File::create(AUTH_FILE_PATH.to_path_buf())?,
|
||||
&config,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn delete_api_token(&self) -> anyhow::Result<()> {
|
||||
let mut config = AUTH_FILE.clone();
|
||||
|
||||
config.api_token = None;
|
||||
|
||||
serde_yaml::to_writer(
|
||||
&mut std::fs::File::create(AUTH_FILE_PATH.to_path_buf())?,
|
||||
&config,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub static API_TOKEN_SOURCE: Lazy<Box<dyn ApiTokenSource>> = Lazy::new(|| {
|
||||
let sources: Vec<Box<dyn ApiTokenSource>> = vec![
|
||||
Box::new(EnvVarApiTokenSource),
|
||||
Box::new(KeyringApiTokenSource),
|
||||
Box::new(ConfigFileApiTokenSource),
|
||||
];
|
||||
|
||||
let mut valid_sources = vec![];
|
||||
|
||||
for source in sources {
|
||||
match source.get_api_token() {
|
||||
Ok(Some(_)) => return source,
|
||||
Ok(None) => {
|
||||
if source.persists() {
|
||||
valid_sources.push(source);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("error getting api token: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
valid_sources.pop().unwrap()
|
||||
});
|
|
@ -2,9 +2,9 @@ use clap::Subcommand;
|
|||
use pesde::index::Index;
|
||||
use reqwest::{header::AUTHORIZATION, Url};
|
||||
|
||||
use crate::{send_request, CliParams};
|
||||
use crate::cli::{api_token::API_TOKEN_SOURCE, send_request, INDEX, REQWEST_CLIENT};
|
||||
|
||||
#[derive(Subcommand)]
|
||||
#[derive(Subcommand, Clone)]
|
||||
pub enum AuthCommand {
|
||||
/// Logs in to the registry
|
||||
Login,
|
||||
|
@ -12,14 +12,14 @@ pub enum AuthCommand {
|
|||
Logout,
|
||||
}
|
||||
|
||||
pub fn auth_command(cmd: AuthCommand, params: CliParams) -> anyhow::Result<()> {
|
||||
let index_config = params.index.config()?;
|
||||
|
||||
pub fn auth_command(cmd: AuthCommand) -> anyhow::Result<()> {
|
||||
match cmd {
|
||||
AuthCommand::Login => {
|
||||
let response = send_request(params.reqwest_client.post(Url::parse_with_params(
|
||||
let github_oauth_client_id = INDEX.config()?.github_oauth_client_id;
|
||||
|
||||
let response = send_request(REQWEST_CLIENT.post(Url::parse_with_params(
|
||||
"https://github.com/login/device/code",
|
||||
&[("client_id", index_config.github_oauth_client_id.as_str())],
|
||||
&[("client_id", &github_oauth_client_id)],
|
||||
)?))?
|
||||
.json::<serde_json::Value>()?;
|
||||
|
||||
|
@ -43,10 +43,10 @@ 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(REQWEST_CLIENT.post(Url::parse_with_params(
|
||||
"https://github.com/login/oauth/access_token",
|
||||
&[
|
||||
("client_id", index_config.github_oauth_client_id.as_str()),
|
||||
("client_id", github_oauth_client_id.as_str()),
|
||||
("device_code", device_code),
|
||||
("grant_type", "urn:ietf:params:oauth:grant-type:device_code"),
|
||||
],
|
||||
|
@ -80,11 +80,10 @@ pub fn auth_command(cmd: AuthCommand, params: CliParams) -> anyhow::Result<()> {
|
|||
.as_str()
|
||||
.ok_or(anyhow::anyhow!("couldn't get access_token"))?;
|
||||
|
||||
params.api_token_entry.set_password(access_token)?;
|
||||
API_TOKEN_SOURCE.set_api_token(access_token)?;
|
||||
|
||||
let response = send_request(
|
||||
params
|
||||
.reqwest_client
|
||||
REQWEST_CLIENT
|
||||
.get("https://api.github.com/user")
|
||||
.header(AUTHORIZATION, format!("Bearer {access_token}")),
|
||||
)?
|
||||
|
@ -102,7 +101,7 @@ pub fn auth_command(cmd: AuthCommand, params: CliParams) -> anyhow::Result<()> {
|
|||
anyhow::bail!("code expired, please re-run the login command");
|
||||
}
|
||||
AuthCommand::Logout => {
|
||||
params.api_token_entry.delete_password()?;
|
||||
API_TOKEN_SOURCE.delete_api_token()?;
|
||||
|
||||
println!("you're now logged out");
|
||||
}
|
||||
|
|
|
@ -2,9 +2,9 @@ use std::path::PathBuf;
|
|||
|
||||
use clap::Subcommand;
|
||||
|
||||
use crate::{CliConfig, CliParams};
|
||||
use crate::{cli::CLI_CONFIG, CliConfig};
|
||||
|
||||
#[derive(Subcommand)]
|
||||
#[derive(Subcommand, Clone)]
|
||||
pub enum ConfigCommand {
|
||||
/// Sets the index repository URL
|
||||
SetIndexRepo {
|
||||
|
@ -25,41 +25,41 @@ pub enum ConfigCommand {
|
|||
GetCacheDir,
|
||||
}
|
||||
|
||||
pub fn config_command(cmd: ConfigCommand, params: CliParams) -> anyhow::Result<()> {
|
||||
pub fn config_command(cmd: ConfigCommand) -> anyhow::Result<()> {
|
||||
match cmd {
|
||||
ConfigCommand::SetIndexRepo { url } => {
|
||||
let cli_config = CliConfig {
|
||||
index_repo_url: url.clone(),
|
||||
..params.cli_config
|
||||
..CLI_CONFIG.clone()
|
||||
};
|
||||
|
||||
cli_config.write(¶ms.directories)?;
|
||||
cli_config.write()?;
|
||||
|
||||
println!("index repository url set to: `{url}`");
|
||||
}
|
||||
ConfigCommand::GetIndexRepo => {
|
||||
println!(
|
||||
"current index repository url: `{}`",
|
||||
params.cli_config.index_repo_url
|
||||
CLI_CONFIG.index_repo_url
|
||||
);
|
||||
}
|
||||
ConfigCommand::SetCacheDir { directory } => {
|
||||
let cli_config = CliConfig {
|
||||
cache_dir: directory,
|
||||
..params.cli_config
|
||||
..CLI_CONFIG.clone()
|
||||
};
|
||||
|
||||
cli_config.write(¶ms.directories)?;
|
||||
cli_config.write()?;
|
||||
|
||||
println!(
|
||||
"cache directory set to: `{}`",
|
||||
cli_config.cache_dir(¶ms.directories).display()
|
||||
cli_config.cache_dir().display()
|
||||
);
|
||||
}
|
||||
ConfigCommand::GetCacheDir => {
|
||||
println!(
|
||||
"current cache directory: `{}`",
|
||||
params.cli_config.cache_dir(¶ms.directories).display()
|
||||
CLI_CONFIG.cache_dir().display()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
274
src/cli/mod.rs
274
src/cli/mod.rs
|
@ -1,3 +1,277 @@
|
|||
use crate::cli::{api_token::API_TOKEN_SOURCE, auth::AuthCommand, config::ConfigCommand};
|
||||
use auth_git2::GitAuthenticator;
|
||||
use clap::{Parser, Subcommand};
|
||||
use directories::ProjectDirs;
|
||||
use indicatif::MultiProgress;
|
||||
use indicatif_log_bridge::LogWrapper;
|
||||
use log::error;
|
||||
use once_cell::sync::Lazy;
|
||||
use pesde::{index::GitIndex, manifest::Realm, package_name::PackageName};
|
||||
use pretty_env_logger::env_logger::Env;
|
||||
use reqwest::{
|
||||
blocking::{RequestBuilder, Response},
|
||||
header::ACCEPT,
|
||||
};
|
||||
use semver::{Version, VersionReq};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
hash::{DefaultHasher, Hash, Hasher},
|
||||
path::PathBuf,
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
pub mod api_token;
|
||||
pub mod auth;
|
||||
pub mod config;
|
||||
pub mod root;
|
||||
|
||||
#[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, Clone)]
|
||||
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: 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>,
|
||||
},
|
||||
|
||||
/// Publishes the project to the registry
|
||||
Publish,
|
||||
|
||||
/// Converts a `wally.toml` file to a `pesde.yaml` file
|
||||
Convert,
|
||||
|
||||
/// Begins a new patch
|
||||
Patch {
|
||||
/// The package to patch
|
||||
#[clap(value_name = "PACKAGE")]
|
||||
package: VersionedPackageName<Version>,
|
||||
},
|
||||
|
||||
/// Commits (finishes) the patch
|
||||
PatchCommit {
|
||||
/// The package's changed directory
|
||||
#[clap(value_name = "DIRECTORY")]
|
||||
dir: PathBuf,
|
||||
},
|
||||
|
||||
/// Auth-related commands
|
||||
Auth {
|
||||
#[clap(subcommand)]
|
||||
command: AuthCommand,
|
||||
},
|
||||
|
||||
/// Config-related commands
|
||||
Config {
|
||||
#[clap(subcommand)]
|
||||
command: ConfigCommand,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Parser, Clone)]
|
||||
pub struct Cli {
|
||||
#[clap(subcommand)]
|
||||
pub command: Command,
|
||||
|
||||
/// The directory to run the command in
|
||||
#[arg(short, long, value_name = "DIRECTORY")]
|
||||
pub directory: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct CliConfig {
|
||||
pub index_repo_url: String,
|
||||
pub cache_dir: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl Default for CliConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
index_repo_url: "https://github.com/daimond113/pesde-index".to_string(),
|
||||
cache_dir: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CliConfig {
|
||||
pub fn cache_dir(&self) -> PathBuf {
|
||||
self.cache_dir
|
||||
.clone()
|
||||
.unwrap_or_else(|| DIRS.cache_dir().to_path_buf())
|
||||
}
|
||||
|
||||
pub fn open() -> anyhow::Result<Self> {
|
||||
let cli_config_path = DIRS.config_dir().join("config.yaml");
|
||||
|
||||
if cli_config_path.exists() {
|
||||
Ok(serde_yaml::from_slice(&std::fs::read(cli_config_path)?)?)
|
||||
} else {
|
||||
let config = CliConfig::default();
|
||||
config.write()?;
|
||||
Ok(config)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(&self) -> anyhow::Result<()> {
|
||||
let cli_config_path = DIRS.config_dir().join("config.yaml");
|
||||
serde_yaml::to_writer(
|
||||
&mut std::fs::File::create(cli_config_path.as_path())?,
|
||||
&self,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_request(request_builder: RequestBuilder) -> anyhow::Result<Response> {
|
||||
let res = request_builder.send()?;
|
||||
|
||||
match res.error_for_status_ref() {
|
||||
Ok(_) => Ok(res),
|
||||
Err(e) => {
|
||||
error!("request failed: {e}\nbody: {}", res.text()?);
|
||||
Err(e.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub static CLI: Lazy<Cli> = Lazy::new(Cli::parse);
|
||||
|
||||
pub static DIRS: Lazy<ProjectDirs> = Lazy::new(|| {
|
||||
ProjectDirs::from("com", env!("CARGO_BIN_NAME"), env!("CARGO_BIN_NAME"))
|
||||
.expect("couldn't get home directory")
|
||||
});
|
||||
|
||||
pub static CLI_CONFIG: Lazy<CliConfig> = Lazy::new(|| CliConfig::open().unwrap());
|
||||
|
||||
pub static INDEX_DIR: Lazy<PathBuf> = Lazy::new(|| {
|
||||
let mut hasher = DefaultHasher::new();
|
||||
CLI_CONFIG.index_repo_url.hash(&mut hasher);
|
||||
let hash = hasher.finish().to_string();
|
||||
|
||||
CLI_CONFIG.cache_dir().join("indices").join(hash)
|
||||
});
|
||||
|
||||
pub static INDEX: Lazy<GitIndex> = Lazy::new(|| {
|
||||
let index = GitIndex::new(
|
||||
INDEX_DIR.join("index"),
|
||||
&CLI_CONFIG.index_repo_url,
|
||||
Some(Box::new(|| {
|
||||
Box::new(|a, b, c| {
|
||||
let git_authenticator = GitAuthenticator::new();
|
||||
let config = git2::Config::open_default().unwrap();
|
||||
let mut cred = git_authenticator.credentials(&config);
|
||||
|
||||
cred(a, b, c)
|
||||
})
|
||||
})),
|
||||
);
|
||||
|
||||
index.refresh().unwrap();
|
||||
|
||||
index
|
||||
});
|
||||
|
||||
pub static CWD: Lazy<PathBuf> = Lazy::new(|| {
|
||||
CLI.directory
|
||||
.clone()
|
||||
.or(std::env::current_dir().ok())
|
||||
.expect("couldn't get current directory")
|
||||
});
|
||||
|
||||
pub static REQWEST_CLIENT: Lazy<reqwest::blocking::Client> = Lazy::new(|| {
|
||||
let mut header_map = reqwest::header::HeaderMap::new();
|
||||
header_map.insert(ACCEPT, "application/json".parse().unwrap());
|
||||
header_map.insert("X-GitHub-Api-Version", "2022-11-28".parse().unwrap());
|
||||
|
||||
if let Ok(Some(token)) = API_TOKEN_SOURCE.get_api_token() {
|
||||
header_map.insert(
|
||||
reqwest::header::AUTHORIZATION,
|
||||
format!("Bearer {token}").parse().unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
reqwest::blocking::Client::builder()
|
||||
.user_agent(concat!(
|
||||
env!("CARGO_PKG_NAME"),
|
||||
"/",
|
||||
env!("CARGO_PKG_VERSION")
|
||||
))
|
||||
.default_headers(header_map)
|
||||
.build()
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
pub static MULTI: Lazy<MultiProgress> = Lazy::new(|| {
|
||||
let logger = pretty_env_logger::formatted_builder()
|
||||
.parse_env(Env::default().default_filter_or("info"))
|
||||
.build();
|
||||
let multi = MultiProgress::new();
|
||||
|
||||
LogWrapper::new(multi.clone(), logger).try_init().unwrap();
|
||||
|
||||
multi
|
||||
});
|
||||
|
|
|
@ -10,6 +10,7 @@ use ignore::{overrides::OverrideBuilder, WalkBuilder};
|
|||
use inquire::{validator::Validation, Select, Text};
|
||||
use log::debug;
|
||||
use lune::Runtime;
|
||||
use once_cell::sync::Lazy;
|
||||
use reqwest::{header::AUTHORIZATION, Url};
|
||||
use semver::Version;
|
||||
use serde_json::Value;
|
||||
|
@ -27,27 +28,19 @@ use pesde::{
|
|||
SERVER_PACKAGES_FOLDER,
|
||||
};
|
||||
|
||||
use crate::{send_request, CliParams, Command};
|
||||
use crate::cli::{
|
||||
api_token::API_TOKEN_SOURCE, send_request, Command, CLI_CONFIG, CWD, DIRS, INDEX, MULTI,
|
||||
REQWEST_CLIENT,
|
||||
};
|
||||
|
||||
pub const MAX_ARCHIVE_SIZE: usize = 4 * 1024 * 1024;
|
||||
|
||||
fn get_project(params: &CliParams) -> anyhow::Result<Project<GitIndex>> {
|
||||
Project::from_path(
|
||||
¶ms.cwd,
|
||||
params.cli_config.cache_dir(¶ms.directories),
|
||||
params.index.clone(),
|
||||
params.api_token_entry.get_password().ok(),
|
||||
)
|
||||
.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(
|
||||
let bar = MULTI.add(
|
||||
indicatif::ProgressBar::new(len)
|
||||
.with_style(
|
||||
indicatif::ProgressStyle::default_bar()
|
||||
|
@ -77,13 +70,21 @@ macro_rules! none_if_empty {
|
|||
};
|
||||
}
|
||||
|
||||
pub fn root_command(cmd: Command, params: CliParams) -> anyhow::Result<()> {
|
||||
pub fn root_command(cmd: Command) -> anyhow::Result<()> {
|
||||
let project: Lazy<Project<GitIndex>> = Lazy::new(|| {
|
||||
Project::from_path(
|
||||
CWD.to_path_buf(),
|
||||
CLI_CONFIG.cache_dir(),
|
||||
INDEX.clone(),
|
||||
API_TOKEN_SOURCE.get_api_token().ok().flatten(),
|
||||
)
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
match cmd {
|
||||
Command::Install { locked } => {
|
||||
let project = get_project(¶ms)?;
|
||||
|
||||
for packages_folder in &[PACKAGES_FOLDER, DEV_PACKAGES_FOLDER, SERVER_PACKAGES_FOLDER] {
|
||||
if let Err(e) = remove_dir_all(¶ms.cwd.join(packages_folder)) {
|
||||
if let Err(e) = remove_dir_all(CWD.join(packages_folder)) {
|
||||
if e.kind() != std::io::ErrorKind::NotFound {
|
||||
return Err(e.into());
|
||||
} else {
|
||||
|
@ -97,7 +98,6 @@ pub fn root_command(cmd: Command, params: CliParams) -> anyhow::Result<()> {
|
|||
let download_job = project.download(&resolved_versions_map)?;
|
||||
|
||||
multithreaded_bar(
|
||||
¶ms,
|
||||
download_job,
|
||||
resolved_versions_map.len() as u64,
|
||||
"Downloading packages".to_string(),
|
||||
|
@ -111,8 +111,6 @@ pub fn root_command(cmd: Command, params: CliParams) -> anyhow::Result<()> {
|
|||
)?;
|
||||
}
|
||||
Command::Run { package, args } => {
|
||||
let project = get_project(¶ms)?;
|
||||
|
||||
let lockfile = project
|
||||
.lockfile()?
|
||||
.ok_or(anyhow::anyhow!("lockfile not found"))?;
|
||||
|
@ -145,10 +143,10 @@ pub fn root_command(cmd: Command, params: CliParams) -> anyhow::Result<()> {
|
|||
))?;
|
||||
}
|
||||
Command::Search { query } => {
|
||||
let config = params.index.config()?;
|
||||
let config = INDEX.config()?;
|
||||
let api_url = config.api();
|
||||
|
||||
let response = send_request(params.reqwest_client.get(Url::parse_with_params(
|
||||
let response = send_request(REQWEST_CLIENT.get(Url::parse_with_params(
|
||||
&format!("{}/v0/search", api_url),
|
||||
&query.map(|q| vec![("query", q)]).unwrap_or_default(),
|
||||
)?))?
|
||||
|
@ -171,8 +169,6 @@ pub fn root_command(cmd: Command, params: CliParams) -> anyhow::Result<()> {
|
|||
}
|
||||
}
|
||||
Command::Publish => {
|
||||
let project = get_project(¶ms)?;
|
||||
|
||||
if project.manifest().private {
|
||||
anyhow::bail!("package is private, cannot publish");
|
||||
}
|
||||
|
@ -180,9 +176,11 @@ pub fn root_command(cmd: Command, params: CliParams) -> anyhow::Result<()> {
|
|||
let encoder = GzEncoder::new(vec![], Compression::default());
|
||||
let mut archive = TarBuilder::new(encoder);
|
||||
|
||||
let mut walk_builder = WalkBuilder::new(¶ms.cwd);
|
||||
let cwd = &CWD.to_path_buf();
|
||||
|
||||
let mut walk_builder = WalkBuilder::new(cwd);
|
||||
walk_builder.add_custom_ignore_filename(".pesdeignore");
|
||||
let mut overrides = OverrideBuilder::new(¶ms.cwd);
|
||||
let mut overrides = OverrideBuilder::new(cwd);
|
||||
|
||||
for packages_folder in IGNORED_FOLDERS {
|
||||
overrides.add(&format!("!{}", packages_folder))?;
|
||||
|
@ -193,7 +191,7 @@ pub fn root_command(cmd: Command, params: CliParams) -> anyhow::Result<()> {
|
|||
for entry in walk_builder.build() {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
let relative_path = path.strip_prefix(¶ms.cwd)?;
|
||||
let relative_path = path.strip_prefix(cwd)?;
|
||||
let entry_type = entry
|
||||
.file_type()
|
||||
.ok_or(anyhow::anyhow!("failed to get file type"))?;
|
||||
|
@ -222,8 +220,7 @@ pub fn root_command(cmd: Command, params: CliParams) -> anyhow::Result<()> {
|
|||
.file_name("tarball.tar.gz")
|
||||
.mime_str("application/gzip")?;
|
||||
|
||||
let mut request = params
|
||||
.reqwest_client
|
||||
let mut request = REQWEST_CLIENT
|
||||
.post(format!("{}/v0/packages", project.index().config()?.api()))
|
||||
.multipart(reqwest::blocking::multipart::Form::new().part("tarball", part));
|
||||
|
||||
|
@ -236,8 +233,6 @@ pub fn root_command(cmd: Command, params: CliParams) -> anyhow::Result<()> {
|
|||
println!("{}", send_request(request)?.text()?);
|
||||
}
|
||||
Command::Patch { package } => {
|
||||
let project = get_project(¶ms)?;
|
||||
|
||||
let lockfile = project
|
||||
.lockfile()?
|
||||
.ok_or(anyhow::anyhow!("lockfile not found"))?;
|
||||
|
@ -247,7 +242,7 @@ pub fn root_command(cmd: Command, params: CliParams) -> anyhow::Result<()> {
|
|||
.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!(
|
||||
let dir = DIRS.data_dir().join("patches").join(format!(
|
||||
"{}_{}",
|
||||
package.0.escaped(),
|
||||
package.1
|
||||
|
@ -273,8 +268,6 @@ pub fn root_command(cmd: Command, params: CliParams) -> anyhow::Result<()> {
|
|||
println!("done! modify the files in {} and run `{} patch-commit <DIRECTORY>` to commit the changes", dir.display(), env!("CARGO_BIN_NAME"));
|
||||
}
|
||||
Command::PatchCommit { dir } => {
|
||||
let project = get_project(¶ms)?;
|
||||
|
||||
let manifest = Manifest::from_path(&dir)?;
|
||||
let patch_path = project.path().join(PATCHES_FOLDER).join(format!(
|
||||
"{}@{}.patch",
|
||||
|
@ -301,13 +294,13 @@ pub fn root_command(cmd: Command, params: CliParams) -> anyhow::Result<()> {
|
|||
);
|
||||
}
|
||||
Command::Init => {
|
||||
let manifest_path = params.cwd.join(MANIFEST_FILE_NAME);
|
||||
let manifest_path = CWD.join(MANIFEST_FILE_NAME);
|
||||
|
||||
if manifest_path.exists() {
|
||||
anyhow::bail!("manifest already exists");
|
||||
}
|
||||
|
||||
let default_name = params.cwd.file_name().and_then(|s| s.to_str());
|
||||
let default_name = CWD.file_name().and_then(|s| s.to_str());
|
||||
|
||||
let mut name =
|
||||
Text::new("What is the name of the package?").with_validator(|name: &str| {
|
||||
|
@ -380,8 +373,6 @@ pub fn root_command(cmd: Command, params: CliParams) -> anyhow::Result<()> {
|
|||
realm,
|
||||
peer,
|
||||
} => {
|
||||
let project = get_project(¶ms)?;
|
||||
|
||||
let mut manifest = project.manifest().clone();
|
||||
|
||||
let specifier = DependencySpecifier::Registry(RegistryDependencySpecifier {
|
||||
|
@ -402,8 +393,6 @@ pub fn root_command(cmd: Command, params: CliParams) -> anyhow::Result<()> {
|
|||
)?;
|
||||
}
|
||||
Command::Remove { package } => {
|
||||
let project = get_project(¶ms)?;
|
||||
|
||||
let mut manifest = project.manifest().clone();
|
||||
|
||||
for dependencies in [&mut manifest.dependencies, &mut manifest.peer_dependencies] {
|
||||
|
@ -422,8 +411,6 @@ pub fn root_command(cmd: Command, params: CliParams) -> anyhow::Result<()> {
|
|||
)?;
|
||||
}
|
||||
Command::Outdated => {
|
||||
let project = get_project(¶ms)?;
|
||||
|
||||
let manifest = project.manifest();
|
||||
let dependency_tree = manifest.dependency_tree(&project, false)?;
|
||||
|
||||
|
@ -434,7 +421,7 @@ pub fn root_command(cmd: Command, params: CliParams) -> anyhow::Result<()> {
|
|||
}
|
||||
|
||||
if let PackageRef::Registry(registry) = resolved_pkg.pkg_ref {
|
||||
let latest_version = send_request(params.reqwest_client.get(format!(
|
||||
let latest_version = send_request(REQWEST_CLIENT.get(format!(
|
||||
"{}/v0/packages/{}/{}/versions",
|
||||
project.index().config()?.api(),
|
||||
registry.name.scope(),
|
||||
|
@ -459,7 +446,7 @@ pub fn root_command(cmd: Command, params: CliParams) -> anyhow::Result<()> {
|
|||
}
|
||||
}
|
||||
Command::Convert => {
|
||||
Manifest::from_path_or_convert(¶ms.cwd)?;
|
||||
Manifest::from_path_or_convert(CWD.to_path_buf())?;
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
|
|
@ -152,7 +152,7 @@ impl GitPackageRef {
|
|||
}
|
||||
|
||||
repo.reset(&obj, git2::ResetType::Hard, None)?;
|
||||
|
||||
|
||||
Manifest::from_path_or_convert(dest)?;
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -163,7 +163,9 @@ impl<I: Index> Project<I> {
|
|||
|
||||
let project = self.clone();
|
||||
|
||||
job.execute(&tx, move || resolved_package.pkg_ref.download(&project, source));
|
||||
job.execute(&tx, move || {
|
||||
resolved_package.pkg_ref.download(&project, source)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
288
src/main.rs
288
src/main.rs
|
@ -1,289 +1,17 @@
|
|||
use anyhow::bail;
|
||||
use std::{
|
||||
fs::{create_dir_all, read},
|
||||
hash::{DefaultHasher, Hash, Hasher},
|
||||
path::PathBuf,
|
||||
str::FromStr,
|
||||
};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use auth_git2::GitAuthenticator;
|
||||
use clap::{Parser, Subcommand};
|
||||
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::{
|
||||
blocking::{RequestBuilder, Response},
|
||||
header::{ACCEPT, AUTHORIZATION},
|
||||
};
|
||||
use semver::{Version, VersionReq};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use cli::{auth::auth_command, config::config_command, root::root_command};
|
||||
|
||||
use cli::{
|
||||
auth::{auth_command, AuthCommand},
|
||||
config::{config_command, ConfigCommand},
|
||||
root::root_command,
|
||||
};
|
||||
use pesde::{index::GitIndex, manifest::Realm, package_name::PackageName};
|
||||
use crate::cli::{CliConfig, Command, CLI, MULTI};
|
||||
|
||||
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: 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>,
|
||||
},
|
||||
|
||||
/// Publishes the project to the registry
|
||||
Publish,
|
||||
|
||||
/// Converts a `wally.toml` file to a `pesde.yaml` file
|
||||
Convert,
|
||||
|
||||
/// Begins a new patch
|
||||
Patch {
|
||||
/// The package to patch
|
||||
#[clap(value_name = "PACKAGE")]
|
||||
package: VersionedPackageName<Version>,
|
||||
},
|
||||
|
||||
/// Commits (finishes) the patch
|
||||
PatchCommit {
|
||||
/// The package's changed directory
|
||||
#[clap(value_name = "DIRECTORY")]
|
||||
dir: PathBuf,
|
||||
},
|
||||
|
||||
/// Auth-related commands
|
||||
Auth {
|
||||
#[clap(subcommand)]
|
||||
command: AuthCommand,
|
||||
},
|
||||
|
||||
/// Config-related commands
|
||||
Config {
|
||||
#[clap(subcommand)]
|
||||
command: ConfigCommand,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
struct Cli {
|
||||
#[clap(subcommand)]
|
||||
command: Command,
|
||||
|
||||
/// The directory to run the command in
|
||||
#[arg(short, long, value_name = "DIRECTORY")]
|
||||
directory: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
struct CliConfig {
|
||||
index_repo_url: String,
|
||||
cache_dir: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl CliConfig {
|
||||
fn cache_dir(&self, directories: &ProjectDirs) -> PathBuf {
|
||||
self.cache_dir
|
||||
.clone()
|
||||
.unwrap_or_else(|| directories.cache_dir().to_path_buf())
|
||||
}
|
||||
}
|
||||
|
||||
struct CliParams {
|
||||
index: GitIndex,
|
||||
api_token_entry: Entry,
|
||||
reqwest_client: reqwest::blocking::Client,
|
||||
cli_config: CliConfig,
|
||||
cwd: PathBuf,
|
||||
multi: MultiProgress,
|
||||
directories: ProjectDirs,
|
||||
}
|
||||
|
||||
impl CliConfig {
|
||||
fn write(&self, directories: &ProjectDirs) -> anyhow::Result<()> {
|
||||
let cli_config_path = directories.config_dir().join("config.yaml");
|
||||
serde_yaml::to_writer(
|
||||
&mut std::fs::File::create(cli_config_path.as_path())?,
|
||||
&self,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_request(request_builder: RequestBuilder) -> anyhow::Result<Response> {
|
||||
let res = request_builder.send()?;
|
||||
|
||||
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<()> {
|
||||
let logger = pretty_env_logger::formatted_builder()
|
||||
.parse_env(Env::default().default_filter_or("info"))
|
||||
.build();
|
||||
let multi = MultiProgress::new();
|
||||
Lazy::force(&MULTI);
|
||||
|
||||
LogWrapper::new(multi.clone(), logger).try_init().unwrap();
|
||||
|
||||
let cli = Cli::parse();
|
||||
|
||||
let directories = ProjectDirs::from("com", env!("CARGO_BIN_NAME"), env!("CARGO_BIN_NAME"))
|
||||
.expect("couldn't get home directory");
|
||||
|
||||
let cli_config_path = directories.config_dir().join("config.yaml");
|
||||
let cli_config = if cli_config_path.exists() {
|
||||
serde_yaml::from_slice(&read(cli_config_path.as_path())?)?
|
||||
} else {
|
||||
let config = CliConfig {
|
||||
index_repo_url: "https://github.com/daimond113/pesde-index".to_string(),
|
||||
cache_dir: None,
|
||||
};
|
||||
create_dir_all(directories.config_dir())?;
|
||||
config.write(&directories)?;
|
||||
config
|
||||
};
|
||||
|
||||
let cwd_buf = cli
|
||||
.directory
|
||||
.or(std::env::current_dir().ok())
|
||||
.ok_or(anyhow::anyhow!("couldn't get current directory"))?;
|
||||
|
||||
let api_token_entry = Entry::new(env!("CARGO_BIN_NAME"), "api_token")?;
|
||||
|
||||
let mut hasher = DefaultHasher::new();
|
||||
cli_config.index_repo_url.hash(&mut hasher);
|
||||
let hash = hasher.finish().to_string();
|
||||
|
||||
let index = GitIndex::new(
|
||||
cli_config.cache_dir(&directories).join("index").join(hash),
|
||||
&cli_config.index_repo_url,
|
||||
Some(Box::new(|| {
|
||||
Box::new(|a, b, c| {
|
||||
let git_authenticator = GitAuthenticator::new();
|
||||
let config = git2::Config::open_default().unwrap();
|
||||
let mut cred = git_authenticator.credentials(&config);
|
||||
|
||||
cred(a, b, c)
|
||||
})
|
||||
})),
|
||||
);
|
||||
index.refresh()?;
|
||||
|
||||
let mut header_map = reqwest::header::HeaderMap::new();
|
||||
header_map.insert(ACCEPT, "application/json".parse()?);
|
||||
header_map.insert("X-GitHub-Api-Version", "2022-11-28".parse()?);
|
||||
|
||||
match api_token_entry.get_password() {
|
||||
Ok(api_token) => {
|
||||
header_map.insert(AUTHORIZATION, format!("Bearer {api_token}").parse()?);
|
||||
}
|
||||
Err(err) => match err {
|
||||
keyring::Error::NoEntry => {}
|
||||
_ => {
|
||||
bail!("error getting api token from keyring: {err}")
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let reqwest_client = reqwest::blocking::Client::builder()
|
||||
.user_agent(concat!(
|
||||
env!("CARGO_PKG_NAME"),
|
||||
"/",
|
||||
env!("CARGO_PKG_VERSION")
|
||||
))
|
||||
.default_headers(header_map)
|
||||
.build()?;
|
||||
|
||||
let params = CliParams {
|
||||
index,
|
||||
api_token_entry,
|
||||
reqwest_client,
|
||||
cli_config,
|
||||
cwd: cwd_buf,
|
||||
multi,
|
||||
directories,
|
||||
};
|
||||
|
||||
match cli.command {
|
||||
Command::Auth { command } => auth_command(command, params),
|
||||
Command::Config { command } => config_command(command, params),
|
||||
cmd => root_command(cmd, params),
|
||||
match CLI.command.clone() {
|
||||
Command::Auth { command } => auth_command(command),
|
||||
Command::Config { command } => config_command(command),
|
||||
cmd => root_command(cmd),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,10 +13,7 @@ impl<E: Send + Sync + 'static> MultithreadedJob<E> {
|
|||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
let pool = ThreadPool::new(6);
|
||||
|
||||
(Self {
|
||||
progress: rx,
|
||||
pool,
|
||||
}, tx)
|
||||
(Self { progress: rx, pool }, tx)
|
||||
}
|
||||
|
||||
/// Returns the progress of the job
|
||||
|
@ -41,7 +38,7 @@ impl<E: Send + Sync + 'static> MultithreadedJob<E> {
|
|||
F: (FnOnce() -> Result<(), E>) + Send + 'static,
|
||||
{
|
||||
let sender = tx.clone();
|
||||
|
||||
|
||||
self.pool.execute(move || {
|
||||
let result = f();
|
||||
sender.send(result).unwrap();
|
||||
|
|
Loading…
Reference in a new issue