Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[[case]]
name = "command_env_doctor_broken_config"
vp = "global"
skip-platforms = ["windows"]
comment = "An unreadable config breaks the shims, so doctor reports it as an error and exits with 1."
steps = [
{ argv = ["vpt", "write-file", "$HOME/.profile", ". \"$HOME/.vite-plus/env\""], snapshot = false, comment = "source the env file so the output has no platform-specific IDE guidance" },
{ argv = ["vpt", "write-file", "$VP_HOME/config.json", "{ not json"], snapshot = false },
["vp", "env", "doctor"],
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# command_env_doctor_broken_config

An unreadable config breaks the shims, so doctor reports it as an error and exits with 1.

## `vpt write-file $HOME/.profile '. "$HOME/.vite-plus/env"'`

source the env file so the output has no platform-specific IDE guidance


## `vpt write-file $VP_HOME/config.json '{ not json'`


## `vp env doctor`

**Exit code:** 1

```
VITE+ - The Unified Toolchain for the Web

Installation
✓ Bin dir ~/.vite-plus/bin
✓ Data dir ~/.vite-plus
✓ Cache dir ~/.vite-plus/cache (not created yet)
✓ Config dir ~/.vite-plus
✓ State dir ~/.vite-plus

Configuration
✗ Config ~/.vite-plus/config.json (could not read)
✓ IDE integration env sourced in ~/.profile

PATH
✓ vp ~/.vite-plus/bin/vp
✓ Shim dir ~/.vite-plus/bin

Node.js Resolution
Directory <workspace>
✗ Resolution ~/.vite-plus/config.json (could not read)

Package Manager Resolution
Package manager not selected

✗ Some issues found.
```
103 changes: 66 additions & 37 deletions crates/vp_global_cli/src/commands/env/doctor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,10 @@ pub async fn execute(cwd: AbsolutePathBuf, scope: Option<String>) -> Result<Exit

// Section: Configuration
print_section("Configuration");
let (environment_config, system_node_path) = check_shim_mode(scope).await;
let shim_mode = check_shim_mode(scope).await;
let config_readable = shim_mode.is_some();
has_errors |= !config_readable;
let (environment_config, system_node_path) = shim_mode.unwrap_or_default();

// Check env sourcing: IDE-relevant profiles first, then all shell profiles
let env_status = cfg!(not(windows)).then(check_env_sourcing);
Expand All @@ -125,7 +128,15 @@ pub async fn execute(cwd: AbsolutePathBuf, scope: Option<String>) -> Result<Exit
// Section: Version Resolution
let resolution = if scope.includes_node() {
print_section("Node.js Resolution");
check_current_resolution(&cwd, environment_config.node_shim_mode, system_node_path).await
let (ok, resolution) = check_current_resolution(
&cwd,
environment_config.node_shim_mode,
system_node_path,
config_readable,
)
.await;
has_errors |= !ok;
resolution
} else {
None
};
Expand Down Expand Up @@ -154,9 +165,7 @@ pub async fn execute(cwd: AbsolutePathBuf, scope: Option<String>) -> Result<Exit
if has_errors {
vp_shared::output::print_stdout_line(format_args!(
"{}",
style("\u{2717} Some issues found. Run the suggested commands to fix them.")
.red()
.bold()
style("\u{2717} Some issues found.").red().bold()
));
Ok(super::exit_status(1))
} else {
Expand Down Expand Up @@ -261,17 +270,14 @@ async fn check_shims(scope: EnvScope) -> bool {
}
}

/// Check and display shim mode. Returns the mode and any found system node path.
async fn check_shim_mode(scope: EnvScope) -> (config::Config, Option<AbsolutePathBuf>) {
/// Check and display shim mode. Returns the mode and any found system node path,
/// or `None` when the config cannot be read.
async fn check_shim_mode(scope: EnvScope) -> Option<(config::Config, Option<AbsolutePathBuf>)> {
let config = match load_config().await {
Ok(c) => c,
Err(e) => {
print_check(
&style(output::WARN_SIGN).yellow().to_string(),
"Node.js",
&style(format!("config error: {e}")).yellow().to_string(),
);
return (config::Config::default(), None);
Err(_) => {
print_check(&style(output::CROSS).red().to_string(), "Config", &unreadable_config());
return None;
}
};

Expand Down Expand Up @@ -328,7 +334,14 @@ async fn check_shim_mode(scope: EnvScope) -> (config::Config, Option<AbsolutePat
}
}

(config, system_node_path)
Some((config, system_node_path))
}

fn unreadable_config() -> String {
let path = config::get_config_path()
.map(|path| abbreviate_home(&path.as_path().display().to_string()))
.unwrap_or_default();
style(format!("{path} (could not read)")).red().to_string()
}

async fn check_package_manager_session_override() {
Expand Down Expand Up @@ -569,6 +582,7 @@ async fn check_path(scope: EnvScope) -> bool {
};

// Show which tool would be executed for each shim
let mut ok = vp_path.is_some();
for tool in selected_shim_tools(scope) {
if let Some(tool_path) = find_in_path(tool) {
let expected_dir = super::setup::shim_dir(&settings, tool);
Expand All @@ -594,11 +608,16 @@ async fn check_path(scope: EnvScope) -> bool {
);
}
} else {
print_check(" ", tool, "not found");
print_check(
&style(output::CROSS).red().to_string(),
tool,
&style("not found").red().to_string(),
);
ok = false;
}
}

vp_path.is_some()
ok
}

/// Find an executable in PATH.
Expand Down Expand Up @@ -778,19 +797,30 @@ fn format_version_source(source: &str, source_path: Option<&vt_path::AbsolutePat
}
}

/// Check current directory version resolution.
/// Check current directory version resolution. Returns whether the check passed
/// and the managed resolution, if any.
async fn check_current_resolution(
cwd: &AbsolutePathBuf,
shim_mode: ShimMode,
system_node_path: Option<AbsolutePathBuf>,
) -> Option<config::VersionResolution> {
config_readable: bool,
) -> (bool, Option<config::VersionResolution>) {
print_check(" ", "Directory", &cwd.as_path().display().to_string());

// In system-first mode, show system Node.js info instead of managed resolution
if shim_mode == ShimMode::SystemFirst {
if let Some(system_node) = system_node_path {
let version = get_node_version(&system_node).await;
print_check(" ", "Source", "system PATH");
let Some(version) = try_get_tool_version(&system_node).await else {
print_check(
&style(output::CROSS).red().to_string(),
"Node binary",
&style(format!("{} (could not execute)", system_node.as_path().display()))
.red()
.to_string(),
);
return (false, None);
};
print_check(" ", "Version", &style(&version).green().bright().to_string());
print_check(
&style(output::CHECK).green().to_string(),
Expand All @@ -805,7 +835,7 @@ async fn check_current_resolution(
);
print_hint("Install Node.js or run 'vp env on' to use managed Node.js.");
}
return None;
return (true, None);
}

match resolve_version(cwd).await {
Expand Down Expand Up @@ -838,28 +868,27 @@ async fn check_current_resolution(
);
print_hint("Version will be downloaded on first use.");
}
Some(resolution)
(true, Some(resolution))
}
Err(e) => {
print_check(
&style(output::CROSS).red().to_string(),
"Resolution",
&style(format!("failed: {e}")).red().to_string(),
);
None
// Without a `VP_NODE_VERSION` override or a project version, resolution reads the global config.
let reads_config = !config_readable
&& vp_shared::EnvConfig::get()
.node_version
.as_deref()
.is_none_or(|version| version.trim().is_empty())
&& matches!(config::resolve_project_version_source(cwd, false).await, Ok(None));
let message = if reads_config {
unreadable_config()
} else {
style(format!("failed: {e}")).red().to_string()
};
print_check(&style(output::CROSS).red().to_string(), "Resolution", &message);
(false, None)
}
}
}

/// Get the version string from a Node.js binary.
async fn get_node_version(node_path: &vt_path::AbsolutePath) -> String {
get_tool_version(node_path).await
}

async fn get_tool_version(tool_path: &vt_path::AbsolutePath) -> String {
try_get_tool_version(tool_path).await.unwrap_or_else(|| "unknown".to_string())
}

async fn try_get_tool_version(tool_path: &vt_path::AbsolutePath) -> Option<String> {
let output =
tokio::process::Command::new(tool_path.as_path()).arg("--version").output().await.ok()?;
Expand Down
4 changes: 2 additions & 2 deletions rfcs/env-command.md
Original file line number Diff line number Diff line change
Expand Up @@ -831,7 +831,7 @@ PATH

...

✗ Some issues found. Run the suggested commands to fix them.
✗ Some issues found.
```

## User Experience
Expand Down Expand Up @@ -1037,7 +1037,7 @@ IDE Setup
. "$HOME/.vite-plus/env"
Then restart your IDE to apply changes.

✗ Some issues found. Run the suggested commands to fix them.
✗ Some issues found.
```

## Shell Configuration Reference
Expand Down
Loading