Skip to content

Commit

Permalink
new: Add proto tools command. (#206)
Browse files Browse the repository at this point in the history
  • Loading branch information
milesj authored Sep 16, 2023
1 parent 25b9e4a commit 896b445
Show file tree
Hide file tree
Showing 9 changed files with 243 additions and 49 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

#### 🚀 Updates

- Added a `proto tools` command for listing all installed tools and their versions.
- Added an `http` setting to `~/.proto/config.toml` to control proxies and certificates when making http/https requests, primarily for downloading tools.
- New `allow-invalid-certs` setting for allowing invalid certificates (be careful).
- New `proxies` setting for customizing internal proxy URLs.
Expand Down
5 changes: 3 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ proto_core = { version = "0.17.4", path = "../core" }
proto_pdk_api = { version = "0.7.1", path = "../pdk-api" }
proto_schema_plugin = { version = "0.11.4", path = "../schema-plugin" }
proto_wasm_plugin = { version = "0.6.5", path = "../wasm-plugin" }
chrono = "0.4.31"
clap = { workspace = true, features = ["derive", "env"] }
clap_complete = { workspace = true }
convert_case = { workspace = true }
Expand Down
6 changes: 5 additions & 1 deletion crates/cli/src/app.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use crate::commands::{
AddPluginArgs, AliasArgs, BinArgs, CleanArgs, CompletionsArgs, GlobalArgs, InstallArgs,
InstallGlobalArgs, ListArgs, ListGlobalArgs, ListRemoteArgs, LocalArgs, PluginsArgs,
RemovePluginArgs, RunArgs, SetupArgs, UnaliasArgs, UninstallArgs, UninstallGlobalArgs,
RemovePluginArgs, RunArgs, SetupArgs, ToolsArgs, UnaliasArgs, UninstallArgs,
UninstallGlobalArgs,
};
use clap::builder::styling::{Color, Style, Styles};
use clap::{Parser, Subcommand, ValueEnum};
Expand Down Expand Up @@ -191,6 +192,9 @@ pub enum Commands {
#[command(name = "setup", about = "Setup proto for your current shell.")]
Setup(SetupArgs),

#[command(name = "tools", about = "List all installed tools and their versions.")]
Tools(ToolsArgs),

#[command(alias = "ua", name = "unalias", about = "Remove an alias from a tool.")]
Unalias(UnaliasArgs),

Expand Down
2 changes: 2 additions & 0 deletions crates/cli/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ mod plugins;
mod remove_plugin;
mod run;
mod setup;
mod tools;
mod unalias;
mod uninstall;
mod uninstall_global;
Expand All @@ -37,6 +38,7 @@ pub use plugins::*;
pub use remove_plugin::*;
pub use run::*;
pub use setup::*;
pub use tools::*;
pub use unalias::*;
pub use uninstall::*;
pub use uninstall_global::*;
Expand Down
96 changes: 51 additions & 45 deletions crates/cli/src/commands/plugins.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,14 @@
use crate::helpers::load_configured_tools;
use clap::Args;
use miette::IntoDiagnostic;
use proto_core::{
load_tool_from_locator, Id, PluginLocator, ProtoEnvironment, ToolsConfig, UserConfig,
};
use proto_core::{Id, PluginLocator};
use serde::Serialize;
use starbase::system;
use starbase_styles::color;
use starbase_styles::color::{self, OwoStyle};
use starbase_utils::json;
use std::collections::HashMap;
use tracing::debug;

fn render_entry<V: AsRef<str>>(label: &str, value: V) {
println!(
" {} {}",
color::muted_light(format!("{label}:")),
value.as_ref()
);
}
use std::collections::HashSet;
use std::io::{BufWriter, Write};
use tracing::info;

#[derive(Serialize)]
pub struct PluginItem {
Expand All @@ -34,30 +26,21 @@ pub struct PluginsArgs {

#[system]
pub async fn plugins(args: ArgsRef<PluginsArgs>) {
let proto = ProtoEnvironment::new()?;
let user_config = UserConfig::load()?;

let mut tools_config = ToolsConfig::load_upwards()?;
tools_config.inherit_builtin_plugins();

let mut plugins = HashMap::new();
plugins.extend(&user_config.plugins);
plugins.extend(&tools_config.plugins);

debug!("Loading plugins");
if !args.json {
info!("Loading plugins...");
}

let mut items = vec![];

for (id, locator) in plugins {
let tool = load_tool_from_locator(&id, &proto, &locator, &user_config).await?;

load_configured_tools(HashSet::new(), |tool, locator| {
items.push(PluginItem {
id: id.to_owned(),
locator: locator.to_owned(),
id: tool.id.to_owned(),
locator,
name: tool.metadata.name,
version: tool.metadata.plugin_version,
});
}
})
.await?;

items.sort_by(|a, d| a.id.cmp(&d.id));

Expand All @@ -67,36 +50,59 @@ pub async fn plugins(args: ArgsRef<PluginsArgs>) {
return Ok(());
}

let stdout = std::io::stdout();
let mut buffer = BufWriter::new(stdout.lock());

for item in items {
println!(
"{} {} {} {}",
color::id(item.id),
writeln!(
buffer,
"{} {} {}",
OwoStyle::new().bold().style(color::id(item.id)),
color::muted("-"),
item.name,
color::muted_light(if let Some(version) = item.version {
format!("v{version}")
format!("{} v{version}", item.name)
} else {
"".into()
item.name
})
);
)
.unwrap();

match item.locator {
PluginLocator::SourceFile { path, .. } => {
render_entry("Source", color::path(path.canonicalize().unwrap()));
writeln!(
buffer,
" Source: {}",
color::path(path.canonicalize().unwrap())
)
.unwrap();
}
PluginLocator::SourceUrl { url } => {
render_entry("Source", color::url(url));
writeln!(buffer, " Source: {}", color::url(url)).unwrap();
}
PluginLocator::GitHub(github) => {
render_entry("GitHub", color::label(&github.repo_slug));
render_entry("Tag", github.tag.as_deref().unwrap_or("latest"));
writeln!(buffer, " GitHub: {}", color::label(&github.repo_slug)).unwrap();

writeln!(
buffer,
" Tag: {}",
color::hash(github.tag.as_deref().unwrap_or("latest")),
)
.unwrap();
}
PluginLocator::Wapm(wapm) => {
render_entry("Package", color::label(&wapm.package_name));
render_entry("Version", wapm.version.as_deref().unwrap_or("latest"));
writeln!(buffer, " Package: {}", color::label(&wapm.package_name)).unwrap();

writeln!(
buffer,
" Version: {}",
color::hash(wapm.version.as_deref().unwrap_or("latest")),
)
.unwrap();
}
};

println!();
writeln!(buffer).unwrap();
}

buffer.flush().unwrap();
}
146 changes: 146 additions & 0 deletions crates/cli/src/commands/tools.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
use crate::helpers::load_configured_tools;
use chrono::{DateTime, NaiveDateTime};
use clap::Args;
use miette::IntoDiagnostic;
use proto_core::Id;
use starbase::system;
use starbase_styles::color::{self, OwoStyle};
use starbase_utils::json;
use std::collections::{HashMap, HashSet};
use std::io::{BufWriter, Write};
use std::process;
use tracing::info;

#[derive(Args, Clone, Debug)]
pub struct ToolsArgs {
#[arg(help = "IDs of tool to list")]
id: Vec<Id>,

#[arg(long, help = "Print the list in JSON format")]
json: bool,
}

#[system]
pub async fn tools(args: ArgsRef<ToolsArgs>) {
if !args.json {
info!("Loading tools...");
}

let mut tools = vec![];

load_configured_tools(HashSet::from_iter(&args.id), |tool, _| {
if !tool.manifest.installed_versions.is_empty() {
tools.push(tool);
}
})
.await?;

tools.sort_by(|a, d| a.id.cmp(&d.id));

if tools.is_empty() {
eprintln!("No installed tools");
process::exit(1);
}

if args.json {
let items = tools
.into_iter()
.map(|t| (t.id, t.manifest))
.collect::<HashMap<_, _>>();

println!("{}", json::to_string_pretty(&items).into_diagnostic()?);

return Ok(());
}

let stdout = std::io::stdout();
let mut buffer = BufWriter::new(stdout.lock());

for tool in tools {
writeln!(
buffer,
"{} {} {}",
OwoStyle::new().bold().style(color::id(&tool.id)),
color::muted("-"),
color::muted_light(&tool.metadata.name),
)
.unwrap();

writeln!(buffer, " Store: {}", color::path(tool.get_inventory_dir())).unwrap();

if !tool.manifest.aliases.is_empty() {
writeln!(buffer, " Aliases:").unwrap();

for (alias, version) in &tool.manifest.aliases {
writeln!(
buffer,
" {} {} {}",
color::hash(version.to_string()),
color::muted("="),
color::label(alias),
)
.unwrap();
}
}

if !tool.manifest.installed_versions.is_empty() {
writeln!(buffer, " Versions:").unwrap();

let mut versions = tool.manifest.installed_versions.iter().collect::<Vec<_>>();
versions.sort();

for version in versions {
let mut comments = vec![];
let mut is_default = false;

if let Some(meta) = &tool.manifest.versions.get(version) {
if let Some(at) = create_datetime(meta.installed_at) {
comments.push(format!("installed {}", at.format("%x")));
}

if let Some(last_used) = &meta.last_used_at {
if let Some(at) = create_datetime(*last_used) {
comments.push(format!("last used {}", at.format("%x")));
}
}
}

if tool
.manifest
.default_version
.as_ref()
.is_some_and(|dv| dv == &version.to_unresolved_spec())
{
comments.push("default version".into());
is_default = true;
}

if comments.is_empty() {
writeln!(buffer, " {}", color::hash(version.to_string())).unwrap();
} else {
writeln!(
buffer,
" {} {} {}",
if is_default {
color::symbol(version.to_string())
} else {
color::hash(version.to_string())
},
color::muted("-"),
color::muted_light(comments.join(", "))
)
.unwrap();
}
}
}

writeln!(buffer).unwrap();
}

buffer.flush().unwrap();
}

fn create_datetime(millis: u128) -> Option<NaiveDateTime> {
DateTime::from_timestamp((millis / 1000) as i64, ((millis % 1000) * 1_000_000) as u32)
.map(|dt| dt.naive_local())
}
Loading

0 comments on commit 896b445

Please sign in to comment.