From 8a08f2ebf67e3fa68c72cf7348f8270228674aaf Mon Sep 17 00:00:00 2001 From: Liang Date: Wed, 16 Sep 2026 06:17:40 +0800 Subject: [PATCH 1/4] fix(cli): honor NO_COLOR in Rust output --- Cargo.lock | 8 +- crates/vp_cli_help/Cargo.toml | 2 +- crates/vp_cli_help/src/lib.rs | 17 +- .../fixtures/check_lint_fail/snapshots.toml | 16 ++ .../snapshots/check_force_color_zero.md | 30 ++++ .../snapshots/check_no_color.md | 30 ++++ .../fixtures/command_no_color/package.json | 4 + .../fixtures/command_no_color/snapshots.toml | 20 +++ .../snapshots/command_no_color.global.md | 55 ++++++ .../snapshots/command_no_color.local.md | 51 ++++++ .../snapshots/prompt_no_color.md | 25 +++ crates/vp_global_cli/Cargo.toml | 2 +- crates/vp_global_cli/src/cli.rs | 11 +- .../vp_global_cli/src/commands/env/doctor.rs | 167 ++++++++++-------- crates/vp_global_cli/src/commands/env/list.rs | 8 +- .../src/commands/env/list_remote.rs | 12 +- .../vp_global_cli/src/commands/env/which.rs | 54 +++--- .../src/commands/global/install.rs | 8 +- .../src/commands/global/outdated.rs | 27 +-- .../src/commands/global/packages.rs | 4 +- crates/vp_global_cli/src/commands/implode.rs | 4 +- .../vp_global_cli/src/commands/upgrade/mod.rs | 10 +- crates/vp_global_cli/src/main.rs | 15 +- crates/vp_global_cli/src/upgrade_check.rs | 14 +- crates/vp_shared/Cargo.toml | 2 +- crates/vp_shared/src/output.rs | 23 +-- packages/cli/binding/Cargo.toml | 2 +- packages/cli/binding/src/check/analysis.rs | 10 +- packages/cli/binding/src/cli/execution.rs | 7 +- packages/cli/binding/src/cli/help.rs | 15 +- packages/cli/binding/src/cli/script_note.rs | 6 +- packages/cli/binding/src/exec/workspace.rs | 8 +- 32 files changed, 471 insertions(+), 196 deletions(-) create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/check_lint_fail/snapshots/check_force_color_zero.md create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/check_lint_fail/snapshots/check_no_color.md create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_no_color/package.json create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_no_color/snapshots.toml create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_no_color/snapshots/command_no_color.global.md create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_no_color/snapshots/command_no_color.local.md create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_no_color/snapshots/prompt_no_color.md diff --git a/Cargo.lock b/Cargo.lock index 5058ef1095..96fd00b6cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8632,12 +8632,12 @@ dependencies = [ "async-trait", "base64-simd", "clap", + "console", "cow-utils", "fspy", "napi", "napi-build", "napi-derive", - "owo-colors", "petgraph 0.8.3", "pretty_assertions", "rolldown_binding", @@ -8668,7 +8668,7 @@ name = "vp_cli_help" version = "0.0.0" dependencies = [ "clap", - "owo-colors", + "console", "terminal_size", "vp_shared", ] @@ -8741,6 +8741,7 @@ dependencies = [ "chrono", "clap", "clap_complete", + "console", "crossterm", "dialoguer", "directories", @@ -8751,7 +8752,6 @@ dependencies = [ "indicatif", "indoc", "node-semver", - "owo-colors", "oxc_resolver", "rustc-hash", "same-file", @@ -8913,9 +8913,9 @@ dependencies = [ name = "vp_shared" version = "0.0.0" dependencies = [ + "console", "directories", "nix 0.30.1", - "owo-colors", "reqwest", "rustls", "serde", diff --git a/crates/vp_cli_help/Cargo.toml b/crates/vp_cli_help/Cargo.toml index 86dfb739e9..91efcce69b 100644 --- a/crates/vp_cli_help/Cargo.toml +++ b/crates/vp_cli_help/Cargo.toml @@ -9,7 +9,7 @@ rust-version.workspace = true [dependencies] clap = { workspace = true } -owo-colors = { workspace = true } +console = { workspace = true } terminal_size = { workspace = true } vp_shared = { workspace = true } diff --git a/crates/vp_cli_help/src/lib.rs b/crates/vp_cli_help/src/lib.rs index c4595ec270..23f9f79b86 100644 --- a/crates/vp_cli_help/src/lib.rs +++ b/crates/vp_cli_help/src/lib.rs @@ -9,7 +9,7 @@ use std::{borrow::Cow, fmt::Write as _, io::Write as _}; use clap::{Arg, Command}; -use owo_colors::OwoColorize; +use console::style; use terminal_size::{Width, terminal_size_of}; const HELP_RIGHT_MARGIN: usize = 4; @@ -129,14 +129,14 @@ pub fn render_heading(title: &str) -> String { } if should_accent_heading(title) { - heading.bold().bright_blue().to_string() + style(&heading).bold().blue().bright().to_string() } else { - heading.bold().to_string() + style(&heading).bold().to_string() } } fn render_usage_value(usage: &str) -> String { - if should_style_help() { usage.bold().to_string() } else { usage.to_string() } + if should_style_help() { style(&usage).bold().to_string() } else { usage.to_string() } } fn should_accent_heading(title: &str) -> bool { @@ -149,7 +149,7 @@ fn write_documentation_footer(output: &mut String, documentation_url: &str) { } pub fn accent(text: &str) -> String { - if should_style_help() { text.bright_blue().to_string() } else { text.to_string() } + if should_style_help() { style(&text).blue().bright().to_string() } else { text.to_string() } } pub fn accent_command(command: &str) -> String { @@ -157,10 +157,7 @@ pub fn accent_command(command: &str) -> String { } pub fn should_style_help() -> bool { - vp_shared::is_stdout_terminal() - && std::env::var_os("NO_COLOR").is_none() - && std::env::var("CLICOLOR").map_or(true, |value| value != "0") - && std::env::var("TERM").map_or(true, |term| term != "dumb") + console::colors_enabled() } fn terminal_content_width() -> usize { @@ -321,7 +318,7 @@ fn render_muted_comment_suffix(line: &str) -> String { } if let Some((prefix, suffix)) = split_comment_suffix(line) { - return format!("{}{}", prefix, suffix.bright_black()); + return format!("{}{}", prefix, style(&suffix).black().bright()); } line.to_string() diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/check_lint_fail/snapshots.toml b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/check_lint_fail/snapshots.toml index 03ee472126..6a561b82e2 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/check_lint_fail/snapshots.toml +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/check_lint_fail/snapshots.toml @@ -5,3 +5,19 @@ steps = [ { argv = ["vp", "check"], continue-on-failure = true }, { argv = ["vp", "check", "--quiet"], comment = "warnings are suppressed while errors are still reported", continue-on-failure = true }, ] + +[[case]] +name = "check_no_color" +vp = "local" +env = { NO_COLOR = "1" } +steps = [ + { argv = ["vp", "check", "--no-fmt"], comment = "Captured lint diagnostics and Vite+ messages honor NO_COLOR in a terminal", formatted-snapshot = true }, +] + +[[case]] +name = "check_force_color_zero" +vp = "local" +env = { FORCE_COLOR = "0" } +steps = [ + { argv = ["vp", "check", "--no-fmt"], comment = "Capturing lint output preserves an explicit FORCE_COLOR=0", formatted-snapshot = true }, +] diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/check_lint_fail/snapshots/check_force_color_zero.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/check_lint_fail/snapshots/check_force_color_zero.md new file mode 100644 index 0000000000..9cbb2270a0 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/check_lint_fail/snapshots/check_force_color_zero.md @@ -0,0 +1,30 @@ +# check_force_color_zero + +## `vp check --no-fmt` + +Capturing lint output preserves an explicit FORCE_COLOR=0 + +**Exit code:** 1 + +``` +\x1b[31;1merror: Lint issues found +x eslint(no-eval): eval can be harmful. + ,-[src/index.js:2:3] + 1 | function hello() { + 2 | eval(\"code\"); + : ^^^^ + 3 | console.log(\"warning\"); + `---- + help: Avoid eval(). For JSON parsing use JSON.parse(); for dynamic property access use bracket notation (obj[key]); for other cases refactor to avoid evaluating strings as code. + + ! eslint(no-console): Unexpected console statement. + ,-[src/index.js:3:3] + 2 | eval(\"code\"); + 3 | console.log(\"warning\"); + : ^^^^^^^^^^^ + 4 | return \"hello\"; + `---- + help: Delete this console statement. + +Found 1 error and 1 warning in 2 files (, threads) +``` diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/check_lint_fail/snapshots/check_no_color.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/check_lint_fail/snapshots/check_no_color.md new file mode 100644 index 0000000000..6853285561 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/check_lint_fail/snapshots/check_no_color.md @@ -0,0 +1,30 @@ +# check_no_color + +## `vp check --no-fmt` + +Captured lint diagnostics and Vite+ messages honor NO_COLOR in a terminal + +**Exit code:** 1 + +``` +error: Lint issues found +x eslint(no-eval): eval can be harmful. + ,-[src/index.js:2:3] + 1 | function hello() { + 2 | eval(\"code\"); + : ^^^^ + 3 | console.log(\"warning\"); + `---- + help: Avoid eval(). For JSON parsing use JSON.parse(); for dynamic property access use bracket notation (obj[key]); for other cases refactor to avoid evaluating strings as code. + + ! eslint(no-console): Unexpected console statement. + ,-[src/index.js:3:3] + 2 | eval(\"code\"); + 3 | console.log(\"warning\"); + : ^^^^^^^^^^^ + 4 | return \"hello\"; + `---- + help: Delete this console statement. + +Found 1 error and 1 warning in 2 files (, threads) +``` diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_no_color/package.json b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_no_color/package.json new file mode 100644 index 0000000000..dc925c803c --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_no_color/package.json @@ -0,0 +1,4 @@ +{ + "name": "command-no-color", + "private": true +} diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_no_color/snapshots.toml b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_no_color/snapshots.toml new file mode 100644 index 0000000000..5d055a774c --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_no_color/snapshots.toml @@ -0,0 +1,20 @@ +[[case]] +name = "command_no_color" +vp = ["local", "global"] +env = { NO_COLOR = "1" } +steps = [ + { argv = ["vp", "check", "--help"], formatted-snapshot = true, continue-on-failure = true }, + { argv = ["vp", "--definitely-invalid"], comment = "Argument errors and highlighted arguments honor NO_COLOR", formatted-snapshot = true, continue-on-failure = true }, + { argv = ["vp", "check", "--no-fmt", "--no-lint"], comment = "Shared errors and command-specific summaries honor NO_COLOR", formatted-snapshot = true, continue-on-failure = true }, +] + +[[case]] +name = "prompt_no_color" +vp = "local" +env = { NO_COLOR = "1" } +steps = [ + { argv = ["vp", "create"], comment = "The picocolors-backed picker remains interactive without color", formatted-snapshot = true, interactions = [ + { "expect-milestone" = "select:select:0" }, + { "write-key" = "ctrl-c" }, + ] }, +] diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_no_color/snapshots/command_no_color.global.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_no_color/snapshots/command_no_color.global.md new file mode 100644 index 0000000000..26bf2a55a0 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_no_color/snapshots/command_no_color.global.md @@ -0,0 +1,55 @@ +# command_no_color + +## `vp check --help` + +``` +VITE+ - The Unified Toolchain for the Web + +Usage: vp check [OPTIONS] [PATHS]... + +Run format, lint, and type checks. + +Arguments: + [PATHS]... File paths to pass to fmt and lint + +Options: + --fix Auto-fix format and lint issues + --quiet Disable reporting on warnings, only errors are reported + --no-fmt Skip format check + --no-lint Skip lint rules; type-check still runs when `lint.options.typeCheck` is true + --no-error-on-unmatched-pattern Do not exit with error when pattern is unmatched + -h, --help Print help + +Examples: + vp check + vp check --fix + vp check --no-lint src/index.ts + +Documentation: https://viteplus.dev/guide/check +``` + +## `vp --definitely-invalid` + +Argument errors and highlighted arguments honor NO_COLOR + +**Exit code:** 2 + +``` +VITE+ - The Unified Toolchain for the Web + +error: Unexpected argument \'--definitely-invalid\' +``` + +## `vp check --no-fmt --no-lint` + +Shared errors and command-specific summaries honor NO_COLOR + +**Exit code:** 1 + +``` +VITE+ - The Unified Toolchain for the Web + +error: No checks enabled + +Enable `lint.options.typeCheck` in vite.config.ts for type-check only, drop a `--no-fmt`/`--no-lint` flag, or re-enable `check.fmt`/`check.lint` in vite.config.ts. +``` diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_no_color/snapshots/command_no_color.local.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_no_color/snapshots/command_no_color.local.md new file mode 100644 index 0000000000..9c5c81e3e5 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_no_color/snapshots/command_no_color.local.md @@ -0,0 +1,51 @@ +# command_no_color + +## `vp check --help` + +``` +VITE+ - The Unified Toolchain for the Web + +Usage: vp check [OPTIONS] [PATHS]... + +Run format, lint, and type checks. + +Arguments: + [PATHS]... File paths to pass to fmt and lint + +Options: + --fix Auto-fix format and lint issues + --quiet Disable reporting on warnings, only errors are reported + --no-fmt Skip format check + --no-lint Skip lint rules; type-check still runs when `lint.options.typeCheck` is true + --no-error-on-unmatched-pattern Do not exit with error when pattern is unmatched + -h, --help Print help + +Examples: + vp check + vp check --fix + vp check --no-lint src/index.ts + +Documentation: https://viteplus.dev/guide/check +``` + +## `vp --definitely-invalid` + +Argument errors and highlighted arguments honor NO_COLOR + +**Exit code:** 2 + +``` +error: Unexpected argument \'--definitely-invalid\' +``` + +## `vp check --no-fmt --no-lint` + +Shared errors and command-specific summaries honor NO_COLOR + +**Exit code:** 1 + +``` +error: No checks enabled + +Enable `lint.options.typeCheck` in vite.config.ts for type-check only, drop a `--no-fmt`/`--no-lint` flag, or re-enable `check.fmt`/`check.lint` in vite.config.ts. +``` diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_no_color/snapshots/prompt_no_color.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_no_color/snapshots/prompt_no_color.md new file mode 100644 index 0000000000..fa7a2672c6 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_no_color/snapshots/prompt_no_color.md @@ -0,0 +1,25 @@ +# prompt_no_color + +## `vp create` + +The picocolors-backed picker remains interactive without color + +**→ expect-milestone:** `select:select:0` + +``` +VITE+ - The Unified Toolchain for the Web + + › Vite+ Monorepo: Create a new Vite+ monorepo project + Vite+ Application: Create vite applications + Vite+ Library: Create vite libraries +``` + +**← write-key:** `ctrl-c` + +``` +VITE+ - The Unified Toolchain for the Web + + Vite+ Monorepo + +Operation cancelled +``` diff --git a/crates/vp_global_cli/Cargo.toml b/crates/vp_global_cli/Cargo.toml index c802d93c1b..4212e00bca 100644 --- a/crates/vp_global_cli/Cargo.toml +++ b/crates/vp_global_cli/Cargo.toml @@ -29,7 +29,7 @@ tar = { workspace = true } tempfile = { workspace = true } tokio = { workspace = true, features = ["full"] } tracing = { workspace = true } -owo-colors = { workspace = true } +console = { workspace = true } same-file = { workspace = true } oxc_resolver = { workspace = true } rustc-hash = { workspace = true } diff --git a/crates/vp_global_cli/src/cli.rs b/crates/vp_global_cli/src/cli.rs index caefa8ec74..8d549844c8 100644 --- a/crates/vp_global_cli/src/cli.rs +++ b/crates/vp_global_cli/src/cli.rs @@ -7,8 +7,8 @@ use std::{collections::HashSet, ffi::OsStr, process::ExitStatus}; use clap::{CommandFactory, FromArgMatches, Parser, Subcommand}; use clap_complete::ArgValueCompleter; +use console::style; use dialoguer::{Confirm, theme::ColorfulTheme}; -use owo_colors::OwoColorize; use tokio::runtime::Runtime; use vp_pm_cli::{ManagedGlobalCommand, PackageManagerCommand}; use vp_shared::output; @@ -1019,14 +1019,17 @@ fn prompt_reinstall_node_mismatches( ) -> bool { output::info("Some global packages were installed with a different Node.js version."); output::raw(""); - output::raw(&format!("Current Node.js: {}", display_node_version(current_node_version).bold())); + output::raw(&format!( + "Current Node.js: {}", + style(&display_node_version(current_node_version)).bold() + )); output::raw(""); output::raw("Affected packages:"); for package in packages { output::raw(&format!( "- {} (installed with {})", - package.name.bold(), - display_node_version(&package.installed_node).bold() + style(&package.name).bold(), + style(&display_node_version(&package.installed_node)).bold() )); } output::raw(""); diff --git a/crates/vp_global_cli/src/commands/env/doctor.rs b/crates/vp_global_cli/src/commands/env/doctor.rs index 70673d5893..01c673873b 100644 --- a/crates/vp_global_cli/src/commands/env/doctor.rs +++ b/crates/vp_global_cli/src/commands/env/doctor.rs @@ -2,7 +2,7 @@ use std::process::ExitStatus; -use owo_colors::OwoColorize; +use console::style; use vp_pm_cli::{package_manager_bin_path, package_manager_install_dir}; use vp_shared::{env_vars, output}; use vt_path::{AbsolutePathBuf, current_dir}; @@ -44,12 +44,12 @@ const KEY_WIDTH: usize = 18; /// Print a section header (bold, with blank line before). fn print_section(name: &str) { println!(); - println!("{}", name.bold()); + println!("{}", style(&name).bold()); } /// Print an aligned key-value line with a status indicator. /// -/// `status` should be a colored string like "✓".green(), "✗".red(), etc. +/// `status` should be a styled string like `style("✓").green().to_string()`. /// Use `" "` for informational lines with no status. fn print_check(status: &str, key: &str, value: &str) { if status.trim().is_empty() { @@ -66,12 +66,12 @@ fn print_package_manager_mode(key: &str, mode: ShimMode) { ShimMode::Managed => "managed mode", ShimMode::SystemFirst => "system-first mode", }; - print_check(&output::CHECK.green().to_string(), key, mode); + print_check(&style(output::CHECK).green().to_string(), key, mode); } /// Print a continuation/hint line (dimmed). fn print_hint(text: &str) { - println!(" {}", format!("note: {text}").dimmed()); + println!(" {}", style(format!("note: {text}")).dim()); } /// Abbreviate home directory to `~` for display. @@ -90,7 +90,7 @@ pub async fn execute(cwd: AbsolutePathBuf, scope: Option) -> Result) -> Result bool { for (label, dir, required) in rows { let display = abbreviate_home(&dir.as_path().display().to_string()); if tokio::fs::try_exists(dir).await.unwrap_or(false) { - print_check(&output::CHECK.green().to_string(), label, &display); + print_check(&style(output::CHECK).green().to_string(), label, &display); } else if required { print_check( - &output::CROSS.red().to_string(), + &style(output::CROSS).red().to_string(), label, - &format!("{display} {}", "(does not exist)".red()), + &format!("{display} {}", style("(does not exist)").red()), ); print_hint("Run 'vp env setup' to create the directory."); ok = false; } else { print_check( - &output::CHECK.green().to_string(), + &style(output::CHECK).green().to_string(), label, - &format!("{display} {}", "(not created yet)".bright_black()), + &format!("{display} {}", style("(not created yet)").black().bright()), ); } } @@ -225,13 +227,13 @@ async fn check_shims(scope: EnvScope) -> bool { } if missing.is_empty() { - print_check(&output::CHECK.green().to_string(), "Shims", &tools.join(", ")); + print_check(&style(output::CHECK).green().to_string(), "Shims", &tools.join(", ")); true } else { print_check( - &output::CROSS.red().to_string(), + &style(output::CROSS).red().to_string(), "Missing shims", - &missing.join(", ").red().to_string(), + &style(&missing.join(", ")).red().to_string(), ); print_hint("Run 'vp env setup' to create missing shims."); false @@ -258,9 +260,9 @@ async fn check_shim_mode(scope: EnvScope) -> (config::Config, Option c, Err(e) => { print_check( - &output::WARN_SIGN.yellow().to_string(), + &style(output::WARN_SIGN).yellow().to_string(), "Node.js", - &format!("config error: {e}").yellow().to_string(), + &style(format!("config error: {e}")).yellow().to_string(), ); return (config::Config::default(), None); } @@ -271,13 +273,13 @@ async fn check_shim_mode(scope: EnvScope) -> (config::Config, Option { - print_check(&output::CHECK.green().to_string(), "Node.js", "managed mode"); + print_check(&style(output::CHECK).green().to_string(), "Node.js", "managed mode"); } ShimMode::SystemFirst => { print_check( - &output::CHECK.green().to_string(), + &style(output::CHECK).green().to_string(), "Node.js", - &"system-first mode".bright_blue().to_string(), + &style("system-first mode").blue().bright().to_string(), ); // Check if system Node.js is available @@ -290,9 +292,9 @@ async fn check_shim_mode(scope: EnvScope) -> (config::Config, Option selected.map(|resolution| resolution.package_manager_type), Err(error) => { print_check( - &output::CROSS.red().to_string(), + &style(output::CROSS).red().to_string(), "Package manager", &error.to_string(), ); @@ -363,9 +365,9 @@ async fn check_package_manager_resolution( let Some(version) = try_get_tool_version(&system_binary).await else { print_check(" ", "Source", "system PATH"); print_check( - &output::CROSS.red().to_string(), + &style(output::CROSS).red().to_string(), "PM binary", - &format!("{} (could not execute)", system_binary.as_path().display()) + &style(format!("{} (could not execute)", system_binary.as_path().display())) .red() .to_string(), ); @@ -375,10 +377,10 @@ async fn check_package_manager_resolution( print_check( " ", "Version", - &format!("{selected_type}@{version}").bright_green().to_string(), + &style(format!("{selected_type}@{version}")).green().bright().to_string(), ); print_check( - &output::CHECK.green().to_string(), + &style(output::CHECK).green().to_string(), "PM binary", &system_binary.as_path().display().to_string(), ); @@ -395,8 +397,9 @@ async fn check_package_manager_resolution( print_check( " ", "Version", - &format!("{}@{}", resolution.package_manager_type, resolution.version) - .bright_green() + &style(format!("{}@{}", resolution.package_manager_type, resolution.version)) + .green() + .bright() .to_string(), ); let installed = @@ -408,9 +411,9 @@ async fn check_package_manager_resolution( }); let status = if installed { "installed" } else { "not installed" }; let indicator = if installed { - output::CHECK.green().to_string() + style(output::CHECK).green().to_string() } else { - output::WARN_SIGN.yellow().to_string() + style(output::WARN_SIGN).yellow().to_string() }; print_check(&indicator, "PM binaries", status); true @@ -420,7 +423,11 @@ async fn check_package_manager_resolution( true } Err(error) => { - print_check(&output::CROSS.red().to_string(), "Package manager", &error.to_string()); + print_check( + &style(output::CROSS).red().to_string(), + "Package manager", + &error.to_string(), + ); false } } @@ -446,7 +453,7 @@ fn check_env_sourcing() -> EnvSourcingStatus { // First: check IDE-relevant profiles (login/environment files visible to GUI apps) if let Some(file) = check_profile_files(&env_path, IDE_SHELL_PROFILES) { print_check( - &output::CHECK.green().to_string(), + &style(output::CHECK).green().to_string(), "IDE integration", &format!("env sourced in {file}"), ); @@ -456,12 +463,12 @@ fn check_env_sourcing() -> EnvSourcingStatus { // Second: check all shell profiles (interactive terminal sessions) if let Some(file) = check_profile_files(&env_path, ALL_SHELL_PROFILES) { print_check( - &output::WARN_SIGN.yellow().to_string(), + &style(output::WARN_SIGN).yellow().to_string(), "IDE integration", &format!( "{} {}", - format!("env sourced in {file}").yellow(), - "(may not be visible to GUI apps)".dimmed(), + style(format!("env sourced in {file}")).yellow(), + style("(may not be visible to GUI apps)").dim(), ), ); return EnvSourcingStatus::ShellOnly; @@ -476,9 +483,9 @@ fn check_session_override() { let version = version.trim(); if !version.is_empty() { print_check( - &output::WARN_SIGN.yellow().to_string(), + &style(output::WARN_SIGN).yellow().to_string(), "Session override", - &format!("{}={version}", env_vars::VP_NODE_VERSION).yellow().to_string(), + &style(format!("{}={version}", env_vars::VP_NODE_VERSION)).yellow().to_string(), ); print_hint("Overrides all file-based resolution."); print_hint("Run 'vp env use --unset' to remove."); @@ -488,9 +495,9 @@ fn check_session_override() { // Also check session version file if let Some(version) = config::read_session_version_sync() { print_check( - &output::WARN_SIGN.yellow().to_string(), + &style(output::WARN_SIGN).yellow().to_string(), "Session override (file)", - &format!("{}={version}", config::SESSION_VERSION_FILE).yellow().to_string(), + &style(format!("{}={version}", config::SESSION_VERSION_FILE)).yellow().to_string(), ); print_hint("Written by 'vp env use'. Run 'vp env use --unset' to remove."); } @@ -513,9 +520,13 @@ async fn check_path(scope: EnvScope) -> bool { let bin_display = abbreviate_home(&bin_dir.as_path().display().to_string()); if bin_in_path { - print_check(&output::CHECK.green().to_string(), "vp", "in PATH"); + print_check(&style(output::CHECK).green().to_string(), "vp", "in PATH"); } else { - print_check(&output::CROSS.red().to_string(), "vp", &"not in PATH".red().to_string()); + print_check( + &style(output::CROSS).red().to_string(), + "vp", + &style("not in PATH").red().to_string(), + ); print_hint(&format!("Expected: {bin_display}")); println!(); print_path_fix(&vp_shared::EnvConfig::get().dirs.config); @@ -529,15 +540,15 @@ async fn check_path(scope: EnvScope) -> bool { let display = abbreviate_home(&tool_path.display().to_string()); if tool_path == expected.as_path() { print_check( - &output::CHECK.green().to_string(), + &style(output::CHECK).green().to_string(), tool, - &format!("{display} {}", "(vp shim)".dimmed()), + &format!("{display} {}", style("(vp shim)").dim()), ); } else { print_check( - &output::WARN_SIGN.yellow().to_string(), + &style(output::WARN_SIGN).yellow().to_string(), tool, - &format!("{} {}", display.yellow(), "(not vp shim)".dimmed()), + &format!("{} {}", style(&display).yellow(), style("(not vp shim)").dim()), ); } } else { @@ -570,28 +581,28 @@ fn print_path_fix(env_dir: &vt_path::AbsolutePath) { env_path }; - println!(" {}", "Add to your shell profile (~/.zshrc, ~/.bashrc, etc.):".dimmed()); + println!(" {}", style("Add to your shell profile (~/.zshrc, ~/.bashrc, etc.):").dim()); println!(); println!(" . \"{env_path}/env\""); println!(); - println!(" {}", "For fish shell, add to ~/.config/fish/config.fish:".dimmed()); + println!(" {}", style("For fish shell, add to ~/.config/fish/config.fish:").dim()); println!(); println!(" source \"{env_path}/env.fish\""); println!(); - println!(" {}", "For Nushell, add to ~/.config/nushell/config.nu:".dimmed()); + println!(" {}", style("For Nushell, add to ~/.config/nushell/config.nu:").dim()); println!(); println!(" source '{env_path}/env.nu'"); println!(); - println!(" {}", "Then restart your terminal.".dimmed()); + println!(" {}", style("Then restart your terminal.").dim()); } #[cfg(windows)] { let _ = env_dir; - println!(" {}", "Add the bin directory to your PATH via:".dimmed()); + println!(" {}", style("Add the bin directory to your PATH via:").dim()); println!(" System Properties -> Environment Variables -> Path"); println!(); - println!(" {}", "Then restart your terminal.".dimmed()); + println!(" {}", style("Then restart your terminal.").dim()); } } @@ -641,34 +652,34 @@ fn print_ide_setup_guidance(env_dir: &vt_path::AbsolutePath) { print_section("IDE Setup"); print_check( - &output::WARN_SIGN.yellow().to_string(), + &style(output::WARN_SIGN).yellow().to_string(), "", - &"GUI applications may not see shell PATH changes.".yellow().to_string(), + &style("GUI applications may not see shell PATH changes.").yellow().to_string(), ); println!(); #[cfg(target_os = "macos")] { - println!(" {}", "macOS:".dimmed()); - println!(" {}", "Add to ~/.zshenv or ~/.profile:".dimmed()); + println!(" {}", style("macOS:").dim()); + println!(" {}", style("Add to ~/.zshenv or ~/.profile:").dim()); println!(" . \"{env_path}/env\""); - println!(" {}", "Then restart your IDE to apply changes.".dimmed()); + println!(" {}", style("Then restart your IDE to apply changes.").dim()); } #[cfg(target_os = "linux")] { - println!(" {}", "Linux:".dimmed()); - println!(" {}", "Add to ~/.profile:".dimmed()); + println!(" {}", style("Linux:").dim()); + println!(" {}", style("Add to ~/.profile:").dim()); println!(" . \"{env_path}/env\""); - println!(" {}", "Then log out and log back in for changes to take effect.".dimmed()); + println!(" {}", style("Then log out and log back in for changes to take effect.").dim()); } // Fallback for other Unix platforms #[cfg(not(any(target_os = "macos", target_os = "linux")))] { - println!(" {}", "Add to your shell profile:".dimmed()); + println!(" {}", style("Add to your shell profile:").dim()); println!(" . \"{env_path}/env\""); - println!(" {}", "Then restart your IDE to apply changes.".dimmed()); + println!(" {}", style("Then restart your IDE to apply changes.").dim()); } } @@ -700,17 +711,17 @@ async fn check_current_resolution( if let Some(system_node) = system_node_path { let version = get_node_version(&system_node).await; print_check(" ", "Source", "system PATH"); - print_check(" ", "Version", &version.bright_green().to_string()); + print_check(" ", "Version", &style(&version).green().bright().to_string()); print_check( - &output::CHECK.green().to_string(), + &style(output::CHECK).green().to_string(), "Node binary", &system_node.as_path().display().to_string(), ); } else { print_check( - &output::WARN_SIGN.yellow().to_string(), + &style(output::WARN_SIGN).yellow().to_string(), "System Node.js", - &"not found in PATH".yellow().to_string(), + &style("not found in PATH").yellow().to_string(), ); print_hint("Install Node.js or run 'vp env on' to use managed Node.js."); } @@ -722,7 +733,7 @@ async fn check_current_resolution( let source_display = format_version_source(&resolution.source, resolution.source_path.as_deref()); print_check(" ", "Source", &source_display); - print_check(" ", "Version", &resolution.version.bright_green().to_string()); + print_check(" ", "Version", &style(&resolution.version).green().bright().to_string()); // Check if Node.js is installed let home_dir = vp_shared::EnvConfig::get() @@ -738,12 +749,12 @@ async fn check_current_resolution( let binary_path = home_dir.join("bin").join("node"); if tokio::fs::try_exists(&binary_path).await.unwrap_or(false) { - print_check(&output::CHECK.green().to_string(), "Node binary", "installed"); + print_check(&style(output::CHECK).green().to_string(), "Node binary", "installed"); } else { print_check( - &output::WARN_SIGN.yellow().to_string(), + &style(output::WARN_SIGN).yellow().to_string(), "Node binary", - &"not installed".yellow().to_string(), + &style("not installed").yellow().to_string(), ); print_hint("Version will be downloaded on first use."); } @@ -751,9 +762,9 @@ async fn check_current_resolution( } Err(e) => { print_check( - &output::CROSS.red().to_string(), + &style(output::CROSS).red().to_string(), "Resolution", - &format!("failed: {e}").red().to_string(), + &style(format!("failed: {e}")).red().to_string(), ); None } @@ -844,9 +855,9 @@ async fn check_dev_engines( for finding in findings { if finding.warn { print_check( - &output::WARN_SIGN.yellow().to_string(), + &style(output::WARN_SIGN).yellow().to_string(), finding.key, - &finding.message.yellow().to_string(), + &style(&finding.message).yellow().to_string(), ); } else { print_check(" ", finding.key, &finding.message); @@ -1176,16 +1187,16 @@ fn check_conflicts() { print_section("Conflicts"); for manager in &conflicts { print_check( - &output::WARN_SIGN.yellow().to_string(), + &style(output::WARN_SIGN).yellow().to_string(), manager, - &format!( + &style(format!( "detected ({} is set)", KNOWN_VERSION_MANAGERS .iter() .find(|(n, _)| n == manager) .map(|(_, e)| *e) .unwrap_or("in PATH") - ) + )) .yellow() .to_string(), ); diff --git a/crates/vp_global_cli/src/commands/env/list.rs b/crates/vp_global_cli/src/commands/env/list.rs index 62624fa772..1931a07aad 100644 --- a/crates/vp_global_cli/src/commands/env/list.rs +++ b/crates/vp_global_cli/src/commands/env/list.rs @@ -1,6 +1,6 @@ use std::{collections::BTreeMap, process::ExitStatus}; -use owo_colors::OwoColorize; +use console::style; use serde::Serialize; use vp_pm_cli::{PackageManagerType, package_manager_bin_path, package_manager_install_dir}; use vt_path::AbsolutePathBuf; @@ -175,14 +175,14 @@ fn print_section(title: &str, versions: &[InstalledVersionJson], node: bool) { let suffix = if markers.is_empty() { String::new() } else if colorize { - format!(" {}", markers.join(" ").dimmed()) + format!(" {}", style(&markers.join(" ")).dim()) } else { format!(" {}", markers.join(" ")) }; let display = if node { format!("v{}", version.version) } else { version.version.clone() }; let line = format!("* {display}"); if version.current && colorize { - println!(" {}{suffix}", line.bright_blue()); + println!(" {}{suffix}", style(&line).blue().bright()); } else { println!(" {line}{suffix}"); } @@ -190,5 +190,5 @@ fn print_section(title: &str, versions: &[InstalledVersionJson], node: bool) { } pub(super) fn use_color() -> bool { - vp_shared::is_stdout_terminal() && std::env::var_os("NO_COLOR").is_none() + console::colors_enabled() } diff --git a/crates/vp_global_cli/src/commands/env/list_remote.rs b/crates/vp_global_cli/src/commands/env/list_remote.rs index 9758d953c2..a3ef2b0caf 100644 --- a/crates/vp_global_cli/src/commands/env/list_remote.rs +++ b/crates/vp_global_cli/src/commands/env/list_remote.rs @@ -1,7 +1,7 @@ use std::{collections::BTreeMap, process::ExitStatus}; +use console::style; use futures::future::try_join_all; -use owo_colors::OwoColorize; use serde::Serialize; use vp_js_runtime::{LtsInfo, NodeProvider, NodeVersionEntry}; use vp_pm_cli::{fetch_package_manager_versions, resolve_package_manager_version}; @@ -178,7 +178,7 @@ pub async fn execute( fn print_node_versions(versions: &[NodeVersionJson]) { if versions.is_empty() { - eprintln!(" {}", "No versions were found!".red()); + eprintln!(" {}", style("No versions were found!").for_stderr().red()); return; } @@ -224,18 +224,18 @@ fn format_remote_version( if colorize { let display = if current { - display.bright_blue().to_string() + style(&display).blue().bright().to_string() } else if installed { - display.green().to_string() + style(&display).green().to_string() } else { display.to_string() }; let annotation = if annotation.is_empty() { String::new() } else { - annotation.bright_blue().to_string() + style(&annotation).blue().bright().to_string() }; - let labels = if labels.is_empty() { labels } else { labels.dimmed().to_string() }; + let labels = if labels.is_empty() { labels } else { style(&labels).dim().to_string() }; format!("{display}{annotation}{labels}") } else { // Preserve installed state in redirected output, where color is unavailable. diff --git a/crates/vp_global_cli/src/commands/env/which.rs b/crates/vp_global_cli/src/commands/env/which.rs index 444764ebf1..33d975b787 100644 --- a/crates/vp_global_cli/src/commands/env/which.rs +++ b/crates/vp_global_cli/src/commands/env/which.rs @@ -9,7 +9,7 @@ use std::process::ExitStatus; use chrono::Local; -use owo_colors::OwoColorize; +use console::style; use vp_pm_cli::{ PackageManagerType, package_manager_bin_path, package_manager_install_dir, resolve_package_manager_version, @@ -60,7 +60,7 @@ pub async fn execute(cwd: AbsolutePathBuf, tool: &str) -> Result Result path, _ => { - output::error(&format!("binary '{}' not found", tool.bold())); + output::error(&format!("binary '{}' not found", style(tool).for_stderr().bold())); eprintln!("Package {} may need to be reinstalled.", bin_config.package); eprintln!("Run 'npm install -g {}' to recreate the link.", bin_config.package); return Ok(exit_status(1)); @@ -98,11 +98,15 @@ async fn execute_npm_link_binary(tool: &str, bin_config: &BinConfig) -> Result Result Result>() .join(", "); output::raw(&format!(" Bins: {}", bins)); diff --git a/crates/vp_global_cli/src/commands/global/outdated.rs b/crates/vp_global_cli/src/commands/global/outdated.rs index 178b5df238..91e2adb1ff 100644 --- a/crates/vp_global_cli/src/commands/global/outdated.rs +++ b/crates/vp_global_cli/src/commands/global/outdated.rs @@ -5,7 +5,7 @@ use std::{ process::ExitStatus, }; -use owo_colors::OwoColorize; +use console::style; use serde::Serialize; use vp_pm_cli::OutdatedFormat; @@ -290,23 +290,28 @@ fn print_list(packages: &[OutdatedPackage], long: bool) { println!(); } - println!("{} {}", package.name.bold(), "(global)".dimmed()); + println!("{} {}", style(&package.name).bold(), style("(global)").dim()); if package.wanted == package.latest { - println!("{} {} {}", package.current.dimmed(), "=>".dimmed(), package.wanted.bold()); + println!( + "{} {} {}", + style(&package.current).dim(), + style("=>").dim(), + style(&package.wanted).bold() + ); } else { println!( "{} {} {} {}", - package.current.dimmed(), - "=>".dimmed(), - package.wanted.bold(), - format!("(latest: {})", package.latest).dimmed() + style(&package.current).dim(), + style("=>").dim(), + style(&package.wanted).bold(), + style(format!("(latest: {})", package.latest)).dim() ); } if long { - println!("{} {}", "node".dimmed(), package.node); + println!("{} {}", style("node").dim(), package.node); if !package.bins.is_empty() { - println!("{} {}", "bins".dimmed(), package.bins.join(", ")); + println!("{} {}", style("bins").dim(), package.bins.join(", ")); } } } @@ -359,7 +364,7 @@ fn print_table(packages: &[OutdatedPackage], long: bool) { if long { println!( "{}{:>gap$}{:gap$}{:gap$}{:gap$}{:gap$}{}", - format!("{:gap$}{:gap$}{:gap$}{}", - format!("{:) -> Resultgap$}{:gap$}{}", - name.bright_blue(), + style(&name).blue().bright(), "", pkg.platform.node, "", diff --git a/crates/vp_global_cli/src/commands/implode.rs b/crates/vp_global_cli/src/commands/implode.rs index 9254010ca4..0b3992c841 100644 --- a/crates/vp_global_cli/src/commands/implode.rs +++ b/crates/vp_global_cli/src/commands/implode.rs @@ -6,7 +6,7 @@ use std::{ process::ExitStatus, }; -use owo_colors::OwoColorize; +use console::style; use rustc_hash::FxHashSet; use vp_shared::output; use vt_path::AbsolutePathBuf; @@ -325,7 +325,7 @@ fn confirm_implode( } } output::raw(""); - output::raw(&vt_str::format!("Type {} to confirm:", "uninstall".bold())); + output::raw(&vt_str::format!("Type {} to confirm:", style("uninstall").bold())); // String is needed here for read_line #[expect(clippy::disallowed_types)] diff --git a/crates/vp_global_cli/src/commands/upgrade/mod.rs b/crates/vp_global_cli/src/commands/upgrade/mod.rs index be397e5db4..758de45d6b 100644 --- a/crates/vp_global_cli/src/commands/upgrade/mod.rs +++ b/crates/vp_global_cli/src/commands/upgrade/mod.rs @@ -5,7 +5,7 @@ use std::process::ExitStatus; -use owo_colors::OwoColorize; +use console::style; use vp_pm_cli::HttpClient; use vp_setup::{install, integrity, platform, registry}; use vp_shared::output; @@ -77,7 +77,7 @@ pub async fn execute(options: UpgradeOptions) -> Result { // Step 4: Handle --check (report and exit) if options.check { if resolved.version == current_version { - println!("\n{} Already up to date ({})", output::CHECK.green(), current_version); + println!("\n{} Already up to date ({})", style(output::CHECK).green(), current_version); } else { println!("Update available: {} \u{2192} {}", current_version, resolved.version); println!("Run `vp upgrade` to update."); @@ -88,7 +88,7 @@ pub async fn execute(options: UpgradeOptions) -> Result { // Step 5: Handle already up-to-date if resolved.version == current_version && !options.force { if !options.silent { - println!("\n{} Already up to date ({})", output::CHECK.green(), current_version); + println!("\n{} Already up to date ({})", style(output::CHECK).green(), current_version); } return Ok(ExitStatus::default()); } @@ -228,7 +228,7 @@ async fn install_platform_and_main( if !silent { println!( "\n{} Updated vite-plus from {} {} {}", - output::CHECK.green(), + style(output::CHECK).green(), current_version, output::ARROW, new_version @@ -277,7 +277,7 @@ async fn execute_rollback( install::refresh_shims(install_dir).await?; if !silent { - println!("\n{} Rolled back to {}", output::CHECK.green(), previous); + println!("\n{} Rolled back to {}", style(output::CHECK).green(), previous); } Ok(ExitStatus::default()) diff --git a/crates/vp_global_cli/src/main.rs b/crates/vp_global_cli/src/main.rs index 61a6d43dbf..5f4cb4d341 100644 --- a/crates/vp_global_cli/src/main.rs +++ b/crates/vp_global_cli/src/main.rs @@ -31,7 +31,7 @@ use std::{ use clap::error::{ContextKind, ContextValue}; use clap_complete::env::CompleteEnv; -use owo_colors::OwoColorize; +use console::style; use vp_shared::{exit_code_from_status, output}; pub use crate::cli::try_parse_args_from; @@ -178,13 +178,15 @@ fn extract_invalid_subcommand_details(error: &clap::Error) -> Option bool { } eprintln!(); - let highlighted_suggestion = format!("`vp {suggestion}`").bright_blue().to_string(); + let highlighted_suggestion = + style(format!("`vp {suggestion}`")).for_stderr().blue().bright().to_string(); eprint!("Do you want to run {highlighted_suggestion}? (y/N): "); if std::io::stderr().flush().is_err() { return false; @@ -333,14 +336,14 @@ fn print_unknown_argument_error(error: &clap::Error) -> bool { vp_shared::header::print_header(); - let highlighted_argument = invalid_argument.bright_blue().to_string(); + let highlighted_argument = style(&invalid_argument).for_stderr().blue().bright().to_string(); output::error(&format!("Unexpected argument '{highlighted_argument}'")); if has_pass_as_value_suggestion(error) { eprintln!(); let pass_through_argument = format!("-- {invalid_argument}"); let highlighted_pass_through_argument = - format!("`{}`", pass_through_argument.bright_blue()); + format!("`{}`", style(&pass_through_argument).for_stderr().blue().bright()); eprintln!("Use {highlighted_pass_through_argument} to pass the argument as a value"); } diff --git a/crates/vp_global_cli/src/upgrade_check.rs b/crates/vp_global_cli/src/upgrade_check.rs index 61a09516ed..1fe991c285 100644 --- a/crates/vp_global_cli/src/upgrade_check.rs +++ b/crates/vp_global_cli/src/upgrade_check.rs @@ -12,7 +12,7 @@ use std::{ time::{SystemTime, UNIX_EPOCH}, }; -use owo_colors::OwoColorize; +use console::style; use serde::{Deserialize, Serialize}; use vp_setup::registry; @@ -308,12 +308,12 @@ pub fn display_cached_upgrade_notice() { eprintln!( "\n{} {} {} {}{} {}", - "vp update available:".bright_black(), - current_version.bright_black(), - "\u{2192}".bright_black(), - cache.latest.bright_green().bold(), - ", run".bright_black(), - "vp upgrade".bright_green().bold(), + style("vp update available:").for_stderr().black().bright(), + style(¤t_version).for_stderr().black().bright(), + style("\u{2192}").for_stderr().black().bright(), + style(&cache.latest).for_stderr().green().bright().bold(), + style(", run").for_stderr().black().bright(), + style("vp upgrade").for_stderr().green().bright().bold(), ); cache.prompted_at = now; diff --git a/crates/vp_shared/Cargo.toml b/crates/vp_shared/Cargo.toml index bc7e4a24fb..ecc58afb7d 100644 --- a/crates/vp_shared/Cargo.toml +++ b/crates/vp_shared/Cargo.toml @@ -17,7 +17,7 @@ test-utils = ["dep:temp-env", "dep:tempfile"] [dependencies] directories = { workspace = true } nix = { workspace = true, features = ["fs", "poll", "term"] } -owo-colors = { workspace = true } +console = { workspace = true } serde = { workspace = true } # use `preserve_order` feature to preserve the order of the fields in `package.json` serde_json = { workspace = true, features = ["preserve_order"] } diff --git a/crates/vp_shared/src/output.rs b/crates/vp_shared/src/output.rs index add79395bb..30cf4c6a0d 100644 --- a/crates/vp_shared/src/output.rs +++ b/crates/vp_shared/src/output.rs @@ -1,11 +1,12 @@ //! Shared CLI output formatting for consistent message prefixes and status symbols. //! //! All commands should use these functions instead of ad-hoc formatting to ensure -//! consistent output across the entire CLI. +//! consistent output across the entire CLI. Styling uses console's color detection +//! for the stream receiving each message. use std::sync::atomic::{AtomicBool, Ordering}; -use owo_colors::OwoColorize; +use console::style; /// When set, user-facing stdout output (info/pass/note/success/raw) is routed /// to stderr instead. Shim dispatch enables this once at entry: a shim's @@ -39,9 +40,9 @@ pub const ARROW: &str = "\u{2192}"; #[expect(clippy::print_stdout, clippy::print_stderr, clippy::disallowed_macros)] pub fn info(msg: &str) { if user_output_to_stderr() { - eprintln!("{} {msg}", "info:".bright_blue().bold()); + eprintln!("{} {msg}", style("info:").for_stderr().blue().bright().bold()); } else { - println!("{} {msg}", "info:".bright_blue().bold()); + println!("{} {msg}", style("info:").blue().bright().bold()); } } @@ -49,22 +50,22 @@ pub fn info(msg: &str) { #[expect(clippy::print_stdout, clippy::print_stderr, clippy::disallowed_macros)] pub fn pass(msg: &str) { if user_output_to_stderr() { - eprintln!("{} {msg}", "pass:".bright_blue().bold()); + eprintln!("{} {msg}", style("pass:").for_stderr().blue().bright().bold()); } else { - println!("{} {msg}", "pass:".bright_blue().bold()); + println!("{} {msg}", style("pass:").blue().bright().bold()); } } /// Print a warning message to stderr. #[expect(clippy::print_stderr, clippy::disallowed_macros)] pub fn warn(msg: &str) { - eprintln!("{} {msg}", "warn:".yellow().bold()); + eprintln!("{} {msg}", style("warn:").for_stderr().yellow().bold()); } /// Print an error message to stderr. #[expect(clippy::print_stderr, clippy::disallowed_macros)] pub fn error(msg: &str) { - eprintln!("{} {msg}", "error:".red().bold()); + eprintln!("{} {msg}", style("error:").for_stderr().red().bold()); } /// Print a note message to stderr (supplementary info). @@ -74,16 +75,16 @@ pub fn error(msg: &str) { /// or a parser keeps the command's own output intact. #[expect(clippy::print_stderr, clippy::disallowed_macros)] pub fn note(msg: &str) { - eprintln!("{} {msg}", "note:".dimmed().bold()); + eprintln!("{} {msg}", style("note:").for_stderr().dim().bold()); } /// Print a success line with checkmark to stdout. #[expect(clippy::print_stdout, clippy::print_stderr, clippy::disallowed_macros)] pub fn success(msg: &str) { if user_output_to_stderr() { - eprintln!("{} {msg}", CHECK.green()); + eprintln!("{} {msg}", style(CHECK).for_stderr().green()); } else { - println!("{} {msg}", CHECK.green()); + println!("{} {msg}", style(CHECK).green()); } } diff --git a/packages/cli/binding/Cargo.toml b/packages/cli/binding/Cargo.toml index cc0f3bfd65..e46bb18a41 100644 --- a/packages/cli/binding/Cargo.toml +++ b/packages/cli/binding/Cargo.toml @@ -21,7 +21,7 @@ rustc-hash = { workspace = true } napi = { workspace = true } napi-derive = { workspace = true } petgraph = { workspace = true } -owo-colors = { workspace = true } +console = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } tokio = { workspace = true, features = ["fs"] } diff --git a/packages/cli/binding/src/check/analysis.rs b/packages/cli/binding/src/check/analysis.rs index 8a9ea32694..77ec38b2ed 100644 --- a/packages/cli/binding/src/check/analysis.rs +++ b/packages/cli/binding/src/check/analysis.rs @@ -1,4 +1,4 @@ -use owo_colors::OwoColorize; +use console::style; use vp_shared::output; #[derive(Debug, Clone)] @@ -163,7 +163,7 @@ pub(super) fn print_summary_line(message: &str) { let mut is_accent = true; for segment in segments { if is_accent { - formatted.push_str(&format!("{}", format!("`{segment}`").bright_blue())); + formatted.push_str(&format!("{}", style(format!("`{segment}`")).blue().bright())); } else { formatted.push_str(segment); } @@ -185,7 +185,11 @@ pub(super) fn print_error_block(error_msg: &str, combined_output: &str, summary_ pub(super) fn print_pass_line(message: &str, detail: Option<&str>) { if let Some(detail) = detail { - output::raw(&format!("{} {message} {}", "pass:".bright_blue().bold(), detail.dimmed())); + output::raw(&format!( + "{} {message} {}", + style("pass:").blue().bright().bold(), + style(&detail).dim() + )); } else { output::pass(message); } diff --git a/packages/cli/binding/src/cli/execution.rs b/packages/cli/binding/src/cli/execution.rs index c0ad16f4fe..0d90f92c06 100644 --- a/packages/cli/binding/src/cli/execution.rs +++ b/packages/cli/binding/src/cli/execution.rs @@ -129,7 +129,12 @@ pub(crate) async fn resolve_and_capture_output( resolve_and_build_command(resolver, subcommand, resolved_vite_config, envs, cwd).await?; cmd.stdout(Stdio::piped()); cmd.stderr(Stdio::piped()); - if force_color_if_terminal && vp_shared::is_stdout_terminal() { + // Capturing output hides the terminal from the child. Preserve colors only when + // the parent supports them, without overriding an explicit FORCE_COLOR value. + if force_color_if_terminal + && console::colors_enabled() + && !cmd.as_std().get_envs().any(|(key, _)| key == "FORCE_COLOR") + { cmd.env("FORCE_COLOR", "1"); } diff --git a/packages/cli/binding/src/cli/help.rs b/packages/cli/binding/src/cli/help.rs index daf2f765aa..431445cc1f 100644 --- a/packages/cli/binding/src/cli/help.rs +++ b/packages/cli/binding/src/cli/help.rs @@ -1,5 +1,5 @@ use clap::error::{ContextKind, ContextValue, ErrorKind}; -use owo_colors::OwoColorize; +use console::style; use vp_error::Error; use vp_shared::output; use vt::ExitStatus; @@ -120,19 +120,22 @@ fn print_invalid_subcommand_error(error: &clap::Error) -> bool { }; if GLOBAL_ONLY_SUBCOMMANDS.contains(&invalid_subcommand.as_str()) { - let command = format!("`{invalid_subcommand}`").bright_blue().to_string(); + let command = + style(format!("`{invalid_subcommand}`")).for_stderr().blue().bright().to_string(); output::error(&format!( "The {command} command is only available in the global `vp` CLI. See https://viteplus.dev/guide/ to install it, then run the same command via the global `vp` binary." )); return true; } - let highlighted_subcommand = invalid_subcommand.bright_blue().to_string(); + let highlighted_subcommand = + style(&invalid_subcommand).for_stderr().blue().bright().to_string(); output::error(&format!("Command '{highlighted_subcommand}' not found")); if let Some(suggestion) = suggestion { eprintln!(); - let highlighted_suggestion = format!("`vp {suggestion}`").bright_blue().to_string(); + let highlighted_suggestion = + style(format!("`vp {suggestion}`")).for_stderr().blue().bright().to_string(); eprintln!("Did you mean {highlighted_suggestion}?"); } @@ -169,14 +172,14 @@ fn print_unknown_argument_error(error: &clap::Error) -> bool { return false; }; - let highlighted_argument = invalid_argument.bright_blue().to_string(); + let highlighted_argument = style(&invalid_argument).for_stderr().blue().bright().to_string(); output::error(&format!("Unexpected argument '{highlighted_argument}'")); if has_pass_as_value_suggestion(error) { eprintln!(); let pass_through_argument = format!("-- {invalid_argument}"); let highlighted_pass_through_argument = - format!("`{}`", pass_through_argument.bright_blue()); + format!("`{}`", style(&pass_through_argument).for_stderr().blue().bright()); eprintln!("Use {highlighted_pass_through_argument} to pass the argument as a value"); } diff --git a/packages/cli/binding/src/cli/script_note.rs b/packages/cli/binding/src/cli/script_note.rs index ec1d812302..299293433d 100644 --- a/packages/cli/binding/src/cli/script_note.rs +++ b/packages/cli/binding/src/cli/script_note.rs @@ -5,7 +5,7 @@ //! built-in when they meant the script, so a built-in whose name a script also //! uses points at `vpr`. -use owo_colors::OwoColorize; +use console::style; use vp_shared::output; use vt::MARKER_ENV_NAME; use vt_path::AbsolutePath; @@ -32,8 +32,8 @@ pub(super) fn print(command: Option<&str>, cwd: &AbsolutePath) { return; } - let built_in = format!("`vp {command}`").bright_blue().to_string(); - let via_run = format!("`vpr {command}`").bright_blue().to_string(); + let built_in = style(format!("`vp {command}`")).for_stderr().blue().bright().to_string(); + let via_run = style(format!("`vpr {command}`")).for_stderr().blue().bright().to_string(); output::note(&format!( "You are running {built_in} as a Vite+ built-in command. \ If you meant to run the {command} npm script, use {via_run} instead." diff --git a/packages/cli/binding/src/exec/workspace.rs b/packages/cli/binding/src/exec/workspace.rs index 666c6c76ee..e9573dbe42 100644 --- a/packages/cli/binding/src/exec/workspace.rs +++ b/packages/cli/binding/src/exec/workspace.rs @@ -1,6 +1,6 @@ use std::{collections::BTreeMap, process::Stdio, sync::Arc}; -use owo_colors::OwoColorize; +use console::style; use petgraph::prelude::DiGraphMap; use vp_error::Error; use vp_shared::{PrependOptions, ToolPathEnv}; @@ -233,9 +233,9 @@ pub(super) async fn execute_exec_workspace( ) { Ok(cmd) => cmd, Err(Error::CannotFindBinaryPath(_)) if single_package => { - let command = args.command[0].bright_blue().to_string(); - let vp_install = "`vp install`".bright_blue().to_string(); - let vpx = "`vpx`".bright_blue().to_string(); + let command = style(&args.command[0]).for_stderr().blue().bright().to_string(); + let vp_install = style("`vp install`").for_stderr().blue().bright().to_string(); + let vpx = style("`vpx`").for_stderr().blue().bright().to_string(); vp_shared::output::error(&vt_str::format!( "Command '{}' not found in node_modules/.bin\n\n\ Run {} to install dependencies, or use {} for invoking remote commands.", From d6d59b5063ddec5feeb5b83b409b3c299d241cb5 Mon Sep 17 00:00:00 2001 From: Liang Date: Wed, 16 Sep 2026 06:20:47 +0800 Subject: [PATCH 2/4] docs: update CLI color library references --- rfcs/cli-output-polish.md | 48 +++++++++++++++++++-------------------- rfcs/windows-installer.md | 2 +- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/rfcs/cli-output-polish.md b/rfcs/cli-output-polish.md index 76004e09c2..bc1107e1ee 100644 --- a/rfcs/cli-output-polish.md +++ b/rfcs/cli-output-polish.md @@ -54,7 +54,7 @@ Neither identifies the experience as "Vite+". Users who installed `vite-plus` se | Layer | Library | | ------------------ | ----------------------- | -| Rust (global CLI) | `owo_colors` | +| Rust (global CLI) | `console` | | JS (vite-plus CLI) | `node:util styleText()` | | vite | `picocolors` | @@ -226,7 +226,7 @@ A full audit of vite source for user-visible "vite" strings: Add formatting functions to a shared location. This could be a new `vite_output` crate or a module within an existing shared crate. ```rust -use owo_colors::OwoColorize; +use console::style; // Standard status symbols pub const CHECK: &str = "\u{2713}"; // ✓ — success @@ -236,27 +236,27 @@ pub const ARROW: &str = "\u{2192}"; // → — transitions /// Print an info message to stderr. pub fn info(msg: &str) { - eprintln!("{} {}", "info:".bright_blue().bold(), msg); + eprintln!("{} {}", style("info:").for_stderr().blue().bright().bold(), msg); } /// Print a warning message to stderr. pub fn warn(msg: &str) { - eprintln!("{} {}", "warn:".yellow().bold(), msg); + eprintln!("{} {}", style("warn:").for_stderr().yellow().bold(), msg); } /// Print an error message to stderr. pub fn error(msg: &str) { - eprintln!("{} {}", "error:".red().bold(), msg); + eprintln!("{} {}", style("error:").for_stderr().red().bold(), msg); } /// Print a note message to stderr (supplementary info). pub fn note(msg: &str) { - eprintln!("{} {}", "note:".dimmed().bold(), msg); + eprintln!("{} {}", style("note:").for_stderr().dim().bold(), msg); } /// Print a success line with checkmark to stdout. pub fn success(msg: &str) { - println!("{} {}", CHECK.green(), msg); + println!("{} {}", style(CHECK).green(), msg); } ``` @@ -279,20 +279,20 @@ Adopt a single set everywhere: Commands to update (representative, not exhaustive): -| File | Current | New | -| -------------------- | ------------------------------------- | ---------------------------------- | -| `upgrade/mod.rs:58` | `eprintln!("info: checking...")` | `output::info("checking...")` | -| `upgrade/mod.rs:69` | `eprintln!("info: found...")` | `output::info("found...")` | -| `upgrade/mod.rs:173` | `eprintln!("warn: Shim refresh...")` | `output::warn("Shim refresh...")` | -| `upgrade/mod.rs:75` | `"\u{2714}".green()` | `output::CHECK.green()` | -| `main.rs:75` | `eprintln!("Error: Failed...")` | `output::error("Failed...")` | -| `main.rs:121` | `eprintln!("Error: {e}")` | `output::error(...)` | -| `vpx.rs:72` | `eprintln!("Error: vpx requires...")` | `output::error("vpx requires...")` | -| `which.rs:40` | `"error:".red().bold()` | `output::error(...)` | -| `pin.rs:142` | `println!(" Note: Version...")` | `output::note("Version...")` | -| `pin.rs:155` | `eprintln!("Warning: Failed...")` | `output::warn("Failed...")` | -| `dlx.rs:167` | `eprintln!("Warning: yarn dlx...")` | `output::warn("yarn dlx...")` | -| `dlx.rs:184` | `eprintln!("Note: yarn@1...")` | `output::note("yarn@1...")` | +| File | Current | New | +| -------------------- | ------------------------------------------- | ---------------------------------- | +| `upgrade/mod.rs:58` | `eprintln!("info: checking...")` | `output::info("checking...")` | +| `upgrade/mod.rs:69` | `eprintln!("info: found...")` | `output::info("found...")` | +| `upgrade/mod.rs:173` | `eprintln!("warn: Shim refresh...")` | `output::warn("Shim refresh...")` | +| `upgrade/mod.rs:75` | `"\u{2714}".green()` | `style(output::CHECK).green()` | +| `main.rs:75` | `eprintln!("Error: Failed...")` | `output::error("Failed...")` | +| `main.rs:121` | `eprintln!("Error: {e}")` | `output::error(...)` | +| `vpx.rs:72` | `eprintln!("Error: vpx requires...")` | `output::error("vpx requires...")` | +| `which.rs:40` | `style("error:").for_stderr().red().bold()` | `output::error(...)` | +| `pin.rs:142` | `println!(" Note: Version...")` | `output::note("Version...")` | +| `pin.rs:155` | `eprintln!("Warning: Failed...")` | `output::warn("Failed...")` | +| `dlx.rs:167` | `eprintln!("Warning: yarn dlx...")` | `output::warn("yarn dlx...")` | +| `dlx.rs:184` | `eprintln!("Note: yarn@1...")` | `output::note("yarn@1...")` | The `vite_install` crate also has `Warning:` and `Note:` messages across multiple command files (`list.rs`, `why.rs`, `outdated.rs`, `pack.rs`, `publish.rs`, `cache.rs`, `config.rs`, `audit.rs`, `dlx.rs`, `unlink.rs`, `update.rs`, `rebuild.rs`, `whoami.rs`). All should be migrated. @@ -395,11 +395,11 @@ Migrate JS-side code (`migration/bin.ts`, `create/bin.ts`) to use these shared f **Rationale:** Parsing or wrapping sub-tool stdout/stderr is fragile and can break ANSI colors, progress indicators, and interactive output. A single leading line is non-intrusive. Long-term, these sub-tools should be directly modified once their source is cloned. -### D6: Keep each layer's color library +### D6: Use each layer's color detection -**Decision:** Rust keeps `owo_colors`, JS keeps `node:util styleText()`, vite keeps `picocolors`. +**Decision:** Rust uses `console`, JS uses `node:util styleText()`, and vite uses `picocolors`. -**Rationale:** Changing color libraries is high-risk, low-reward. The shared formatting module abstracts the library choice so the output convention is consistent regardless of the underlying library. +**Rationale:** These libraries already handle terminal color preferences such as `NO_COLOR`. Rust output uses the same library as the installer, prompts, and progress bars, with stream-specific detection for stdout and stderr. Vite+ relies on this detection rather than maintaining a separate color policy. ## Scope of vite Changes diff --git a/rfcs/windows-installer.md b/rfcs/windows-installer.md index eb3fd97380..ea9bb73832 100644 --- a/rfcs/windows-installer.md +++ b/rfcs/windows-installer.md @@ -119,7 +119,7 @@ vp_installer (binary, ~3-5 MB) ├── clap (CLI parsing) ├── tokio (async runtime) ├── indicatif (progress bars) - └── owo-colors (terminal colors) + └── console (terminal colors) vp_global_cli (existing) ├── vp_setup (replaces inline upgrade code) From 6698961f72cb1a4a66cfb27ca10a690c0ec17747 Mon Sep 17 00:00:00 2001 From: Liang Date: Wed, 16 Sep 2026 21:06:39 +0800 Subject: [PATCH 3/4] fix(ci): refresh color snapshots and update rustls --- Cargo.lock | 4 ++-- .../app_root_listing/snapshots/listing.global.md | 6 +++--- .../app_root_listing/snapshots/listing.local.md | 6 +++--- .../snapshots/script_at_root_elicits.global.md | 2 +- .../snapshots/script_at_root_elicits.local.md | 2 +- .../snapshots/builtin_script_note.global.md | 2 +- .../snapshots/builtin_script_note.local.md | 2 +- .../check_backpressure_nonblocking_stdout.global.md | 4 ++-- .../check_backpressure_nonblocking_stdout.local.md | 4 ++-- .../snapshots/command_outdated_global.md | 8 ++++---- .../snapshots/command_run_with_vp_config.md | 4 ++-- .../snapshots/command_update_global_dev_engines.md | 4 ++-- .../snapshots/command_update_node_mismatch.md | 10 +++++----- .../command_update_node_mismatch_install_failure.md | 4 ++-- 14 files changed, 31 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 96fd00b6cd..77444f7b6c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6969,9 +6969,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.44" +version = "0.23.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6725596c3f2c3a0aef021139e145d4eafe314a6623e4680ca83852b2c67ab2ba" +checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634" dependencies = [ "once_cell", "ring", diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/app_root_listing/snapshots/listing.global.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/app_root_listing/snapshots/listing.global.md index c140784432..88d989ca1d 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/app_root_listing/snapshots/listing.global.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/app_root_listing/snapshots/listing.global.md @@ -9,8 +9,8 @@ root (rfcs/cwd-flag.md). **Exit code:** 1 ``` -note: You are running `vp build` as a Vite+ built-in command. If you meant to run the build npm script, use `vpr build` instead. -error: `vp build` at the workspace root needs a target package. +note: You are running `vp build` as a Vite+ built-in command. If you meant to run the build npm script, use `vpr build` instead. +error: `vp build` at the workspace root needs a target package. Packages in this workspace: admin apps/admin @@ -29,7 +29,7 @@ dev at the root no longer starts a server against the root **Exit code:** 1 ``` -error: `vp dev` at the workspace root needs a target package. +error: `vp dev` at the workspace root needs a target package. Packages in this workspace: admin apps/admin diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/app_root_listing/snapshots/listing.local.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/app_root_listing/snapshots/listing.local.md index c140784432..88d989ca1d 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/app_root_listing/snapshots/listing.local.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/app_root_listing/snapshots/listing.local.md @@ -9,8 +9,8 @@ root (rfcs/cwd-flag.md). **Exit code:** 1 ``` -note: You are running `vp build` as a Vite+ built-in command. If you meant to run the build npm script, use `vpr build` instead. -error: `vp build` at the workspace root needs a target package. +note: You are running `vp build` as a Vite+ built-in command. If you meant to run the build npm script, use `vpr build` instead. +error: `vp build` at the workspace root needs a target package. Packages in this workspace: admin apps/admin @@ -29,7 +29,7 @@ dev at the root no longer starts a server against the root **Exit code:** 1 ``` -error: `vp dev` at the workspace root needs a target package. +error: `vp dev` at the workspace root needs a target package. Packages in this workspace: admin apps/admin diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/app_root_listing/snapshots/script_at_root_elicits.global.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/app_root_listing/snapshots/script_at_root_elicits.global.md index c03898f8c9..217ebe6e73 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/app_root_listing/snapshots/script_at_root_elicits.global.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/app_root_listing/snapshots/script_at_root_elicits.global.md @@ -11,7 +11,7 @@ listing and the task fails instead of silently building the root. ``` $ vp build ⊘ cache disabled -error: `vp build` at the workspace root needs a target package. +error: `vp build` at the workspace root needs a target package. Packages in this workspace: admin apps/admin diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/app_root_listing/snapshots/script_at_root_elicits.local.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/app_root_listing/snapshots/script_at_root_elicits.local.md index c03898f8c9..217ebe6e73 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/app_root_listing/snapshots/script_at_root_elicits.local.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/app_root_listing/snapshots/script_at_root_elicits.local.md @@ -11,7 +11,7 @@ listing and the task fails instead of silently building the root. ``` $ vp build ⊘ cache disabled -error: `vp build` at the workspace root needs a target package. +error: `vp build` at the workspace root needs a target package. Packages in this workspace: admin apps/admin diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/builtin_script_note/snapshots/builtin_script_note.global.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/builtin_script_note/snapshots/builtin_script_note.global.md index 6f7b02d348..6c68285ce4 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/builtin_script_note/snapshots/builtin_script_note.global.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/builtin_script_note/snapshots/builtin_script_note.global.md @@ -134,5 +134,5 @@ the note still reaches piped output, such as an AI agent capturing the command; ``` Found 0 warnings and 0 errors. Finished in on 1 file with rules using threads. -note: You are running `vp lint` as a Vite+ built-in command. If you meant to run the lint npm script, use `vpr lint` instead. +note: You are running `vp lint` as a Vite+ built-in command. If you meant to run the lint npm script, use `vpr lint` instead. ``` diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/builtin_script_note/snapshots/builtin_script_note.local.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/builtin_script_note/snapshots/builtin_script_note.local.md index fd668f3386..a02f2ab1f8 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/builtin_script_note/snapshots/builtin_script_note.local.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/builtin_script_note/snapshots/builtin_script_note.local.md @@ -120,5 +120,5 @@ the note still reaches piped output, such as an AI agent capturing the command; ``` Found 0 warnings and 0 errors. Finished in on 1 file with rules using threads. -note: You are running `vp lint` as a Vite+ built-in command. If you meant to run the lint npm script, use `vpr lint` instead. +note: You are running `vp lint` as a Vite+ built-in command. If you meant to run the lint npm script, use `vpr lint` instead. ``` diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/check_backpressure/snapshots/check_backpressure_nonblocking_stdout.global.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/check_backpressure/snapshots/check_backpressure_nonblocking_stdout.global.md index 6565962928..298e78835e 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/check_backpressure/snapshots/check_backpressure_nonblocking_stdout.global.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/check_backpressure/snapshots/check_backpressure_nonblocking_stdout.global.md @@ -7,7 +7,7 @@ vp check exposes the stdout EAGAIN failure when a large diagnostic replay meets ``` --- stdout --- stdout: 1282 lines -pass: All 3 files are correctly formatted (, threads) +pass: All 3 files are correctly formatted (, threads) ! eslint(no-unused-vars): Variable 'unused000' is declared but never used. Unused variables should start with a '_'. ,-[src/index.js:2:9] 1 | export function emitDiagnostics() { @@ -24,5 +24,5 @@ stdout: 1282 lines Found 0 errors and 128 warnings in 2 files (, threads) --- stderr --- stderr: 1 lines -warn: Lint warnings found +warn: Lint warnings found ``` diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/check_backpressure/snapshots/check_backpressure_nonblocking_stdout.local.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/check_backpressure/snapshots/check_backpressure_nonblocking_stdout.local.md index 6565962928..298e78835e 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/check_backpressure/snapshots/check_backpressure_nonblocking_stdout.local.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/check_backpressure/snapshots/check_backpressure_nonblocking_stdout.local.md @@ -7,7 +7,7 @@ vp check exposes the stdout EAGAIN failure when a large diagnostic replay meets ``` --- stdout --- stdout: 1282 lines -pass: All 3 files are correctly formatted (, threads) +pass: All 3 files are correctly formatted (, threads) ! eslint(no-unused-vars): Variable 'unused000' is declared but never used. Unused variables should start with a '_'. ,-[src/index.js:2:9] 1 | export function emitDiagnostics() { @@ -24,5 +24,5 @@ stdout: 1282 lines Found 0 errors and 128 warnings in 2 files (, threads) --- stderr --- stderr: 1 lines -warn: Lint warnings found +warn: Lint warnings found ``` diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_outdated_global/snapshots/command_outdated_global.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_outdated_global/snapshots/command_outdated_global.md index c49601c7df..270af9d6ef 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_outdated_global/snapshots/command_outdated_global.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_outdated_global/snapshots/command_outdated_global.md @@ -61,7 +61,7 @@ should warn and skip when the recorded version spec no longer resolves ``` All global packages are up to date. -warn: npm view failed for testnpm2@no-such-tag: npm error code E404; skipping +warn: npm view failed for testnpm2@no-such-tag: npm error code E404; skipping ``` ## `vpt json-edit $VP_HOME/packages/testnpm2.json versionSpec null` @@ -106,8 +106,8 @@ should override a recorded version spec with --latest ## `vp update -g --latest` ``` -info: Updating 1 global package with Node.js -✓ Updated testnpm2 to 1.0.1 +info: Updating 1 global package with Node.js +✓ Updated testnpm2 to 1.0.1 ``` ## `vpt grep-file $VP_HOME/packages/testnpm2.json versionSpec` @@ -168,7 +168,7 @@ should not persist an explicit spec that fails to resolve ``` All global packages are up to date. -warn: npm view failed for testnpm2@no-such-tag: npm error code E404; skipping +warn: npm view failed for testnpm2@no-such-tag: npm error code E404; skipping ``` ## `vpt grep-file $VP_HOME/packages/testnpm2.json 'versionSpec": "1.0.1'` diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_run_with_vp_config/snapshots/command_run_with_vp_config.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_run_with_vp_config/snapshots/command_run_with_vp_config.md index 5ea7a83806..3671b1f6d8 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_run_with_vp_config/snapshots/command_run_with_vp_config.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_run_with_vp_config/snapshots/command_run_with_vp_config.md @@ -18,7 +18,7 @@ should throw error ``` $ vp not-exist-command ⊘ cache disabled -error: Command 'not-exist-command' not found +error: Command 'not-exist-command' not found -Did you mean `vp test`? +Did you mean `vp test`? ``` diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_update_global_dev_engines/snapshots/command_update_global_dev_engines.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_update_global_dev_engines/snapshots/command_update_global_dev_engines.md index 4152afc4c0..b27df1c632 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_update_global_dev_engines/snapshots/command_update_global_dev_engines.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_update_global_dev_engines/snapshots/command_update_global_dev_engines.md @@ -8,6 +8,6 @@ Global updates ignore the current project's package-manager requirement. ## `vp update -g --latest` ``` -info: Updating 1 global package with Node.js -✓ Updated testnpm2 to 1.0.1 +info: Updating 1 global package with Node.js +✓ Updated testnpm2 to 1.0.1 ``` diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_update_node_mismatch/snapshots/command_update_node_mismatch.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_update_node_mismatch/snapshots/command_update_node_mismatch.md index fdc19c0090..ad46cefef5 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_update_node_mismatch/snapshots/command_update_node_mismatch.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_update_node_mismatch/snapshots/command_update_node_mismatch.md @@ -3,8 +3,8 @@ ## `vp install -g --node 20 testnpm2` ``` -info: Installing 1 global package with Node.js -✓ Installed testnpm2 1.0.1 +info: Installing 1 global package with Node.js +✓ Installed testnpm2 1.0.1 ``` ## `vp update -g testnpm2` @@ -13,7 +13,7 @@ should warn and skip node mismatch reinstall in CI ``` All global packages are up to date. -warn: Skipping reinstall for global packages installed with a different Node.js version: testnpm2. Use --reinstall-node-mismatch to reinstall them. +warn: Skipping reinstall for global packages installed with a different Node.js version: testnpm2. Use --reinstall-node-mismatch to reinstall them. ``` ## `vp update -g testnpm2 --ignore-node-mismatch` @@ -27,6 +27,6 @@ All global packages are up to date. ## `vp update -g testnpm2 --reinstall-node-mismatch` ``` -info: Updating 1 global package with Node.js -✓ Updated testnpm2 to 1.0.1 +info: Updating 1 global package with Node.js +✓ Updated testnpm2 to 1.0.1 ``` diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_update_node_mismatch/snapshots/command_update_node_mismatch_install_failure.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_update_node_mismatch/snapshots/command_update_node_mismatch_install_failure.md index ed5bfa90fb..f2974c1319 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_update_node_mismatch/snapshots/command_update_node_mismatch_install_failure.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_update_node_mismatch/snapshots/command_update_node_mismatch_install_failure.md @@ -13,8 +13,8 @@ should keep the recorded spec when the reinstall fails **Exit code:** 1 ``` -info: Updating 1 global package with Node.js -error: Failed to update semver: Executable 'semver' is already installed by conflicting-package +info: Updating 1 global package with Node.js +error: Failed to update semver: Executable 'semver' is already installed by conflicting-package Please remove conflicting-package before installing semver, or use --force to auto-replace ``` From 99f7c1bd3831282ad98bc1d0802b631a9ccb4563 Mon Sep 17 00:00:00 2001 From: Liang Date: Wed, 16 Sep 2026 22:07:34 +0800 Subject: [PATCH 4/4] ci: rerun checks for color output fix