From 8b69c42f5ec7f3146381e6b6843e7134d640888d Mon Sep 17 00:00:00 2001 From: Michael Chernicoff <76788795+mchernicoff@users.noreply.github.com> Date: Thu, 30 May 2024 10:57:13 -0400 Subject: [PATCH] feat: Adds hc ready command (#81) Introduces a new `hc ready` command that checks if Hipcheck is ready to run. This is experimental for now, but is intended to make it easier to debug issues with Hipcheck installs in the future, similar to `brew doctor` for Homebrew. Signed-off-by: Andrew Lilley Brinker --------- Signed-off-by: Andrew Lilley Brinker Co-authored-by: Andrew Lilley Brinker --- hipcheck/src/cli.rs | 23 +++---- hipcheck/src/main.rs | 151 +++++++++++++++++++++++++++---------------- 2 files changed, 105 insertions(+), 69 deletions(-) diff --git a/hipcheck/src/cli.rs b/hipcheck/src/cli.rs index 881d3af0..973766dd 100644 --- a/hipcheck/src/cli.rs +++ b/hipcheck/src/cli.rs @@ -7,25 +7,13 @@ #[command(about, disable_help_flag=true, disable_version_flag=true, long_about=None)] pub struct Args { /// print help text - #[arg(name="extra-help", short = 'h', long = "help")] + #[arg(name = "extra-help", short = 'h', long = "help")] pub extra_help: bool, /// print version information #[arg(short = 'V', long, global = true)] pub version: bool, - /// print the home directory for Hipcheck - #[arg(long = "print-home", global = true)] - pub print_home: bool, - - /// print the config file path for Hipcheck - #[arg(long = "print-config", global = true)] - pub print_config: bool, - - /// print the data folder path for Hipcheck - #[arg(long = "print-data", global = true)] - pub print_data: bool, - /// silences progress reporting #[arg(short = 'q', long = "quiet", global = true)] pub verbosity: bool, @@ -71,6 +59,9 @@ pub enum Commands { /// Analyzes a repository or pull/merge request #[command(disable_help_subcommand = true)] Check(CheckArgs), + /// Check if Hipcheck is ready to execute and reports status to user + #[command(disable_help_subcommand = true)] + Ready, /// Print help information, optionally for a given subcommand #[command(disable_help_subcommand = true)] Help(HelpArgs), @@ -79,6 +70,12 @@ pub enum Commands { Schema(SchemaArgs), } +impl Default for Commands { + fn default() -> Commands { + Commands::Help(HelpArgs { command: None }) + } +} + #[derive(Debug, clap::Args)] pub struct CheckArgs { /// print help text diff --git a/hipcheck/src/main.rs b/hipcheck/src/main.rs index 48d4d816..0765fce3 100644 --- a/hipcheck/src/main.rs +++ b/hipcheck/src/main.rs @@ -31,6 +31,7 @@ use crate::analysis::session::Check; use crate::analysis::session::CheckType; use crate::analysis::session::Session; use crate::cli::Commands; +use crate::command_util::DependentProgram; use crate::context::Context as _; use crate::error::Error; use crate::error::Result; @@ -44,6 +45,7 @@ use clap::Parser as _; use cli::CheckCommand; use cli::HelpCommand; use cli::SchemaCommand; +use dotenv::var; use env_logger::Builder; use env_logger::Env; use schemars::schema_for; @@ -178,10 +180,6 @@ impl CliArgs { path.map(PathBuf::from) }; - if args.print_home { - print_home(home_dir.as_deref()); - } - // PANIC: Optional but has a default value, so unwrap() should never panic. let color_choice = { let color: &String = &args.color.unwrap(); @@ -193,19 +191,11 @@ impl CliArgs { config.map(PathBuf::from) }; - if args.print_config { - print_config(config_path.as_deref()); - } - let data_path = { let data: Option<&String> = args.data.as_ref(); data.map(PathBuf::from) }; - if args.print_data { - print_data(data_path.as_deref()); - } - let format = Format::use_json(args.json); // initialized later when the "check" subcommand is called @@ -217,6 +207,13 @@ impl CliArgs { Some(HelpCommand::Check) => print_check_help(), Some(HelpCommand::Schema) => print_schema_help(), }, + Some(Commands::Ready) => { + print_readiness( + home_dir.as_deref(), + config_path.as_deref(), + data_path.as_deref(), + ); + } Some(Commands::Check(args)) => { if args.extra_help { print_check_help(); @@ -307,9 +304,6 @@ impl CliArgs { const GLOBAL_HELP: &str = "\ FLAGS: -V, --version print version information - --print-config print the config file path for Hipcheck - --print-data print the data folder path for Hipcheck - --print-home print the home directory for Hipcheck OPTIONS (CONFIGURATION): -c, --config path to the configuration file [default: ./Hipcheck.toml] @@ -336,6 +330,7 @@ USAGE: TASKS: check analyzes a repository or pull/merge request + ready print a report of whether or not Hipcheck is ready to run (experimental) schema print the schema for JSON-format output for a specified subtarget help [] print help information, optionally for a given subcommand @@ -460,63 +455,107 @@ fn print_pypi_schema() -> ! { print_missing() } -/// Print the current home directory for Hipcheck. -/// -/// Exits `Ok` if home directory is specified, `Err` otherwise. -fn print_home(path: Option<&Path>) -> ! { - let hipcheck_home = resolve_home(path); - - let exit_code = match hipcheck_home { - Ok(path_buffer) => { - println!("{}", path_buffer.display()); - Outcome::Ok.exit_code() +/// Prints a "readiness report" for Hipcheck, indicating if any dependent programs are missing or our of date +/// or if any necessary folders or files are missing. It then returns a final readiness status. +fn print_readiness( + home_dir: Option<&Path>, + config_path: Option<&Path>, + data_path: Option<&Path>, +) -> ! { + let mut failed = false; + + // Print Hipcheck version + let raw_version = env!("CARGO_PKG_VERSION", "can't find Hipcheck package version"); + + let version_text = format!( + "{} {}", + env!("CARGO_PKG_NAME"), + version::get_version(raw_version).unwrap() + ); + println!("{}", version_text); + + // Check that git is installed and that its version is up to date + // Print the version number either way + let git_check = data::git::get_git_version(); + match git_check { + Ok(git_version) => match DependentProgram::Git.check_version(&git_version) { + // No need to check Boolean value, because currentl check_version() only returns Ok(true) or Err() + Ok(_) => print!("Found installed {}", git_version), + Err(err) => { + print_error(&err); + failed = true; + } + }, + Err(err) => { + print_error(&err); + failed = true; } + } + + // Check that git is installed and that its version is up to date + // Print the version number either way + let npm_check = data::npm::get_npm_version(); + match npm_check { + Ok(npm_version) => match DependentProgram::Npm.check_version(&npm_version) { + // No need to check Boolean value, because currently check_version() only returns Ok(true) or Err() + Ok(_) => print!("Found installed NPM version {}", npm_version), + Err(err) => { + print_error(&err); + failed = true; + } + }, Err(err) => { print_error(&err); - Outcome::Err.exit_code() + failed = true; } - }; + } - exit(exit_code); -} + // Check that the Hipcheck home folder is findable + let hipcheck_home = resolve_home(home_dir); + match hipcheck_home { + Ok(path_buffer) => println!("Hipcheck home directory: {}", path_buffer.display()), + Err(err) => { + failed = true; + print_error(&err); + } + } -/// Print the current config path for Hipcheck. -/// -/// Exits `Ok` if config path is specified, `Err` otherwise. -fn print_config(config_path: Option<&Path>) -> ! { + // Check that the Hipcheck config TOML exists in the designated location let hipcheck_config = resolve_config(config_path); - - let exit_code = match hipcheck_config { - Ok(path_buffer) => { - println!("{}", path_buffer.display()); - Outcome::Ok.exit_code() - } + match hipcheck_config { + Ok(path_buffer) => println!("Hipcheck config file: {}", path_buffer.display()), Err(err) => { + failed = true; print_error(&err); - Outcome::Err.exit_code() } - }; - - exit(exit_code); -} + } -/// Print the current data folder path for Hipcheck. -/// -/// Exits `Ok` if config path is specified, `Err` otherwise. -fn print_data(data_path: Option<&Path>) -> ! { + // Check that Hipcheck data folder is findable let hipcheck_data = resolve_data(data_path); - - let exit_code = match hipcheck_data { - Ok(path_buffer) => { - println!("{}", path_buffer.display()); - Outcome::Ok.exit_code() - } + match hipcheck_data { + Ok(path_buffer) => println!("Hipcheck data directory: {}", path_buffer.display()), Err(err) => { + failed = true; print_error(&err); - Outcome::Err.exit_code() } - }; + } + + // Check that a GitHub token has been provided as an environment variable + // This does not check if the token is valid or not + // The absence of a token does not trigger the failure state for the readiness check, because + // Hipcheck *can* run without a token, but some analyses will not. + match var("HC_GITHUB_TOKEN") { + Ok(_) => println!("HC_GITHUB_TOKEN system environment variable found."), + Err(_) => println!("Missing HC_GITHUB_TOKEN system environment variable. Some analyses will not run without this token set."), + } + + if failed { + println!("One or more dependencies or configuration settings are missing. Hipcheck is not ready to run."); + } else { + println!("Hipcheck is ready to run!"); + } + let exit_code = Outcome::Err.exit_code(); exit(exit_code); }