Skip to content

Commit

Permalink
new: Add proto outdated command. (#222)
Browse files Browse the repository at this point in the history
  • Loading branch information
milesj committed Sep 29, 2023
1 parent f82c007 commit 7ad3fa6
Show file tree
Hide file tree
Showing 11 changed files with 174 additions and 29 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

#### 🚀 Updates

- Added a `proto outdated` command that'll check for new versions of configured tools.
- Added a `proto pin` command, which is a merge of the old `proto global` and `proto local` commands.
- Added a `pin-latest` setting to `~/.proto/config.toml` that'll automatically pin tools when they're being installed with the "latest" version.
- Updated `proto install` to auto-clean stale plugins after a successful installation.
Expand Down
10 changes: 8 additions & 2 deletions crates/cli/src/app.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::commands::{
AddPluginArgs, AliasArgs, BinArgs, CleanArgs, CompletionsArgs, InstallArgs, InstallGlobalArgs,
ListArgs, ListGlobalArgs, ListRemoteArgs, PinArgs, PluginsArgs, RemovePluginArgs, RunArgs,
SetupArgs, ToolsArgs, UnaliasArgs, UninstallArgs, UninstallGlobalArgs,
ListArgs, ListGlobalArgs, ListRemoteArgs, OutdatedArgs, PinArgs, PluginsArgs, RemovePluginArgs,
RunArgs, SetupArgs, ToolsArgs, UnaliasArgs, UninstallArgs, UninstallGlobalArgs,
};
use clap::builder::styling::{Color, Style, Styles};
use clap::{Parser, Subcommand, ValueEnum};
Expand Down Expand Up @@ -155,6 +155,12 @@ pub enum Commands {
)]
ListRemote(ListRemoteArgs),

#[command(
name = "outdated",
about = "Check if configured tool versions are out of date."
)]
Outdated(OutdatedArgs),

#[command(
alias = "p",
name = "pin",
Expand Down
6 changes: 1 addition & 5 deletions crates/cli/src/commands/add_plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ use clap::Args;
use proto_core::{Id, PluginLocator, ToolsConfig, UserConfig};
use starbase::system;
use starbase_styles::color;
use std::env;
use std::path::PathBuf;
use tracing::info;

#[derive(Args, Clone, Debug)]
Expand Down Expand Up @@ -39,9 +37,7 @@ pub async fn add_plugin(args: ArgsRef<AddPluginArgs>) {
return Ok(());
}

let local_path = env::current_dir().unwrap_or_else(|_| PathBuf::from("."));

let mut config = ToolsConfig::load_from(local_path)?;
let mut config = ToolsConfig::load()?;
config.plugins.insert(args.id.clone(), args.plugin.clone());
config.save()?;

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 @@ -9,6 +9,7 @@ mod install_global;
mod list;
mod list_global;
mod list_remote;
mod outdated;
mod pin;
mod plugins;
mod remove_plugin;
Expand All @@ -31,6 +32,7 @@ pub use install_global::*;
pub use list::*;
pub use list_global::*;
pub use list_remote::*;
pub use outdated::*;
pub use pin::*;
pub use plugins::*;
pub use remove_plugin::*;
Expand Down
126 changes: 126 additions & 0 deletions crates/cli/src/commands/outdated.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
use clap::Args;
use miette::IntoDiagnostic;
use proto_core::{load_tool, ToolsConfig, UnresolvedVersionSpec, VersionSpec};
use serde::Serialize;
use starbase::system;
use starbase_styles::color::{self, OwoStyle};
use starbase_utils::json;
use std::collections::HashMap;
use std::process;
use tracing::{debug, info};

#[derive(Args, Clone, Debug)]
pub struct OutdatedArgs {
#[arg(long, help = "Print the list in JSON format")]
json: bool,

#[arg(
long,
help = "Check for latest available version ignoring requirements and ranges"
)]
latest: bool,

#[arg(long, help = "Update and write the versions to the local .prototools")]
update: bool,
}

#[derive(Serialize)]
pub struct OutdatedItem {
is_latest: bool,
version_config: UnresolvedVersionSpec,
current_version: VersionSpec,
newer_version: VersionSpec,
}

#[system]
pub async fn outdated(args: ArgsRef<OutdatedArgs>) {
let mut tools_config = ToolsConfig::load_closest()?;
let initial_version = UnresolvedVersionSpec::default(); // latest

if tools_config.tools.is_empty() {
eprintln!("No configured tools in .prototools");
process::exit(1);
}

if !args.json {
info!("Checking for newer versions...");
}

let mut items = HashMap::new();
let mut tool_versions = HashMap::new();

for (tool_id, config_version) in &tools_config.tools {
let mut tool = load_tool(tool_id).await?;
tool.disable_caching();

debug!("Checking {}", tool.get_name());

let mut comments = vec![];
let versions = tool.load_version_resolver(&initial_version).await?;
let current_version = versions.resolve(config_version)?;
let is_latest = args.latest || matches!(config_version, UnresolvedVersionSpec::Version(_));

comments.push(format!(
"current version {} {}",
color::symbol(current_version.to_string()),
color::muted_light(format!("(via {})", config_version))
));

let newer_version = versions.resolve_without_manifest(if is_latest {
&initial_version // latest alias
} else {
config_version // req, range, etc
})?;

comments.push(format!(
"{} {}",
if is_latest {
"latest version"
} else {
"newer version"
},
color::symbol(newer_version.to_string())
));

let is_outdated = match (&current_version, &newer_version) {
(VersionSpec::Version(a), VersionSpec::Version(b)) => b > a,
_ => false,
};

if is_outdated {
comments.push(color::success("update available!"));
}

if args.update {
tool_versions.insert(tool.id.clone(), newer_version.to_unresolved_spec());
}

if args.json {
items.insert(
tool.id,
OutdatedItem {
is_latest,
version_config: config_version.to_owned(),
current_version,
newer_version,
},
);
} else {
println!(
"{} {} {}",
OwoStyle::new().bold().style(color::id(&tool.id)),
color::muted("-"),
comments.join(&color::muted_light(", "))
);
}
}

if args.update {
tools_config.tools.extend(tool_versions);
tools_config.save()?;
}

if args.json {
println!("{}", json::to_string_pretty(&items).into_diagnostic()?);
}
}
7 changes: 1 addition & 6 deletions crates/cli/src/commands/pin.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
use std::env;
use std::path::PathBuf;

use clap::Args;
use proto_core::{load_tool, Id, Tool, ToolsConfig, UnresolvedVersionSpec};
use starbase::{system, SystemResult};
Expand Down Expand Up @@ -33,9 +30,7 @@ pub fn internal_pin(tool: &mut Tool, args: &PinArgs) -> SystemResult {
"Wrote the global version",
);
} else {
let local_path = env::current_dir().unwrap_or_else(|_| PathBuf::from("."));

let mut config = ToolsConfig::load_from(local_path)?;
let mut config = ToolsConfig::load()?;
config.tools.insert(args.id.clone(), args.spec.clone());
config.save()?;

Expand Down
1 change: 1 addition & 0 deletions crates/cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ async fn main() -> MainResult {
Commands::List(args) => app.execute_with_args(commands::list, args),
Commands::ListGlobal(args) => app.execute_with_args(commands::list_global, args),
Commands::ListRemote(args) => app.execute_with_args(commands::list_remote, args),
Commands::Outdated(args) => app.execute_with_args(commands::outdated, args),
Commands::Pin(args) => app.execute_with_args(commands::pin, args),
Commands::Plugins(args) => app.execute_with_args(commands::plugins, args),
Commands::RemovePlugin(args) => app.execute_with_args(commands::remove_plugin, args),
Expand Down
2 changes: 1 addition & 1 deletion crates/cli/tests/add_plugin_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ mod add_plugin {

assert!(config_file.exists());

let manifest = ToolsConfig::load(config_file).unwrap();
let manifest = ToolsConfig::load_from(sandbox.path()).unwrap();

assert_eq!(
manifest.plugins,
Expand Down
38 changes: 24 additions & 14 deletions crates/core/src/tools_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,23 +40,25 @@ impl ToolsConfig {
}
}

pub fn load_from<P: AsRef<Path>>(dir: P) -> miette::Result<Self> {
Self::load(dir.as_ref().join(TOOLS_CONFIG_NAME))
#[tracing::instrument(skip_all)]
pub fn load() -> miette::Result<Self> {
let working_dir = env::current_dir().unwrap_or_else(|_| PathBuf::from("."));

Self::load_from(working_dir)
}

#[tracing::instrument(skip_all)]
pub fn load<P: AsRef<Path>>(path: P) -> miette::Result<Self> {
let path = path.as_ref();
pub fn load_from<P: AsRef<Path>>(dir: P) -> miette::Result<Self> {
let path = dir.as_ref().join(TOOLS_CONFIG_NAME);

let mut config: ToolsConfig = if path.exists() {
debug!(file = ?path, "Loading {}", TOOLS_CONFIG_NAME);

toml::from_str(&fs::read_file_with_lock(path)?).into_diagnostic()?
toml::from_str(&fs::read_file_with_lock(&path)?).into_diagnostic()?
} else {
ToolsConfig::default()
};

config.path = path.to_owned();
config.path = path.clone();

// Update plugin file paths to be absolute
for locator in config.plugins.values_mut() {
Expand All @@ -72,29 +74,37 @@ impl ToolsConfig {
Ok(config)
}

pub fn load_closest() -> miette::Result<Self> {
let working_dir = env::current_dir().expect("Unknown current working directory!");

Self::load_upwards_from(working_dir, true)
}

pub fn load_upwards() -> miette::Result<Self> {
let working_dir = env::current_dir().expect("Unknown current working directory!");

Self::load_upwards_from(working_dir)
Self::load_upwards_from(working_dir, false)
}

pub fn load_upwards_from<P>(starting_dir: P) -> miette::Result<Self>
pub fn load_upwards_from<P>(starting_dir: P, stop_at_first: bool) -> miette::Result<Self>
where
P: AsRef<Path>,
{
trace!("Traversing upwards and loading all .prototools files");
trace!("Traversing upwards and loading .prototools files");

let mut current_dir = Some(starting_dir.as_ref());
let mut config = ToolsConfig::default();

while let Some(dir) = current_dir {
let path = dir.join(TOOLS_CONFIG_NAME);

if path.exists() {
let mut parent_config = Self::load(&path)?;
if dir.join(TOOLS_CONFIG_NAME).exists() {
let mut parent_config = Self::load_from(dir)?;
parent_config.merge(config);

config = parent_config;

if stop_at_first {
break;
}
}

match dir.parent() {
Expand Down
7 changes: 7 additions & 0 deletions crates/core/src/version_resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@ impl<'tool> VersionResolver<'tool> {
pub fn resolve(&self, candidate: &UnresolvedVersionSpec) -> miette::Result<VersionSpec> {
resolve_version(candidate, &self.versions, &self.aliases, self.manifest)
}

pub fn resolve_without_manifest(
&self,
candidate: &UnresolvedVersionSpec,
) -> miette::Result<VersionSpec> {
resolve_version(candidate, &self.versions, &self.aliases, None)
}
}

pub fn match_highest_version<'l, I>(req: &'l VersionReq, versions: I) -> Option<VersionSpec>
Expand Down
3 changes: 2 additions & 1 deletion crates/core/tests/tools_config_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,8 @@ foo = "source:./test.toml"
"#,
);

let config = ToolsConfig::load_upwards_from(sandbox.path().join("one/two/three")).unwrap();
let config =
ToolsConfig::load_upwards_from(sandbox.path().join("one/two/three"), false).unwrap();

assert_eq!(
config.tools,
Expand Down

0 comments on commit 7ad3fa6

Please sign in to comment.