Skip to content

Commit

Permalink
feat: added mise dr --json
Browse files Browse the repository at this point in the history
Fixes #3012
  • Loading branch information
jdx committed Dec 19, 2024
1 parent b7c8da5 commit f8e1147
Show file tree
Hide file tree
Showing 6 changed files with 159 additions and 31 deletions.
6 changes: 5 additions & 1 deletion docs/cli/doctor.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
# `mise doctor`

- **Usage**: `mise doctor <SUBCOMMAND>`
- **Usage**: `mise doctor [-J --json] <SUBCOMMAND>`
- **Aliases**: `dr`
- **Source code**: [`src/cli/doctor.rs`](https://github.com/jdx/mise/blob/main/src/cli/doctor.rs)

Check mise installation for possible problems

## Flags

### `-J --json`

## Subcommands

- [`mise doctor path [-f --full]`](/cli/doctor/path.md)
Expand Down
2 changes: 1 addition & 1 deletion docs/cli/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ Can also use `MISE_NO_CONFIG=1`
- [`mise config ls [--no-header] [-J --json]`](/cli/config/ls.md)
- [`mise config set [-f --file <FILE>] [-t --type <TYPE>] <KEY> <VALUE>`](/cli/config/set.md)
- [`mise deactivate`](/cli/deactivate.md)
- [`mise doctor <SUBCOMMAND>`](/cli/doctor.md)
- [`mise doctor [-J --json] <SUBCOMMAND>`](/cli/doctor.md)
- [`mise doctor path [-f --full]`](/cli/doctor/path.md)
- [`mise en [-s --shell <SHELL>] [DIR]`](/cli/en.md)
- [`mise env [FLAGS] [TOOL@VERSION]...`](/cli/env.md)
Expand Down
1 change: 1 addition & 0 deletions e2e/cli/test_doctor
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ eval "$(mise activate bash)" && _mise_hook
mise p add uv
mise use uv
assert_contains "mise doctor" "asdf:uv@"
assert_contains "mise doctor -J" "dummy"
1 change: 1 addition & 0 deletions mise.usage.kdl
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,7 @@ cmd "doctor" help="Check mise installation for possible problems" {
$ mise doctor
[WARN] plugin node is not installed
"
flag "-J --json"
cmd "path" help="Print the current PATH entries mise is providing" {
alias "paths" hide=true
after_long_help r"Examples:
Expand Down
171 changes: 142 additions & 29 deletions src/cli/doctor/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
mod path;

use crate::exit;
use std::collections::BTreeMap;

use crate::backend::backend_type::BackendType;
use crate::build_time::built_info;
Expand All @@ -13,14 +14,17 @@ use crate::git::Git;
use crate::plugins::core::CORE_PLUGINS;
use crate::plugins::PluginType;
use crate::shell::ShellType;
use crate::toolset::{Toolset, ToolsetBuilder};
use crate::toolset::{ToolVersion, Toolset, ToolsetBuilder};
use crate::ui::{info, style};
use crate::{backend, cmd, dirs, duration, env, file, shims};
use console::{pad_str, style, Alignment};
use heck::ToSnakeCase;
use indexmap::IndexMap;
use indoc::formatdoc;
use itertools::Itertools;
use rayon::prelude::*;
use std::env::split_paths;
use std::path::{Path, PathBuf};
use strum::IntoEnumIterator;

/// Check mise installation for possible problems
Expand All @@ -33,6 +37,8 @@ pub struct Doctor {
errors: Vec<String>,
#[clap(skip)]
warnings: Vec<String>,
#[clap(long, short = 'J')]
json: bool,
}

#[derive(Debug, clap::Subcommand)]
Expand All @@ -46,11 +52,106 @@ impl Doctor {
match cmd {
Commands::Path(cmd) => cmd.run(),
}
} else if self.json {
self.doctor_json()
} else {
self.doctor()
}
}

fn doctor_json(mut self) -> crate::Result<()> {
let mut data: BTreeMap<String, _> = BTreeMap::new();
data.insert(
"version".into(),
serde_json::Value::String(VERSION.to_string()),
);
data.insert("activated".into(), env::is_activated().into());
data.insert("shims_on_path".into(), shims_on_path().into());
if env::is_activated() && shims_on_path() {
self.errors.push("shims are on PATH and mise is also activated. You should only use one of these methods.".to_string());
}
data.insert(
"build_info".into(),
build_info()
.into_iter()
.map(|(k, v)| (k.to_snake_case(), v))
.collect(),
);
let shell = shell();
let mut shell_lines = shell.lines();
let mut shell = serde_json::Map::new();
if let Some(name) = shell_lines.next() {
shell.insert("name".into(), name.into());
}
if let Some(version) = shell_lines.next() {
shell.insert("version".into(), version.into());
}
data.insert("shell".into(), shell.into());
data.insert(
"dirs".into(),
mise_dirs()
.into_iter()
.map(|(k, p)| (k, p.to_string_lossy().to_string()))
.collect(),
);
data.insert("env_vars".into(), mise_env_vars().into_iter().collect());
data.insert(
"settings".into(),
serde_json::from_str(&cmd!("mise", "settings", "-J").read()?)?,
);

let config = Config::get();
let ts = config.get_toolset()?;
self.analyze_shims(ts);
self.analyze_plugins();
data.insert(
"paths".into(),
self.paths(ts)?
.into_iter()
.map(|p| p.to_string_lossy().to_string())
.collect(),
);

let tools = ts.list_versions_by_plugin().into_iter().map(|(f, tv)| {
let versions: serde_json::Value = tv
.iter()
.map(|tv: &ToolVersion| {
let mut tool = serde_json::Map::new();
match f.is_version_installed(tv, true) {
true => {
tool.insert("version".into(), tv.version.to_string().into());
}
false => {
tool.insert("version".into(), tv.version.to_string().into());
tool.insert("missing".into(), true.into());
}
}
serde_json::Value::from(tool)
})
.collect();
(f.ba().to_string(), versions)
});
data.insert("toolset".into(), tools.collect());

if !self.errors.is_empty() {
data.insert("errors".into(), self.errors.clone().into_iter().collect());
}
if !self.warnings.is_empty() {
data.insert(
"warnings".into(),
self.warnings.clone().into_iter().collect(),
);
}

let out = serde_json::to_string_pretty(&data)?;
println!("{}", out);

if !self.errors.is_empty() {
exit(1);
}
Ok(())
}

fn doctor(mut self) -> eyre::Result<()> {
info::inline_section("version", &*VERSION)?;
#[cfg(unix)]
Expand All @@ -60,9 +161,17 @@ impl Doctor {
self.errors.push("shims are on PATH and mise is also activated. You should only use one of these methods.".to_string());
}

info::section("build_info", build_info())?;
let build_info = build_info()
.into_iter()
.map(|(k, v)| format!("{k}: {v}"))
.join("\n");
info::section("build_info", build_info)?;
info::section("shell", shell())?;
info::section("dirs", mise_dirs())?;
let mise_dirs = mise_dirs()
.into_iter()
.map(|(k, p)| format!("{k}: {}", display_path(p)))
.join("\n");
info::section("dirs", mise_dirs)?;

match Config::try_get() {
Ok(config) => self.analyze_config(config)?,
Expand All @@ -71,7 +180,15 @@ impl Doctor {

self.analyze_plugins();

info::section("env_vars", mise_env_vars())?;
let env_vars = mise_env_vars()
.into_iter()
.map(|(k, v)| format!("{k}={v}"))
.join("\n");
if env_vars.is_empty() {
info::section("env_vars", "(none)")?;
} else {
info::section("env_vars", env_vars)?;
}
self.analyze_settings()?;

if let Some(latest) = version::check_for_new_version(duration::HOURLY) {
Expand Down Expand Up @@ -231,16 +348,16 @@ impl Doctor {
}
}

fn analyze_paths(&mut self, toolset: &Toolset) -> eyre::Result<()> {
let env = toolset.full_env()?;
fn paths(&mut self, ts: &Toolset) -> eyre::Result<Vec<PathBuf>> {
let env = ts.full_env()?;
let path = env
.get(&*PATH_KEY)
.ok_or_else(|| eyre::eyre!("Path not found"))?;
let paths = split_paths(path)
.collect::<Vec<std::path::PathBuf>>()
.into_iter()
.map(display_path)
.join("\n");
Ok(split_paths(path).map(PathBuf::from).collect())
}

fn analyze_paths(&mut self, ts: &Toolset) -> eyre::Result<()> {
let paths = self.paths(ts)?.into_iter().map(display_path).join("\n");

info::section("path", paths)?;
Ok(())
Expand All @@ -259,7 +376,7 @@ fn yn(b: bool) -> String {
}
}

fn mise_dirs() -> String {
fn mise_dirs() -> Vec<(String, &'static Path)> {
[
("cache", &*dirs::CACHE),
("config", &*dirs::CONFIG),
Expand All @@ -268,19 +385,15 @@ fn mise_dirs() -> String {
("state", &*dirs::STATE),
]
.iter()
.map(|(k, p)| format!("{k}: {}", display_path(p)))
.join("\n")
.map(|(k, v)| (k.to_string(), **v))
.collect()
}

fn mise_env_vars() -> String {
let vars = env::vars()
fn mise_env_vars() -> Vec<(String, String)> {
env::vars()
.filter(|(k, _)| k.starts_with("MISE_"))
.filter(|(k, _)| k != "MISE_GITHUB_TOKEN")
.collect::<Vec<(String, String)>>();
if vars.is_empty() {
return "(none)".to_string();
}
vars.iter().map(|(k, v)| format!("{k}={v}")).join("\n")
.collect()
}

fn render_config_files(config: &Config) -> String {
Expand Down Expand Up @@ -340,14 +453,14 @@ fn render_plugins() -> String {
.join("\n")
}

fn build_info() -> String {
let mut s = vec![];
s.push(format!("Target: {}", built_info::TARGET));
s.push(format!("Features: {}", built_info::FEATURES_STR));
s.push(format!("Built: {}", built_info::BUILT_TIME_UTC));
s.push(format!("Rust Version: {}", built_info::RUSTC_VERSION));
s.push(format!("Profile: {}", built_info::PROFILE));
s.join("\n")
fn build_info() -> IndexMap<String, &'static str> {
let mut s = IndexMap::new();
s.insert("Target".into(), built_info::TARGET);
s.insert("Features".into(), built_info::FEATURES_STR);
s.insert("Built".into(), built_info::BUILT_TIME_UTC);
s.insert("Rust Version".into(), built_info::RUSTC_VERSION);
s.insert("Profile".into(), built_info::PROFILE);
s
}

fn shell() -> String {
Expand Down
9 changes: 9 additions & 0 deletions xtasks/fig/src/mise.ts
Original file line number Diff line number Diff line change
Expand Up @@ -876,6 +876,15 @@ const completionSpec: Fig.Spec = {
}
]
}
],
"options": [
{
"name": [
"-J",
"--json"
],
"isRepeatable": false
}
]
},
{
Expand Down

0 comments on commit f8e1147

Please sign in to comment.