From 656c2360d819f11f09bde80d6d6af56e93f65588 Mon Sep 17 00:00:00 2001 From: Liang Mi Date: Fri, 10 Jul 2026 11:00:57 +0800 Subject: [PATCH 1/3] fix: avoid debug formatting in user-facing diagnostics Co-authored-by: GPT-5 Codex --- crates/fspy/src/error.rs | 26 ++++++++++++-- .../error_invalid_package_json/package.json | 3 ++ .../packages/broken/package.invalid | 3 ++ .../pnpm-workspace.yaml | 2 ++ .../error_invalid_package_json/snapshots.toml | 18 ++++++++++ .../snapshots/invalid_package_json_error.md | 19 +++++++++++ crates/vite_task_graph/src/lib.rs | 27 +++++++++++++-- crates/vite_task_plan/src/error.rs | 34 +++++++++++++++++-- crates/vite_task_server/src/lib.rs | 2 +- crates/vite_task_server/tests/integration.rs | 2 ++ crates/vite_workspace/src/error.rs | 12 ++++--- 11 files changed, 135 insertions(+), 13 deletions(-) create mode 100644 crates/vite_task_bin/tests/e2e_snapshots/fixtures/error_invalid_package_json/package.json create mode 100644 crates/vite_task_bin/tests/e2e_snapshots/fixtures/error_invalid_package_json/packages/broken/package.invalid create mode 100644 crates/vite_task_bin/tests/e2e_snapshots/fixtures/error_invalid_package_json/pnpm-workspace.yaml create mode 100644 crates/vite_task_bin/tests/e2e_snapshots/fixtures/error_invalid_package_json/snapshots.toml create mode 100644 crates/vite_task_bin/tests/e2e_snapshots/fixtures/error_invalid_package_json/snapshots/invalid_package_json_error.md diff --git a/crates/fspy/src/error.rs b/crates/fspy/src/error.rs index 017d82a0e..20d6a3541 100644 --- a/crates/fspy/src/error.rs +++ b/crates/fspy/src/error.rs @@ -1,9 +1,16 @@ -use std::{ffi::OsString, path::PathBuf}; +use std::{ + ffi::{OsStr, OsString}, + fmt, + path::PathBuf, +}; #[derive(thiserror::Error, Debug)] pub enum SpawnError { #[error( - "could not resolve the full path of program '{program:?}' with PATH={path:?} under cwd({cwd:?})" + "could not resolve the full path of program '{}' with PATH={} under cwd({})", + .program.display(), + display_path_env(.path.as_deref()), + .cwd.display() )] Which { program: OsString, @@ -27,3 +34,18 @@ pub enum SpawnError { #[error("underlying os error: {0}")] OsSpawn(std::io::Error), } + +struct DisplayPathEnv<'a>(Option<&'a OsStr>); + +impl fmt::Display for DisplayPathEnv<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.0 { + Some(path) => fmt::Display::fmt(&path.display(), f), + None => f.write_str(""), + } + } +} + +const fn display_path_env(path: Option<&OsStr>) -> DisplayPathEnv<'_> { + DisplayPathEnv(path) +} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/error_invalid_package_json/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/error_invalid_package_json/package.json new file mode 100644 index 000000000..3feb448b8 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/error_invalid_package_json/package.json @@ -0,0 +1,3 @@ +{ + "name": "invalid-package-json" +} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/error_invalid_package_json/packages/broken/package.invalid b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/error_invalid_package_json/packages/broken/package.invalid new file mode 100644 index 000000000..f830e8b60 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/error_invalid_package_json/packages/broken/package.invalid @@ -0,0 +1,3 @@ +{ + "name": +} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/error_invalid_package_json/pnpm-workspace.yaml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/error_invalid_package_json/pnpm-workspace.yaml new file mode 100644 index 000000000..924b55f42 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/error_invalid_package_json/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +packages: + - packages/* diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/error_invalid_package_json/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/error_invalid_package_json/snapshots.toml new file mode 100644 index 000000000..dfa058f00 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/error_invalid_package_json/snapshots.toml @@ -0,0 +1,18 @@ +[[e2e]] +name = "invalid_package_json_error" +comment = """ +Tests that package parse errors display paths without Rust debug formatting +""" +steps = [ + [ + "vtt", + "cp", + "packages/broken/package.invalid", + "packages/broken/package.json", + ], + [ + "vt", + "run", + "build", + ], +] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/error_invalid_package_json/snapshots/invalid_package_json_error.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/error_invalid_package_json/snapshots/invalid_package_json_error.md new file mode 100644 index 000000000..a104318d5 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/error_invalid_package_json/snapshots/invalid_package_json_error.md @@ -0,0 +1,19 @@ +# invalid_package_json_error + +Tests that package parse errors display paths without Rust debug formatting + +## `vtt cp packages/broken/package.invalid packages/broken/package.json` + +``` +``` + +## `vt run build` + +**Exit code:** 1 + +``` +error: Failed to load task graph +* Failed to load package graph +* Failed to parse JSON file at /packages/broken/package.json +* expected value at line 3 column 1 +``` diff --git a/crates/vite_task_graph/src/lib.rs b/crates/vite_task_graph/src/lib.rs index 72dc950ac..8bd733f74 100644 --- a/crates/vite_task_graph/src/lib.rs +++ b/crates/vite_task_graph/src/lib.rs @@ -4,7 +4,7 @@ pub mod loader; pub mod query; mod specifier; -use std::{convert::Infallible, sync::Arc}; +use std::{convert::Infallible, fmt, sync::Arc}; use config::{ ResolvedGlobalCacheConfig, ResolvedTaskConfig, UserRunConfig, UserTaskConfig, @@ -93,7 +93,7 @@ pub enum TaskGraphLoadError { #[error("Failed to load package graph")] PackageGraphLoadError(#[from] vite_workspace::Error), - #[error("Failed to load task config file for package at {package_path:?}")] + #[error("Failed to load task config file for package at {}", .package_path.as_path().display())] ConfigLoadError { package_path: Arc, #[source] @@ -138,7 +138,10 @@ pub enum TaskGraphLoadError { /// - When the specifier is from a CLI command, `UnknownPackageError` can be a real error type in case cwd is not in any package. #[derive(Debug, thiserror::Error, Serialize)] pub enum SpecifierLookupError { - #[error("Package '{package_name}' is ambiguous among multiple packages: {package_paths:?}")] + #[error( + "Package '{package_name}' is ambiguous among multiple packages: {}", + display_package_paths(.package_paths) + )] AmbiguousPackageName { package_name: Str, package_paths: Box<[Arc]> }, #[error("Package '{package_name}' not found")] @@ -158,6 +161,24 @@ pub enum SpecifierLookupError { PackageUnknown { unspecifier_package_error: PackageUnknownError, task_name: Str }, } +struct DisplayPackagePaths<'a>(&'a [Arc]); + +impl fmt::Display for DisplayPackagePaths<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for (index, path) in self.0.iter().enumerate() { + if index > 0 { + f.write_str(", ")?; + } + fmt::Display::fmt(&path.as_path().display(), f)?; + } + Ok(()) + } +} + +const fn display_package_paths(paths: &[Arc]) -> DisplayPackagePaths<'_> { + DisplayPackagePaths(paths) +} + /// newtype of `DefaultIx` for indices in task graphs #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)] pub struct TaskIx(DefaultIx); diff --git a/crates/vite_task_plan/src/error.rs b/crates/vite_task_plan/src/error.rs index 674f6dd32..48767cf09 100644 --- a/crates/vite_task_plan/src/error.rs +++ b/crates/vite_task_plan/src/error.rs @@ -3,7 +3,7 @@ reason = "Arc is used for non-UTF-8 path data in error types" )] use std::path::Path; -use std::{env::JoinPathsError, ffi::OsStr, sync::Arc}; +use std::{env::JoinPathsError, ffi::OsStr, fmt, sync::Arc}; use vite_path::{AbsolutePath, relative::InvalidPathDataError}; use vite_str::Str; @@ -47,7 +47,11 @@ impl std::fmt::Display for WhichError { #[derive(Debug, thiserror::Error)] pub enum PathFingerprintErrorKind { - #[error("Path {path:?} is outside of the workspace {workspace_path:?}")] + #[error( + "Path {} is outside of the workspace {}", + .path.as_path().display(), + .workspace_path.as_path().display() + )] PathOutsideWorkspace { path: Arc, workspace_path: Arc }, #[error("Path {path:?} contains characters that make it non-portable")] NonPortableRelativePath { @@ -119,7 +123,11 @@ pub enum Error { #[error(transparent)] TaskRecursionDetected(#[from] TaskRecursionError), - #[error("Invalid vite task command: {program} with args {args:?} under cwd {cwd:?}")] + #[error( + "Invalid Vite+ command: {} under cwd {}", + display_command(.program.as_str(), .args), + .cwd.as_path().display() + )] ParsePlanRequest { program: Str, args: Arc<[Str]>, @@ -176,3 +184,23 @@ pub enum Error { #[error("Cycle dependency detected: {}", _0.iter().map(std::string::ToString::to_string).collect::>().join(" -> "))] CycleDependencyDetected(Vec), } + +struct DisplayCommand<'a> { + program: &'a str, + args: &'a [Str], +} + +impl fmt::Display for DisplayCommand<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&shell_escape::escape(self.program.into()), f)?; + for arg in self.args { + f.write_str(" ")?; + fmt::Display::fmt(&shell_escape::escape(arg.as_str().into()), f)?; + } + Ok(()) + } +} + +const fn display_command<'a>(program: &'a str, args: &'a [Str]) -> DisplayCommand<'a> { + DisplayCommand { program, args } +} diff --git a/crates/vite_task_server/src/lib.rs b/crates/vite_task_server/src/lib.rs index b33ac877a..49a430352 100644 --- a/crates/vite_task_server/src/lib.rs +++ b/crates/vite_task_server/src/lib.rs @@ -45,7 +45,7 @@ pub enum Error { #[error("invalid message from the task")] InvalidRequest(#[source] wincode::ReadError), - #[error("non-absolute path from the task: {path:?}")] + #[error("non-absolute path from the task: {}", .path.display())] NonAbsolutePath { path: OsString }, #[error("invalid glob pattern from the task: {:?}", .0.pattern)] diff --git a/crates/vite_task_server/tests/integration.rs b/crates/vite_task_server/tests/integration.rs index e22581812..e8730a3e1 100644 --- a/crates/vite_task_server/tests/integration.rs +++ b/crates/vite_task_server/tests/integration.rs @@ -234,6 +234,8 @@ fn server_returns_error_on_non_absolute_path() { }) .expect_err("driver should surface the protocol error"); + assert_eq!(err.to_string(), "non-absolute path from the task: relative/path"); + match err { Error::NonAbsolutePath { path } => { assert_eq!(path, OsStr::new("relative/path")); diff --git a/crates/vite_workspace/src/error.rs b/crates/vite_workspace/src/error.rs index bd0b6f766..2cd830d84 100644 --- a/crates/vite_workspace/src/error.rs +++ b/crates/vite_workspace/src/error.rs @@ -16,10 +16,14 @@ pub enum Error { #[error("Duplicate package name `{name}` found at `{path1}` and `{path2}`")] DuplicatedPackageName { name: Str, path1: RelativePathBuf, path2: RelativePathBuf }, - #[error("Package not found in workspace: `{0:?}`")] + #[error("Package not found in workspace: `{}`", .0.as_path().display())] PackageJsonNotFound(AbsolutePathBuf), - #[error("Package at `{package_path:?}` is outside workspace root `{workspace_root:?}`")] + #[error( + "Package at `{}` is outside workspace root `{}`", + .package_path.as_path().display(), + .workspace_root.as_path().display() + )] PackageOutsideWorkspace { package_path: Arc, workspace_root: Arc }, #[error( @@ -35,14 +39,14 @@ pub enum Error { #[error(transparent)] Io(#[from] io::Error), - #[error("Failed to parse JSON file at {file_path:?}")] + #[error("Failed to parse JSON file at {}", .file_path.as_path().display())] SerdeJson { file_path: Arc, #[source] serde_json_error: serde_json::Error, }, - #[error("Failed to parse YAML file at {file_path:?}")] + #[error("Failed to parse YAML file at {}", .file_path.as_path().display())] SerdeYaml { file_path: Arc, #[source] From f2d56edd90edc5fe3ca1668b601840e58a4e07dc Mon Sep 17 00:00:00 2001 From: Liang Mi Date: Fri, 10 Jul 2026 11:01:52 +0800 Subject: [PATCH 2/3] docs(changelog): note diagnostic formatting fix Co-authored-by: GPT-5 Codex --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c387a4be9..aebcbf455 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Changelog +- **Fixed** Vite+ diagnostics now display paths and commands without Rust debug formatting such as escaped backslashes, wrapper types, or list syntax ([#534](https://github.com/voidzero-dev/vite-task/pull/534)). - **Fixed** Windows automatic input tracking now records executable image reads when process creation performs the lookup inside `NtCreateUserProcess` ([#518](https://github.com/voidzero-dev/vite-task/pull/518)). - **Fixed** Failures while waiting for a started task process to exit no longer incorrectly say the process failed to spawn ([#515](https://github.com/voidzero-dev/vite-task/pull/515)). - **Fixed** Missing env vars requested through `@voidzero-dev/vite-task-client` now return `undefined` instead of `null`, preserving Vite production `NODE_ENV` semantics when builds run through `vp run` ([#508](https://github.com/voidzero-dev/vite-task/pull/508)). From dec73242f40086cd22575a63e85744c047d46020 Mon Sep 17 00:00:00 2001 From: Liang Mi Date: Fri, 10 Jul 2026 11:12:38 +0800 Subject: [PATCH 3/3] fix: narrow user-facing display formatting Co-authored-by: GPT-5 Codex --- CHANGELOG.md | 2 +- crates/fspy/src/error.rs | 23 ++--------------------- crates/vite_task_graph/src/lib.rs | 25 ++----------------------- crates/vite_task_plan/src/error.rs | 25 ++----------------------- 4 files changed, 7 insertions(+), 68 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aebcbf455..f049a5dce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -- **Fixed** Vite+ diagnostics now display paths and commands without Rust debug formatting such as escaped backslashes, wrapper types, or list syntax ([#534](https://github.com/voidzero-dev/vite-task/pull/534)). +- **Fixed** Vite+ diagnostics now display individual paths and working directories without Rust debug formatting such as quoted paths or escaped Windows backslashes ([#534](https://github.com/voidzero-dev/vite-task/pull/534)). - **Fixed** Windows automatic input tracking now records executable image reads when process creation performs the lookup inside `NtCreateUserProcess` ([#518](https://github.com/voidzero-dev/vite-task/pull/518)). - **Fixed** Failures while waiting for a started task process to exit no longer incorrectly say the process failed to spawn ([#515](https://github.com/voidzero-dev/vite-task/pull/515)). - **Fixed** Missing env vars requested through `@voidzero-dev/vite-task-client` now return `undefined` instead of `null`, preserving Vite production `NODE_ENV` semantics when builds run through `vp run` ([#508](https://github.com/voidzero-dev/vite-task/pull/508)). diff --git a/crates/fspy/src/error.rs b/crates/fspy/src/error.rs index 20d6a3541..56f983c70 100644 --- a/crates/fspy/src/error.rs +++ b/crates/fspy/src/error.rs @@ -1,15 +1,11 @@ -use std::{ - ffi::{OsStr, OsString}, - fmt, - path::PathBuf, -}; +use std::{ffi::OsString, path::PathBuf}; #[derive(thiserror::Error, Debug)] pub enum SpawnError { #[error( "could not resolve the full path of program '{}' with PATH={} under cwd({})", .program.display(), - display_path_env(.path.as_deref()), + .path.as_deref().unwrap_or_else(|| std::ffi::OsStr::new("")).display(), .cwd.display() )] Which { @@ -34,18 +30,3 @@ pub enum SpawnError { #[error("underlying os error: {0}")] OsSpawn(std::io::Error), } - -struct DisplayPathEnv<'a>(Option<&'a OsStr>); - -impl fmt::Display for DisplayPathEnv<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self.0 { - Some(path) => fmt::Display::fmt(&path.display(), f), - None => f.write_str(""), - } - } -} - -const fn display_path_env(path: Option<&OsStr>) -> DisplayPathEnv<'_> { - DisplayPathEnv(path) -} diff --git a/crates/vite_task_graph/src/lib.rs b/crates/vite_task_graph/src/lib.rs index 8bd733f74..c6f3feff0 100644 --- a/crates/vite_task_graph/src/lib.rs +++ b/crates/vite_task_graph/src/lib.rs @@ -4,7 +4,7 @@ pub mod loader; pub mod query; mod specifier; -use std::{convert::Infallible, fmt, sync::Arc}; +use std::{convert::Infallible, sync::Arc}; use config::{ ResolvedGlobalCacheConfig, ResolvedTaskConfig, UserRunConfig, UserTaskConfig, @@ -138,10 +138,7 @@ pub enum TaskGraphLoadError { /// - When the specifier is from a CLI command, `UnknownPackageError` can be a real error type in case cwd is not in any package. #[derive(Debug, thiserror::Error, Serialize)] pub enum SpecifierLookupError { - #[error( - "Package '{package_name}' is ambiguous among multiple packages: {}", - display_package_paths(.package_paths) - )] + #[error("Package '{package_name}' is ambiguous among multiple packages: {package_paths:?}")] AmbiguousPackageName { package_name: Str, package_paths: Box<[Arc]> }, #[error("Package '{package_name}' not found")] @@ -161,24 +158,6 @@ pub enum SpecifierLookupError { PackageUnknown { unspecifier_package_error: PackageUnknownError, task_name: Str }, } -struct DisplayPackagePaths<'a>(&'a [Arc]); - -impl fmt::Display for DisplayPackagePaths<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - for (index, path) in self.0.iter().enumerate() { - if index > 0 { - f.write_str(", ")?; - } - fmt::Display::fmt(&path.as_path().display(), f)?; - } - Ok(()) - } -} - -const fn display_package_paths(paths: &[Arc]) -> DisplayPackagePaths<'_> { - DisplayPackagePaths(paths) -} - /// newtype of `DefaultIx` for indices in task graphs #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)] pub struct TaskIx(DefaultIx); diff --git a/crates/vite_task_plan/src/error.rs b/crates/vite_task_plan/src/error.rs index 48767cf09..e3d01dee9 100644 --- a/crates/vite_task_plan/src/error.rs +++ b/crates/vite_task_plan/src/error.rs @@ -3,7 +3,7 @@ reason = "Arc is used for non-UTF-8 path data in error types" )] use std::path::Path; -use std::{env::JoinPathsError, ffi::OsStr, fmt, sync::Arc}; +use std::{env::JoinPathsError, ffi::OsStr, sync::Arc}; use vite_path::{AbsolutePath, relative::InvalidPathDataError}; use vite_str::Str; @@ -124,8 +124,7 @@ pub enum Error { TaskRecursionDetected(#[from] TaskRecursionError), #[error( - "Invalid Vite+ command: {} under cwd {}", - display_command(.program.as_str(), .args), + "Invalid vite task command: {program} with args {args:?} under cwd {}", .cwd.as_path().display() )] ParsePlanRequest { @@ -184,23 +183,3 @@ pub enum Error { #[error("Cycle dependency detected: {}", _0.iter().map(std::string::ToString::to_string).collect::>().join(" -> "))] CycleDependencyDetected(Vec), } - -struct DisplayCommand<'a> { - program: &'a str, - args: &'a [Str], -} - -impl fmt::Display for DisplayCommand<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(&shell_escape::escape(self.program.into()), f)?; - for arg in self.args { - f.write_str(" ")?; - fmt::Display::fmt(&shell_escape::escape(arg.as_str().into()), f)?; - } - Ok(()) - } -} - -const fn display_command<'a>(program: &'a str, args: &'a [Str]) -> DisplayCommand<'a> { - DisplayCommand { program, args } -}