diff --git a/Cargo.lock b/Cargo.lock
index d9a3fa87a6..112a4f6f01 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2235,7 +2235,7 @@ dependencies = [
[[package]]
name = "fspy"
version = "0.1.0"
-source = "git+https://github.com/voidzero-dev/vite-task.git?rev=85d4e734c64c96c2ce60e734b3e202b5add53696#85d4e734c64c96c2ce60e734b3e202b5add53696"
+source = "git+https://github.com/voidzero-dev/vite-task.git?rev=5c1d02c750ac21c6f4cf0528062590a145e87fd1#5c1d02c750ac21c6f4cf0528062590a145e87fd1"
dependencies = [
"anyhow",
"bstr",
@@ -2270,7 +2270,7 @@ dependencies = [
[[package]]
name = "fspy_detours_sys"
version = "0.0.0"
-source = "git+https://github.com/voidzero-dev/vite-task.git?rev=85d4e734c64c96c2ce60e734b3e202b5add53696#85d4e734c64c96c2ce60e734b3e202b5add53696"
+source = "git+https://github.com/voidzero-dev/vite-task.git?rev=5c1d02c750ac21c6f4cf0528062590a145e87fd1#5c1d02c750ac21c6f4cf0528062590a145e87fd1"
dependencies = [
"cc",
"winapi",
@@ -2279,7 +2279,7 @@ dependencies = [
[[package]]
name = "fspy_preload_unix"
version = "0.0.0"
-source = "git+https://github.com/voidzero-dev/vite-task.git?rev=85d4e734c64c96c2ce60e734b3e202b5add53696#85d4e734c64c96c2ce60e734b3e202b5add53696"
+source = "git+https://github.com/voidzero-dev/vite-task.git?rev=5c1d02c750ac21c6f4cf0528062590a145e87fd1#5c1d02c750ac21c6f4cf0528062590a145e87fd1"
dependencies = [
"anyhow",
"bstr",
@@ -2294,7 +2294,7 @@ dependencies = [
[[package]]
name = "fspy_preload_windows"
version = "0.1.0"
-source = "git+https://github.com/voidzero-dev/vite-task.git?rev=85d4e734c64c96c2ce60e734b3e202b5add53696#85d4e734c64c96c2ce60e734b3e202b5add53696"
+source = "git+https://github.com/voidzero-dev/vite-task.git?rev=5c1d02c750ac21c6f4cf0528062590a145e87fd1#5c1d02c750ac21c6f4cf0528062590a145e87fd1"
dependencies = [
"constcat",
"fspy_detours_sys",
@@ -2311,7 +2311,7 @@ dependencies = [
[[package]]
name = "fspy_seccomp_unotify"
version = "0.1.0"
-source = "git+https://github.com/voidzero-dev/vite-task.git?rev=85d4e734c64c96c2ce60e734b3e202b5add53696#85d4e734c64c96c2ce60e734b3e202b5add53696"
+source = "git+https://github.com/voidzero-dev/vite-task.git?rev=5c1d02c750ac21c6f4cf0528062590a145e87fd1#5c1d02c750ac21c6f4cf0528062590a145e87fd1"
dependencies = [
"futures-util",
"libc",
@@ -2328,7 +2328,7 @@ dependencies = [
[[package]]
name = "fspy_shared"
version = "0.0.0"
-source = "git+https://github.com/voidzero-dev/vite-task.git?rev=85d4e734c64c96c2ce60e734b3e202b5add53696#85d4e734c64c96c2ce60e734b3e202b5add53696"
+source = "git+https://github.com/voidzero-dev/vite-task.git?rev=5c1d02c750ac21c6f4cf0528062590a145e87fd1#5c1d02c750ac21c6f4cf0528062590a145e87fd1"
dependencies = [
"bitflags 2.13.1",
"bstr",
@@ -2347,7 +2347,7 @@ dependencies = [
[[package]]
name = "fspy_shared_unix"
version = "0.0.0"
-source = "git+https://github.com/voidzero-dev/vite-task.git?rev=85d4e734c64c96c2ce60e734b3e202b5add53696#85d4e734c64c96c2ce60e734b3e202b5add53696"
+source = "git+https://github.com/voidzero-dev/vite-task.git?rev=5c1d02c750ac21c6f4cf0528062590a145e87fd1#5c1d02c750ac21c6f4cf0528062590a145e87fd1"
dependencies = [
"anyhow",
"base64 0.22.1",
@@ -2365,7 +2365,7 @@ dependencies = [
[[package]]
name = "fspy_shm"
version = "0.0.0"
-source = "git+https://github.com/voidzero-dev/vite-task.git?rev=85d4e734c64c96c2ce60e734b3e202b5add53696#85d4e734c64c96c2ce60e734b3e202b5add53696"
+source = "git+https://github.com/voidzero-dev/vite-task.git?rev=5c1d02c750ac21c6f4cf0528062590a145e87fd1#5c1d02c750ac21c6f4cf0528062590a145e87fd1"
dependencies = [
"base64 0.22.1",
"memfd",
@@ -3513,7 +3513,7 @@ dependencies = [
[[package]]
name = "materialized_artifact"
version = "0.0.0"
-source = "git+https://github.com/voidzero-dev/vite-task.git?rev=85d4e734c64c96c2ce60e734b3e202b5add53696#85d4e734c64c96c2ce60e734b3e202b5add53696"
+source = "git+https://github.com/voidzero-dev/vite-task.git?rev=5c1d02c750ac21c6f4cf0528062590a145e87fd1#5c1d02c750ac21c6f4cf0528062590a145e87fd1"
dependencies = [
"tempfile",
]
@@ -3521,7 +3521,7 @@ dependencies = [
[[package]]
name = "materialized_artifact_build"
version = "0.0.0"
-source = "git+https://github.com/voidzero-dev/vite-task.git?rev=85d4e734c64c96c2ce60e734b3e202b5add53696#85d4e734c64c96c2ce60e734b3e202b5add53696"
+source = "git+https://github.com/voidzero-dev/vite-task.git?rev=5c1d02c750ac21c6f4cf0528062590a145e87fd1#5c1d02c750ac21c6f4cf0528062590a145e87fd1"
dependencies = [
"xxhash-rust",
]
@@ -3754,7 +3754,7 @@ dependencies = [
[[package]]
name = "native_str"
version = "0.0.0"
-source = "git+https://github.com/voidzero-dev/vite-task.git?rev=85d4e734c64c96c2ce60e734b3e202b5add53696#85d4e734c64c96c2ce60e734b3e202b5add53696"
+source = "git+https://github.com/voidzero-dev/vite-task.git?rev=5c1d02c750ac21c6f4cf0528062590a145e87fd1#5c1d02c750ac21c6f4cf0528062590a145e87fd1"
dependencies = [
"bumpalo",
"bytemuck",
@@ -5473,7 +5473,7 @@ dependencies = [
[[package]]
name = "pty_terminal"
version = "0.0.0"
-source = "git+https://github.com/voidzero-dev/vite-task.git?rev=85d4e734c64c96c2ce60e734b3e202b5add53696#85d4e734c64c96c2ce60e734b3e202b5add53696"
+source = "git+https://github.com/voidzero-dev/vite-task.git?rev=5c1d02c750ac21c6f4cf0528062590a145e87fd1#5c1d02c750ac21c6f4cf0528062590a145e87fd1"
dependencies = [
"anyhow",
"portable-pty",
@@ -5483,7 +5483,7 @@ dependencies = [
[[package]]
name = "pty_terminal_test"
version = "0.0.0"
-source = "git+https://github.com/voidzero-dev/vite-task.git?rev=85d4e734c64c96c2ce60e734b3e202b5add53696#85d4e734c64c96c2ce60e734b3e202b5add53696"
+source = "git+https://github.com/voidzero-dev/vite-task.git?rev=5c1d02c750ac21c6f4cf0528062590a145e87fd1#5c1d02c750ac21c6f4cf0528062590a145e87fd1"
dependencies = [
"anyhow",
"portable-pty",
@@ -5494,7 +5494,7 @@ dependencies = [
[[package]]
name = "pty_terminal_test_client"
version = "0.0.0"
-source = "git+https://github.com/voidzero-dev/vite-task.git?rev=85d4e734c64c96c2ce60e734b3e202b5add53696#85d4e734c64c96c2ce60e734b3e202b5add53696"
+source = "git+https://github.com/voidzero-dev/vite-task.git?rev=5c1d02c750ac21c6f4cf0528062590a145e87fd1#5c1d02c750ac21c6f4cf0528062590a145e87fd1"
dependencies = [
"base64 0.22.1",
"getrandom 0.4.3",
@@ -7348,7 +7348,7 @@ dependencies = [
[[package]]
name = "snapshot_test"
version = "0.1.0"
-source = "git+https://github.com/voidzero-dev/vite-task.git?rev=85d4e734c64c96c2ce60e734b3e202b5add53696#85d4e734c64c96c2ce60e734b3e202b5add53696"
+source = "git+https://github.com/voidzero-dev/vite-task.git?rev=5c1d02c750ac21c6f4cf0528062590a145e87fd1#5c1d02c750ac21c6f4cf0528062590a145e87fd1"
dependencies = [
"serde",
"serde_json",
@@ -8407,7 +8407,7 @@ dependencies = [
[[package]]
name = "vite_glob"
version = "0.0.0"
-source = "git+https://github.com/voidzero-dev/vite-task.git?rev=85d4e734c64c96c2ce60e734b3e202b5add53696#85d4e734c64c96c2ce60e734b3e202b5add53696"
+source = "git+https://github.com/voidzero-dev/vite-task.git?rev=5c1d02c750ac21c6f4cf0528062590a145e87fd1#5c1d02c750ac21c6f4cf0528062590a145e87fd1"
dependencies = [
"globset",
"thiserror 2.0.19",
@@ -8455,7 +8455,7 @@ dependencies = [
[[package]]
name = "vite_graph_ser"
version = "0.1.0"
-source = "git+https://github.com/voidzero-dev/vite-task.git?rev=85d4e734c64c96c2ce60e734b3e202b5add53696#85d4e734c64c96c2ce60e734b3e202b5add53696"
+source = "git+https://github.com/voidzero-dev/vite-task.git?rev=5c1d02c750ac21c6f4cf0528062590a145e87fd1#5c1d02c750ac21c6f4cf0528062590a145e87fd1"
dependencies = [
"petgraph 0.8.3",
"serde",
@@ -8555,7 +8555,7 @@ dependencies = [
[[package]]
name = "vite_path"
version = "0.1.0"
-source = "git+https://github.com/voidzero-dev/vite-task.git?rev=85d4e734c64c96c2ce60e734b3e202b5add53696#85d4e734c64c96c2ce60e734b3e202b5add53696"
+source = "git+https://github.com/voidzero-dev/vite-task.git?rev=5c1d02c750ac21c6f4cf0528062590a145e87fd1#5c1d02c750ac21c6f4cf0528062590a145e87fd1"
dependencies = [
"diff-struct",
"os_str_bytes",
@@ -8587,7 +8587,7 @@ dependencies = [
[[package]]
name = "vite_powershell"
version = "0.1.0"
-source = "git+https://github.com/voidzero-dev/vite-task.git?rev=85d4e734c64c96c2ce60e734b3e202b5add53696#85d4e734c64c96c2ce60e734b3e202b5add53696"
+source = "git+https://github.com/voidzero-dev/vite-task.git?rev=5c1d02c750ac21c6f4cf0528062590a145e87fd1#5c1d02c750ac21c6f4cf0528062590a145e87fd1"
dependencies = [
"vite_path",
"which",
@@ -8596,7 +8596,7 @@ dependencies = [
[[package]]
name = "vite_select"
version = "0.0.0"
-source = "git+https://github.com/voidzero-dev/vite-task.git?rev=85d4e734c64c96c2ce60e734b3e202b5add53696#85d4e734c64c96c2ce60e734b3e202b5add53696"
+source = "git+https://github.com/voidzero-dev/vite-task.git?rev=5c1d02c750ac21c6f4cf0528062590a145e87fd1#5c1d02c750ac21c6f4cf0528062590a145e87fd1"
dependencies = [
"anyhow",
"crossterm",
@@ -8649,7 +8649,7 @@ dependencies = [
[[package]]
name = "vite_shell"
version = "0.0.0"
-source = "git+https://github.com/voidzero-dev/vite-task.git?rev=85d4e734c64c96c2ce60e734b3e202b5add53696#85d4e734c64c96c2ce60e734b3e202b5add53696"
+source = "git+https://github.com/voidzero-dev/vite-task.git?rev=5c1d02c750ac21c6f4cf0528062590a145e87fd1#5c1d02c750ac21c6f4cf0528062590a145e87fd1"
dependencies = [
"brush-parser 0.4.0",
"diff-struct",
@@ -8676,7 +8676,7 @@ dependencies = [
[[package]]
name = "vite_str"
version = "0.1.0"
-source = "git+https://github.com/voidzero-dev/vite-task.git?rev=85d4e734c64c96c2ce60e734b3e202b5add53696#85d4e734c64c96c2ce60e734b3e202b5add53696"
+source = "git+https://github.com/voidzero-dev/vite-task.git?rev=5c1d02c750ac21c6f4cf0528062590a145e87fd1#5c1d02c750ac21c6f4cf0528062590a145e87fd1"
dependencies = [
"compact_str",
"diff-struct",
@@ -8687,7 +8687,7 @@ dependencies = [
[[package]]
name = "vite_task"
version = "0.0.0"
-source = "git+https://github.com/voidzero-dev/vite-task.git?rev=85d4e734c64c96c2ce60e734b3e202b5add53696#85d4e734c64c96c2ce60e734b3e202b5add53696"
+source = "git+https://github.com/voidzero-dev/vite-task.git?rev=5c1d02c750ac21c6f4cf0528062590a145e87fd1#5c1d02c750ac21c6f4cf0528062590a145e87fd1"
dependencies = [
"anstream",
"anyhow",
@@ -8736,7 +8736,7 @@ dependencies = [
[[package]]
name = "vite_task_client"
version = "0.0.0"
-source = "git+https://github.com/voidzero-dev/vite-task.git?rev=85d4e734c64c96c2ce60e734b3e202b5add53696#85d4e734c64c96c2ce60e734b3e202b5add53696"
+source = "git+https://github.com/voidzero-dev/vite-task.git?rev=5c1d02c750ac21c6f4cf0528062590a145e87fd1#5c1d02c750ac21c6f4cf0528062590a145e87fd1"
dependencies = [
"native_str",
"rustc-hash",
@@ -8749,7 +8749,7 @@ dependencies = [
[[package]]
name = "vite_task_client_napi"
version = "0.1.0"
-source = "git+https://github.com/voidzero-dev/vite-task.git?rev=85d4e734c64c96c2ce60e734b3e202b5add53696#85d4e734c64c96c2ce60e734b3e202b5add53696"
+source = "git+https://github.com/voidzero-dev/vite-task.git?rev=5c1d02c750ac21c6f4cf0528062590a145e87fd1#5c1d02c750ac21c6f4cf0528062590a145e87fd1"
dependencies = [
"napi",
"napi-build",
@@ -8761,7 +8761,7 @@ dependencies = [
[[package]]
name = "vite_task_graph"
version = "0.1.0"
-source = "git+https://github.com/voidzero-dev/vite-task.git?rev=85d4e734c64c96c2ce60e734b3e202b5add53696#85d4e734c64c96c2ce60e734b3e202b5add53696"
+source = "git+https://github.com/voidzero-dev/vite-task.git?rev=5c1d02c750ac21c6f4cf0528062590a145e87fd1#5c1d02c750ac21c6f4cf0528062590a145e87fd1"
dependencies = [
"anyhow",
"async-trait",
@@ -8783,7 +8783,7 @@ dependencies = [
[[package]]
name = "vite_task_ipc_shared"
version = "0.0.0"
-source = "git+https://github.com/voidzero-dev/vite-task.git?rev=85d4e734c64c96c2ce60e734b3e202b5add53696#85d4e734c64c96c2ce60e734b3e202b5add53696"
+source = "git+https://github.com/voidzero-dev/vite-task.git?rev=5c1d02c750ac21c6f4cf0528062590a145e87fd1#5c1d02c750ac21c6f4cf0528062590a145e87fd1"
dependencies = [
"native_str",
"rustc-hash",
@@ -8793,7 +8793,7 @@ dependencies = [
[[package]]
name = "vite_task_plan"
version = "0.1.0"
-source = "git+https://github.com/voidzero-dev/vite-task.git?rev=85d4e734c64c96c2ce60e734b3e202b5add53696#85d4e734c64c96c2ce60e734b3e202b5add53696"
+source = "git+https://github.com/voidzero-dev/vite-task.git?rev=5c1d02c750ac21c6f4cf0528062590a145e87fd1#5c1d02c750ac21c6f4cf0528062590a145e87fd1"
dependencies = [
"anyhow",
"async-trait",
@@ -8821,7 +8821,7 @@ dependencies = [
[[package]]
name = "vite_task_server"
version = "0.0.0"
-source = "git+https://github.com/voidzero-dev/vite-task.git?rev=85d4e734c64c96c2ce60e734b3e202b5add53696#85d4e734c64c96c2ce60e734b3e202b5add53696"
+source = "git+https://github.com/voidzero-dev/vite-task.git?rev=5c1d02c750ac21c6f4cf0528062590a145e87fd1#5c1d02c750ac21c6f4cf0528062590a145e87fd1"
dependencies = [
"futures",
"native_str",
@@ -8845,7 +8845,7 @@ version = "0.0.0"
[[package]]
name = "vite_workspace"
version = "0.0.0"
-source = "git+https://github.com/voidzero-dev/vite-task.git?rev=85d4e734c64c96c2ce60e734b3e202b5add53696#85d4e734c64c96c2ce60e734b3e202b5add53696"
+source = "git+https://github.com/voidzero-dev/vite-task.git?rev=5c1d02c750ac21c6f4cf0528062590a145e87fd1#5c1d02c750ac21c6f4cf0528062590a145e87fd1"
dependencies = [
"clap",
"petgraph 0.8.3",
diff --git a/Cargo.toml b/Cargo.toml
index d1528b63a6..45bee4283e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -193,7 +193,7 @@ dunce = "1.0.5"
fast-glob = "1.0.0"
flate2 = { version = "=1.1.9", features = ["zlib-rs"] }
form_urlencoded = "1.2.1"
-fspy = { git = "https://github.com/voidzero-dev/vite-task.git", rev = "85d4e734c64c96c2ce60e734b3e202b5add53696" }
+fspy = { git = "https://github.com/voidzero-dev/vite-task.git", rev = "5c1d02c750ac21c6f4cf0528062590a145e87fd1" }
futures = "0.3.31"
futures-util = "0.3.31"
glob = "0.3.2"
@@ -250,8 +250,8 @@ pretty_assertions = "1.4.1"
phf = "0.14.0"
prettyplease = "0.2.32"
proc-macro2 = "1"
-pty_terminal_test = { git = "https://github.com/voidzero-dev/vite-task.git", rev = "85d4e734c64c96c2ce60e734b3e202b5add53696" }
-pty_terminal_test_client = { git = "https://github.com/voidzero-dev/vite-task.git", rev = "85d4e734c64c96c2ce60e734b3e202b5add53696" }
+pty_terminal_test = { git = "https://github.com/voidzero-dev/vite-task.git", rev = "5c1d02c750ac21c6f4cf0528062590a145e87fd1" }
+pty_terminal_test_client = { git = "https://github.com/voidzero-dev/vite-task.git", rev = "5c1d02c750ac21c6f4cf0528062590a145e87fd1" }
quote = "1"
rayon = "1.10.0"
regex = "1.11.1"
@@ -274,7 +274,7 @@ sha2 = "0.10.9"
shell-escape = "0.1.5"
simdutf8 = "0.1.5"
smallvec = { version = "1.15.1", features = ["union"] }
-snapshot_test = { git = "https://github.com/voidzero-dev/vite-task.git", rev = "85d4e734c64c96c2ce60e734b3e202b5add53696" }
+snapshot_test = { git = "https://github.com/voidzero-dev/vite-task.git", rev = "5c1d02c750ac21c6f4cf0528062590a145e87fd1" }
string_cache = "0.9.0"
sugar_path = { version = "3", features = ["cached_current_dir"] }
supports-color = "3"
@@ -305,11 +305,11 @@ vite_pm_cli = { path = "crates/vite_pm_cli" }
vite_setup = { path = "crates/vite_setup" }
vite_shared = { path = "crates/vite_shared" }
vite_static_config = { path = "crates/vite_static_config" }
-vite_path = { git = "https://github.com/voidzero-dev/vite-task.git", rev = "85d4e734c64c96c2ce60e734b3e202b5add53696" }
-vite_powershell = { git = "https://github.com/voidzero-dev/vite-task.git", rev = "85d4e734c64c96c2ce60e734b3e202b5add53696" }
-vite_str = { git = "https://github.com/voidzero-dev/vite-task.git", rev = "85d4e734c64c96c2ce60e734b3e202b5add53696" }
-vite_task = { git = "https://github.com/voidzero-dev/vite-task.git", rev = "85d4e734c64c96c2ce60e734b3e202b5add53696" }
-vite_workspace = { git = "https://github.com/voidzero-dev/vite-task.git", rev = "85d4e734c64c96c2ce60e734b3e202b5add53696" }
+vite_path = { git = "https://github.com/voidzero-dev/vite-task.git", rev = "5c1d02c750ac21c6f4cf0528062590a145e87fd1" }
+vite_powershell = { git = "https://github.com/voidzero-dev/vite-task.git", rev = "5c1d02c750ac21c6f4cf0528062590a145e87fd1" }
+vite_str = { git = "https://github.com/voidzero-dev/vite-task.git", rev = "5c1d02c750ac21c6f4cf0528062590a145e87fd1" }
+vite_task = { git = "https://github.com/voidzero-dev/vite-task.git", rev = "5c1d02c750ac21c6f4cf0528062590a145e87fd1" }
+vite_workspace = { git = "https://github.com/voidzero-dev/vite-task.git", rev = "5c1d02c750ac21c6f4cf0528062590a145e87fd1" }
walkdir = "2.5.0"
which = "8.0.0"
winreg = "0.56.0"
diff --git a/crates/vite_cli_snapshots/tests/cli_snapshots/fixtures/builtin_script_note/index.html b/crates/vite_cli_snapshots/tests/cli_snapshots/fixtures/builtin_script_note/index.html
new file mode 100644
index 0000000000..13ee08c5d6
--- /dev/null
+++ b/crates/vite_cli_snapshots/tests/cli_snapshots/fixtures/builtin_script_note/index.html
@@ -0,0 +1 @@
+
diff --git a/crates/vite_cli_snapshots/tests/cli_snapshots/fixtures/builtin_script_note/package.json b/crates/vite_cli_snapshots/tests/cli_snapshots/fixtures/builtin_script_note/package.json
new file mode 100644
index 0000000000..81503a3995
--- /dev/null
+++ b/crates/vite_cli_snapshots/tests/cli_snapshots/fixtures/builtin_script_note/package.json
@@ -0,0 +1,13 @@
+{
+ "name": "@test/builtin-script-note",
+ "version": "1.0.0",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "build": "vp build",
+ "dev": "vite --host --port 5000",
+ "lint": "vp lint --deny-warnings src/",
+ "format": "prettier --write src/",
+ "help": "vpt echo help"
+ }
+}
diff --git a/crates/vite_cli_snapshots/tests/cli_snapshots/fixtures/builtin_script_note/snapshots.toml b/crates/vite_cli_snapshots/tests/cli_snapshots/fixtures/builtin_script_note/snapshots.toml
new file mode 100644
index 0000000000..533ef5c07f
--- /dev/null
+++ b/crates/vite_cli_snapshots/tests/cli_snapshots/fixtures/builtin_script_note/snapshots.toml
@@ -0,0 +1,39 @@
+# A built-in command never runs a `package.json` script of the same name, so it
+# points at `vpr ` instead. This project defines `build`, `dev`, `lint`,
+# `format` and `help` scripts, but no `fmt` one.
+[[case]]
+name = "builtin_script_note"
+vp = ["local", "global"]
+skip-platforms = ["windows"]
+steps = [
+ { argv = ["vp", "dev", "--port", "12312312312"], comment = "`vp dev` points at the `dev` script (invalid port exits the server immediately)", continue-on-failure = true },
+ { argv = ["vp", "build"], comment = "`vp build` points at the same build script used by the suppression cases" },
+ { argv = ["vp", "lint", "src/"], comment = "every built-in that can be mistaken for a script gets the same note", continue-on-failure = true },
+ { argv = ["vp", "lint", "."], cwd = "src", comment = "note reaches the enclosing package from a subdirectory, like `vpr` does", continue-on-failure = true },
+ { argv = ["vp", "format", "src/"], comment = "the `format` alias reaches the local CLI as typed, so its own script gets the note", continue-on-failure = true },
+ { argv = ["vp", "fmt", "src/"], comment = "no note: only `format` is a script here, and that is not the name this ran under", continue-on-failure = true },
+ { argv = ["vp", "help", "dev"], comment = "the local path checks the original `help` spelling; the global CLI renders help before local delegation", continue-on-failure = true },
+ { argv = ["vp", "preview", "--port", "12312312312"], comment = "no note: this project has no `preview` script", continue-on-failure = true },
+ { argv = ["vp", "lint", "src/"], tty = false, comment = "the note still reaches piped output, such as an AI agent capturing the command; it goes to stderr, so parsed stdout stays intact", continue-on-failure = true },
+]
+
+# Vite Task sets `VP_RUN` for a package script's child process. The nested
+# built-in should not point back at the `vp run` already in progress.
+[[case]]
+name = "builtin_script_note_inside_task"
+vp = "local"
+skip-platforms = ["windows"]
+steps = [
+ { argv = ["vp", "run", "build"], comment = "no note: a task-spawned `vp build` is already on the script path" },
+]
+
+# npm, pnpm and Yarn expose the active package script through the same
+# `npm_lifecycle_event` compatibility variable. The note should not recommend
+# switching runners when a package-manager script has already done that.
+[[case]]
+name = "builtin_script_note_inside_npm_script"
+vp = "local"
+skip-platforms = ["windows"]
+steps = [
+ { argv = ["npm", "run", "build"], comment = "no note: npm has already selected and started the build script" },
+]
diff --git a/crates/vite_cli_snapshots/tests/cli_snapshots/fixtures/builtin_script_note/snapshots/builtin_script_note.global.md b/crates/vite_cli_snapshots/tests/cli_snapshots/fixtures/builtin_script_note/snapshots/builtin_script_note.global.md
new file mode 100644
index 0000000000..a679d283b0
--- /dev/null
+++ b/crates/vite_cli_snapshots/tests/cli_snapshots/fixtures/builtin_script_note/snapshots/builtin_script_note.global.md
@@ -0,0 +1,132 @@
+# builtin_script_note
+
+## `vp dev --port 12312312312`
+
+`vp dev` points at the `dev` script (invalid port exits the server immediately)
+
+**Exit code:** 1
+
+```
+VITE+ - The Unified Toolchain for the Web
+
+note: You are running `vp dev` as a Vite+ built-in command. If you meant to run the dev npm script, use `vpr dev` instead.
+error when starting dev server:
+Error: No available ports found between 12312312312 and 65535
+```
+
+## `vp build`
+
+`vp build` points at the same build script used by the suppression cases
+
+```
+VITE+ - The Unified Toolchain for the Web
+
+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.
+✓ 4 modules transformed.
+computing gzip size...
+dist/index.html kB │ gzip: kB
+dist/assets/index-.js kB │ gzip: kB
+
+✓ built in
+```
+
+## `vp lint src/`
+
+every built-in that can be mistaken for a script gets the same note
+
+```
+VITE+ - The Unified Toolchain for the Web
+
+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.
+Found 0 warnings and 0 errors.
+Finished in on 1 file with rules using threads.
+```
+
+## `cd src && vp lint .`
+
+note reaches the enclosing package from a subdirectory, like `vpr` does
+
+```
+VITE+ - The Unified Toolchain for the Web
+
+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.
+Found 0 warnings and 0 errors.
+Finished in on 1 file with rules using threads.
+```
+
+## `vp format src/`
+
+the `format` alias reaches the local CLI as typed, so its own script gets the note
+
+```
+VITE+ - The Unified Toolchain for the Web
+
+note: You are running `vp format` as a Vite+ built-in command. If you meant to run the format npm script, use `vpr format` instead.
+Finished in on 1 files using threads.
+```
+
+## `vp fmt src/`
+
+no note: only `format` is a script here, and that is not the name this ran under
+
+```
+VITE+ - The Unified Toolchain for the Web
+
+Finished in on 1 files using threads.
+```
+
+## `vp help dev`
+
+the local path checks the original `help` spelling; the global CLI renders help before local delegation
+
+```
+VITE+ - The Unified Toolchain for the Web
+
+Usage: vp dev [ROOT] [OPTIONS]
+
+Run the development server.
+Options are forwarded to Vite.
+
+Arguments:
+ [ROOT] Project root directory (default: current directory)
+
+Options:
+ --host [HOST] Specify hostname
+ --port Specify port
+ --open [PATH] Open browser on startup
+ --strictPort Exit if specified port is already in use
+ -c, --config Use specified config file
+ --base Public base path
+ -m, --mode Set env mode
+ -h, --help Print help
+
+Examples:
+ vp dev
+ vp dev --open
+ vp dev --host localhost --port 5173
+
+Documentation: https://viteplus.dev/guide/dev
+```
+
+## `vp preview --port 12312312312`
+
+no note: this project has no `preview` script
+
+**Exit code:** 1
+
+```
+VITE+ - The Unified Toolchain for the Web
+
+error when starting preview server:
+Error: No available ports found between 12312312312 and 65535
+```
+
+## `vp lint src/`
+
+the note still reaches piped output, such as an AI agent capturing the command; it goes to stderr, so parsed stdout stays intact
+
+```
+Found 0 warnings and 0 errors.
+Finished in on 1 file with rules using threads.
+[1m[2mnote:[0m[0m You are running [94m`vp lint`[39m as a Vite+ built-in command. If you meant to run the lint npm script, use [94m`vpr lint`[39m instead.
+```
diff --git a/crates/vite_cli_snapshots/tests/cli_snapshots/fixtures/builtin_script_note/snapshots/builtin_script_note.local.md b/crates/vite_cli_snapshots/tests/cli_snapshots/fixtures/builtin_script_note/snapshots/builtin_script_note.local.md
new file mode 100644
index 0000000000..358efb816b
--- /dev/null
+++ b/crates/vite_cli_snapshots/tests/cli_snapshots/fixtures/builtin_script_note/snapshots/builtin_script_note.local.md
@@ -0,0 +1,128 @@
+# builtin_script_note
+
+## `vp dev --port 12312312312`
+
+`vp dev` points at the `dev` script (invalid port exits the server immediately)
+
+**Exit code:** 1
+
+```
+note: You are running `vp dev` as a Vite+ built-in command. If you meant to run the dev npm script, use `vpr dev` instead.
+error when starting dev server:
+Error: No available ports found between 12312312312 and 65535
+```
+
+## `vp build`
+
+`vp build` points at the same build script used by the suppression cases
+
+```
+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.
+✓ 4 modules transformed.
+computing gzip size...
+dist/index.html kB │ gzip: kB
+dist/assets/index-.js kB │ gzip: kB
+
+✓ built in
+```
+
+## `vp lint src/`
+
+every built-in that can be mistaken for a script gets the same note
+
+```
+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.
+Found 0 warnings and 0 errors.
+Finished in on 1 file with rules using threads.
+```
+
+## `cd src && vp lint .`
+
+note reaches the enclosing package from a subdirectory, like `vpr` does
+
+```
+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.
+Found 0 warnings and 0 errors.
+Finished in on 1 file with rules using threads.
+```
+
+## `vp format src/`
+
+the `format` alias reaches the local CLI as typed, so its own script gets the note
+
+```
+note: You are running `vp format` as a Vite+ built-in command. If you meant to run the format npm script, use `vpr format` instead.
+Finished in on 1 files using threads.
+```
+
+## `vp fmt src/`
+
+no note: only `format` is a script here, and that is not the name this ran under
+
+```
+Finished in on 1 files using threads.
+```
+
+## `vp help dev`
+
+the local path checks the original `help` spelling; the global CLI renders help before local delegation
+
+```
+note: You are running `vp help` as a Vite+ built-in command. If you meant to run the help npm script, use `vpr help` instead.
+vp/
+
+Usage:
+ $ vp [root]
+
+Commands:
+ [root] start dev server
+ build [root] build for production
+ optimize [root] pre-bundle dependencies (deprecated, the pre-bundle process runs automatically and does not need to be called)
+ preview [root] locally preview production build
+
+For more info, run any command with the `--help` flag:
+ $ vp --help
+ $ vp build --help
+ $ vp optimize --help
+ $ vp preview --help
+
+Options:
+ --host [host] [string] specify hostname
+ --port [number] specify port
+ --open [path] [boolean | string] open browser on startup
+ --cors [boolean] enable CORS
+ --strictPort [boolean] exit if specified port is already in use
+ --force [boolean] force the optimizer to ignore the cache and re-bundle
+ --experimentalBundle [boolean] use experimental full bundle mode (this is highly experimental)
+ -c, --config [string] use specified config file
+ --base [string] public base path (default: /)
+ -l, --logLevel [string] info | warn | error | silent
+ --clearScreen [boolean] allow/disable clear screen when logging
+ --configLoader [string] use 'bundle' to bundle the config with Rolldown, or 'runner' (experimental) to process it on the fly, or 'native' (experimental) to load using the native runtime (default: bundle)
+ -d, --debug [feat] [string | boolean] show debug logs
+ -f, --filter [string] filter debug logs
+ -m, --mode [string] set env mode
+ -h, --help Display this message
+ -v, --version Display version number
+```
+
+## `vp preview --port 12312312312`
+
+no note: this project has no `preview` script
+
+**Exit code:** 1
+
+```
+error when starting preview server:
+Error: No available ports found between 12312312312 and 65535
+```
+
+## `vp lint src/`
+
+the note still reaches piped output, such as an AI agent capturing the command; it goes to stderr, so parsed stdout stays intact
+
+```
+Found 0 warnings and 0 errors.
+Finished in on 1 file with rules using threads.
+[1m[2mnote:[0m[0m You are running [94m`vp lint`[39m as a Vite+ built-in command. If you meant to run the lint npm script, use [94m`vpr lint`[39m instead.
+```
diff --git a/crates/vite_cli_snapshots/tests/cli_snapshots/fixtures/builtin_script_note/snapshots/builtin_script_note_inside_npm_script.md b/crates/vite_cli_snapshots/tests/cli_snapshots/fixtures/builtin_script_note/snapshots/builtin_script_note_inside_npm_script.md
new file mode 100644
index 0000000000..984528898e
--- /dev/null
+++ b/crates/vite_cli_snapshots/tests/cli_snapshots/fixtures/builtin_script_note/snapshots/builtin_script_note_inside_npm_script.md
@@ -0,0 +1,18 @@
+# builtin_script_note_inside_npm_script
+
+## `npm run build`
+
+no note: npm has already selected and started the build script
+
+```
+
+> @test/builtin-script-note@1.0.0 build
+> vp build
+
+✓ 4 modules transformed.
+computing gzip size...
+dist/index.html kB │ gzip: kB
+dist/assets/index-.js kB │ gzip: kB
+
+✓ built in
+```
diff --git a/crates/vite_cli_snapshots/tests/cli_snapshots/fixtures/builtin_script_note/snapshots/builtin_script_note_inside_task.md b/crates/vite_cli_snapshots/tests/cli_snapshots/fixtures/builtin_script_note/snapshots/builtin_script_note_inside_task.md
new file mode 100644
index 0000000000..aac6ca268e
--- /dev/null
+++ b/crates/vite_cli_snapshots/tests/cli_snapshots/fixtures/builtin_script_note/snapshots/builtin_script_note_inside_task.md
@@ -0,0 +1,15 @@
+# builtin_script_note_inside_task
+
+## `vp run build`
+
+no note: a task-spawned `vp build` is already on the script path
+
+```
+$ vp build ⊘ cache disabled
+✓ 4 modules transformed.
+computing gzip size...
+dist/index.html kB │ gzip: kB
+dist/assets/index-.js kB │ gzip: kB
+
+✓ built in
+```
diff --git a/crates/vite_cli_snapshots/tests/cli_snapshots/fixtures/builtin_script_note/src/valid.js b/crates/vite_cli_snapshots/tests/cli_snapshots/fixtures/builtin_script_note/src/valid.js
new file mode 100644
index 0000000000..95c37436b4
--- /dev/null
+++ b/crates/vite_cli_snapshots/tests/cli_snapshots/fixtures/builtin_script_note/src/valid.js
@@ -0,0 +1,6 @@
+// Lint-clean and already formatted, so the commands only print their summary.
+function validCode() {
+ return 'hello';
+}
+
+export { validCode };
diff --git a/crates/vite_cli_snapshots/tests/cli_snapshots/fixtures/builtin_script_note/vite.config.ts b/crates/vite_cli_snapshots/tests/cli_snapshots/fixtures/builtin_script_note/vite.config.ts
new file mode 100644
index 0000000000..9590747fc7
--- /dev/null
+++ b/crates/vite_cli_snapshots/tests/cli_snapshots/fixtures/builtin_script_note/vite.config.ts
@@ -0,0 +1,8 @@
+export default {
+ lint: {
+ ignorePatterns: [],
+ },
+ fmt: {
+ ignorePatterns: [],
+ },
+};
diff --git a/crates/vite_cli_snapshots/tests/cli_snapshots/fixtures/migration_eslint_svelte_runes/snapshots/migration_eslint_svelte_runes.md b/crates/vite_cli_snapshots/tests/cli_snapshots/fixtures/migration_eslint_svelte_runes/snapshots/migration_eslint_svelte_runes.md
index 5453ad229c..b20ead3c64 100644
--- a/crates/vite_cli_snapshots/tests/cli_snapshots/fixtures/migration_eslint_svelte_runes/snapshots/migration_eslint_svelte_runes.md
+++ b/crates/vite_cli_snapshots/tests/cli_snapshots/fixtures/migration_eslint_svelte_runes/snapshots/migration_eslint_svelte_runes.md
@@ -84,6 +84,7 @@ valid Svelte rune usage should pass no-undef
```
VITE+ - The Unified Toolchain for the Web
+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.
Found 0 warnings and 0 errors.
Finished in on 1 file with rules using threads.
```
diff --git a/crates/vite_cli_snapshots/tests/cli_snapshots/fixtures/vp_build_cache/snapshots/vp_build_cache.md b/crates/vite_cli_snapshots/tests/cli_snapshots/fixtures/vp_build_cache/snapshots/vp_build_cache.md
index 1cf942bc84..cb03a130ba 100644
--- a/crates/vite_cli_snapshots/tests/cli_snapshots/fixtures/vp_build_cache/snapshots/vp_build_cache.md
+++ b/crates/vite_cli_snapshots/tests/cli_snapshots/fixtures/vp_build_cache/snapshots/vp_build_cache.md
@@ -33,6 +33,7 @@ direct vp build should not be cached
direct vp build has no cache
```
+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.
✓ 4 modules transformed.
computing gzip size...
dist/index.html kB │ gzip: kB
diff --git a/crates/vite_cli_snapshots/tests/cli_snapshots/fixtures/vp_build_cache_monorepo/snapshots/vp_build_cache_monorepo.md b/crates/vite_cli_snapshots/tests/cli_snapshots/fixtures/vp_build_cache_monorepo/snapshots/vp_build_cache_monorepo.md
index c57420d0d9..2dc2fab650 100644
--- a/crates/vite_cli_snapshots/tests/cli_snapshots/fixtures/vp_build_cache_monorepo/snapshots/vp_build_cache_monorepo.md
+++ b/crates/vite_cli_snapshots/tests/cli_snapshots/fixtures/vp_build_cache_monorepo/snapshots/vp_build_cache_monorepo.md
@@ -60,6 +60,7 @@ direct vp build should not be cached
direct vp build has no cache
```
+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.
✓ 4 modules transformed.
computing gzip size...
dist/index.html kB │ gzip: kB
diff --git a/crates/vite_global_cli/src/cli.rs b/crates/vite_global_cli/src/cli.rs
index 163c2ded7a..6ba7ebdc98 100644
--- a/crates/vite_global_cli/src/cli.rs
+++ b/crates/vite_global_cli/src/cli.rs
@@ -844,9 +844,22 @@ fn prompt_reinstall_node_mismatches(
.unwrap_or(false)
}
+/// The subcommand as the user wrote it, taken from `argv` before any rewriting.
+///
+/// Parsing resolves a subcommand to one clap variant, which does not record the
+/// spelling used, so it is read straight from the command line instead.
+#[must_use]
+pub fn raw_subcommand(argv: &[String]) -> Option<&str> {
+ argv.iter().skip(1).map(String::as_str).find(|arg| !arg.starts_with('-'))
+}
+
/// Run the CLI command.
-pub async fn run_command(cwd: AbsolutePathBuf, args: Args) -> Result {
- run_command_with_options(cwd, args, RenderOptions::default()).await
+pub async fn run_command(
+ cwd: AbsolutePathBuf,
+ args: Args,
+ raw_subcommand: Option<&str>,
+) -> Result {
+ run_command_with_options(cwd, args, RenderOptions::default(), raw_subcommand).await
}
/// Run the CLI command with rendering options.
@@ -854,6 +867,7 @@ pub async fn run_command_with_options(
cwd: AbsolutePathBuf,
args: Args,
render_options: RenderOptions,
+ raw_subcommand: Option<&str>,
) -> Result {
// Handle --version flag (Category B: delegates to JS)
if args.version {
@@ -887,13 +901,13 @@ pub async fn run_command_with_options(
}
// Category B: JS Script Commands
- Commands::Create { args } => commands::create::execute(cwd, &args).await,
+ Commands::Create { args } => commands::create::execute(cwd, &args, raw_subcommand).await,
Commands::Migrate { args } => commands::migrate::execute(cwd, &args).await,
- Commands::Config { args } => commands::config::execute(cwd, &args).await,
+ Commands::Config { args } => commands::config::execute(cwd, &args, raw_subcommand).await,
- Commands::Staged { args } => commands::staged::execute(cwd, &args).await,
+ Commands::Staged { args } => commands::staged::execute(cwd, &args, raw_subcommand).await,
// Category C: Local CLI Delegation (stubs)
Commands::Dev { args } => {
@@ -901,7 +915,7 @@ pub async fn run_command_with_options(
return Ok(ExitStatus::default());
}
print_runtime_header(render_options.show_header);
- commands::delegate::execute(cwd, "dev", &args).await
+ commands::delegate::execute(cwd, "dev", &args, raw_subcommand).await
}
Commands::Build { args } => {
@@ -909,7 +923,7 @@ pub async fn run_command_with_options(
return Ok(ExitStatus::default());
}
print_runtime_header(render_options.show_header);
- commands::delegate::execute(cwd, "build", &args).await
+ commands::delegate::execute(cwd, "build", &args, raw_subcommand).await
}
Commands::Test { args } => {
@@ -917,7 +931,7 @@ pub async fn run_command_with_options(
return Ok(ExitStatus::default());
}
print_runtime_header(render_options.show_header);
- commands::delegate::execute(cwd, "test", &args).await
+ commands::delegate::execute(cwd, "test", &args, raw_subcommand).await
}
Commands::Lint { args } => {
@@ -926,9 +940,9 @@ pub async fn run_command_with_options(
}
maybe_print_runtime_header("lint", &args, render_options.show_header);
if should_force_global_delegate("lint", &args) {
- commands::delegate::execute_global(cwd, "lint", &args).await
+ commands::delegate::execute_global(cwd, "lint", &args, raw_subcommand).await
} else {
- commands::delegate::execute(cwd, "lint", &args).await
+ commands::delegate::execute(cwd, "lint", &args, raw_subcommand).await
}
}
@@ -938,9 +952,9 @@ pub async fn run_command_with_options(
}
maybe_print_runtime_header("fmt", &args, render_options.show_header);
if should_force_global_delegate("fmt", &args) {
- commands::delegate::execute_global(cwd, "fmt", &args).await
+ commands::delegate::execute_global(cwd, "fmt", &args, raw_subcommand).await
} else {
- commands::delegate::execute(cwd, "fmt", &args).await
+ commands::delegate::execute(cwd, "fmt", &args, raw_subcommand).await
}
}
@@ -949,7 +963,7 @@ pub async fn run_command_with_options(
return Ok(ExitStatus::default());
}
print_runtime_header(render_options.show_header);
- commands::delegate::execute(cwd, "check", &args).await
+ commands::delegate::execute(cwd, "check", &args, raw_subcommand).await
}
Commands::Pack { args } => {
@@ -957,7 +971,7 @@ pub async fn run_command_with_options(
return Ok(ExitStatus::default());
}
print_runtime_header(render_options.show_header);
- commands::delegate::execute(cwd, "pack", &args).await
+ commands::delegate::execute(cwd, "pack", &args, raw_subcommand).await
}
Commands::Run { args } => {
@@ -965,7 +979,7 @@ pub async fn run_command_with_options(
return Ok(ExitStatus::default());
}
print_runtime_header(render_options.show_header);
- commands::delegate::execute(cwd, "run", &args).await
+ commands::delegate::execute(cwd, "run", &args, raw_subcommand).await
}
Commands::Exec { args } => {
@@ -973,7 +987,7 @@ pub async fn run_command_with_options(
return Ok(ExitStatus::default());
}
print_runtime_header(render_options.show_header);
- commands::delegate::execute(cwd, "exec", &args).await
+ commands::delegate::execute(cwd, "exec", &args, raw_subcommand).await
}
Commands::Preview { args } => {
@@ -982,7 +996,7 @@ pub async fn run_command_with_options(
return Ok(ExitStatus::default());
}
print_runtime_header(render_options.show_header);
- commands::delegate::execute(cwd, "preview", &args).await
+ commands::delegate::execute(cwd, "preview", &args, raw_subcommand).await
}
Commands::Cache { args } => {
@@ -990,7 +1004,7 @@ pub async fn run_command_with_options(
return Ok(ExitStatus::default());
}
print_runtime_header(render_options.show_header);
- commands::delegate::execute(cwd, "cache", &args).await
+ commands::delegate::execute(cwd, "cache", &args, raw_subcommand).await
}
Commands::Env(args) => commands::env::execute(cwd, args).await,
@@ -1086,10 +1100,31 @@ pub fn try_parse_args_from_with_options(
#[cfg(test)]
mod tests {
use super::{
- display_node_version, has_flag_before_terminator, is_same_node_version,
+ display_node_version, has_flag_before_terminator, is_same_node_version, raw_subcommand,
should_force_global_delegate, should_suppress_header_for_subcommand,
};
+ fn argv(args: &[&str]) -> Vec {
+ args.iter().map(|arg| (*arg).to_string()).collect()
+ }
+
+ #[test]
+ fn raw_subcommand_is_the_token_as_written() {
+ assert_eq!(raw_subcommand(&argv(&["vp", "fmt", "src/"])), Some("fmt"));
+ assert_eq!(raw_subcommand(&argv(&["vp", "format", "src/"])), Some("format"));
+ }
+
+ #[test]
+ fn raw_subcommand_skips_leading_flags() {
+ assert_eq!(raw_subcommand(&argv(&["vp", "--silent", "install"])), Some("install"));
+ }
+
+ #[test]
+ fn raw_subcommand_is_none_without_a_subcommand() {
+ assert_eq!(raw_subcommand(&argv(&["vp"])), None);
+ assert_eq!(raw_subcommand(&argv(&["vp", "--version"])), None);
+ }
+
#[test]
fn detects_global_update_node_version_mismatch() {
assert!(is_same_node_version("21.0.0", "v21.0.0"));
diff --git a/crates/vite_global_cli/src/commands/config.rs b/crates/vite_global_cli/src/commands/config.rs
index fb3983e0f1..1e0d1252a1 100644
--- a/crates/vite_global_cli/src/commands/config.rs
+++ b/crates/vite_global_cli/src/commands/config.rs
@@ -7,6 +7,10 @@ use vite_path::AbsolutePathBuf;
use crate::error::Error;
/// Execute the `config` command by delegating to local or global vite-plus.
-pub async fn execute(cwd: AbsolutePathBuf, args: &[String]) -> Result {
- super::delegate::execute(cwd, "config", args).await
+pub async fn execute(
+ cwd: AbsolutePathBuf,
+ args: &[String],
+ raw_subcommand: Option<&str>,
+) -> Result {
+ super::delegate::execute(cwd, "config", args, raw_subcommand).await
}
diff --git a/crates/vite_global_cli/src/commands/create.rs b/crates/vite_global_cli/src/commands/create.rs
index 61925ce7b3..e8c9e8d6a3 100644
--- a/crates/vite_global_cli/src/commands/create.rs
+++ b/crates/vite_global_cli/src/commands/create.rs
@@ -7,8 +7,12 @@ use vite_path::AbsolutePathBuf;
use crate::error::Error;
/// Execute the `create` command by delegating to local or global vite-plus.
-pub async fn execute(cwd: AbsolutePathBuf, args: &[String]) -> Result {
- super::delegate::execute(cwd, "create", args).await
+pub async fn execute(
+ cwd: AbsolutePathBuf,
+ args: &[String],
+ raw_subcommand: Option<&str>,
+) -> Result {
+ super::delegate::execute(cwd, "create", args, raw_subcommand).await
}
#[cfg(test)]
diff --git a/crates/vite_global_cli/src/commands/delegate.rs b/crates/vite_global_cli/src/commands/delegate.rs
index 726169810e..6684a7e71d 100644
--- a/crates/vite_global_cli/src/commands/delegate.rs
+++ b/crates/vite_global_cli/src/commands/delegate.rs
@@ -7,12 +7,16 @@ use vite_path::AbsolutePathBuf;
use crate::{error::Error, js_executor::JsExecutor};
/// Execute a command by delegating to the local `vite-plus` CLI.
+///
+/// `raw_subcommand` is the subcommand as the user wrote it, which the local CLI
+/// cannot recover from `command` alone once parsing has resolved it.
pub async fn execute(
cwd: AbsolutePathBuf,
command: &str,
args: &[String],
+ raw_subcommand: Option<&str>,
) -> Result {
- let mut executor = JsExecutor::new(None);
+ let mut executor = JsExecutor::new(None).with_raw_subcommand(raw_subcommand);
let mut full_args = vec![command.to_string()];
full_args.extend(args.iter().cloned());
executor.delegate_to_local_cli(&cwd, &full_args).await
@@ -31,12 +35,15 @@ pub async fn execute_output(
}
/// Execute a command by delegating to the global `vite-plus` CLI.
+///
+/// `raw_subcommand` is the subcommand as the user wrote it; see [`execute`].
pub async fn execute_global(
cwd: AbsolutePathBuf,
command: &str,
args: &[String],
+ raw_subcommand: Option<&str>,
) -> Result {
- let mut executor = JsExecutor::new(None);
+ let mut executor = JsExecutor::new(None).with_raw_subcommand(raw_subcommand);
let mut full_args = vec![command.to_string()];
full_args.extend(args.iter().cloned());
executor.delegate_to_global_cli(&cwd, &full_args).await
diff --git a/crates/vite_global_cli/src/commands/staged.rs b/crates/vite_global_cli/src/commands/staged.rs
index fa78bd1a24..0bbbcd17ff 100644
--- a/crates/vite_global_cli/src/commands/staged.rs
+++ b/crates/vite_global_cli/src/commands/staged.rs
@@ -7,6 +7,10 @@ use vite_path::AbsolutePathBuf;
use crate::error::Error;
/// Execute the `staged` command by delegating to local or global vite-plus.
-pub async fn execute(cwd: AbsolutePathBuf, args: &[String]) -> Result {
- super::delegate::execute(cwd, "staged", args).await
+pub async fn execute(
+ cwd: AbsolutePathBuf,
+ args: &[String],
+ raw_subcommand: Option<&str>,
+) -> Result {
+ super::delegate::execute(cwd, "staged", args, raw_subcommand).await
}
diff --git a/crates/vite_global_cli/src/commands/vpr.rs b/crates/vite_global_cli/src/commands/vpr.rs
index e043b9c8b0..e304ce12ae 100644
--- a/crates/vite_global_cli/src/commands/vpr.rs
+++ b/crates/vite_global_cli/src/commands/vpr.rs
@@ -15,7 +15,8 @@ pub async fn execute_vpr(args: &[String], cwd: &AbsolutePath) -> i32 {
}
let cwd_buf = cwd.to_absolute_path_buf();
- match super::delegate::execute(cwd_buf, "run", args).await {
+ // `vpr` is a shim, not a subcommand, so no subcommand was written.
+ match super::delegate::execute(cwd_buf, "run", args, None).await {
Ok(status) => status.code().unwrap_or(1),
Err(e) => {
output::error(&e.to_string());
diff --git a/crates/vite_global_cli/src/js_executor.rs b/crates/vite_global_cli/src/js_executor.rs
index 74e15f260e..6e8842d278 100644
--- a/crates/vite_global_cli/src/js_executor.rs
+++ b/crates/vite_global_cli/src/js_executor.rs
@@ -31,6 +31,8 @@ pub struct JsExecutor {
project_runtime: Option,
/// Directory containing JS scripts (from `VITE_GLOBAL_CLI_JS_SCRIPTS_DIR`)
scripts_dir: Option,
+ /// Subcommand as the user wrote it, forwarded to the CLI this one runs
+ raw_subcommand: Option,
}
impl JsExecutor {
@@ -41,7 +43,16 @@ impl JsExecutor {
/// If not provided, will be auto-detected from the binary location.
#[must_use]
pub const fn new(scripts_dir: Option) -> Self {
- Self { cli_runtime: None, project_runtime: None, scripts_dir }
+ Self { cli_runtime: None, project_runtime: None, scripts_dir, raw_subcommand: None }
+ }
+
+ /// Forward the subcommand as the user wrote it to the CLI this one runs.
+ ///
+ /// A command runs under its canonical name, so the spelling the user used is
+ /// otherwise lost on the way down.
+ pub fn with_raw_subcommand(mut self, raw_subcommand: Option<&str>) -> Self {
+ self.raw_subcommand = raw_subcommand.map(ToOwned::to_owned);
+ self
}
/// Get the JS scripts directory.
@@ -340,6 +351,9 @@ impl JsExecutor {
let mut cmd = Self::create_js_command(node_binary, bin_prefix);
cmd.arg(entry_point.as_path()).args(args).current_dir(project_path.as_path());
+ if let Some(raw_subcommand) = &self.raw_subcommand {
+ cmd.env(vite_shared::env_vars::VP_RAW_SUBCOMMAND, raw_subcommand);
+ }
Ok(cmd)
}
diff --git a/crates/vite_global_cli/src/main.rs b/crates/vite_global_cli/src/main.rs
index 5a90eef107..aad56e9738 100644
--- a/crates/vite_global_cli/src/main.rs
+++ b/crates/vite_global_cli/src/main.rs
@@ -35,7 +35,7 @@ use vite_shared::output;
pub use crate::cli::try_parse_args_from;
use crate::cli::{
- RenderOptions, command_with_help, run_command, run_command_with_options,
+ RenderOptions, command_with_help, raw_subcommand, run_command, run_command_with_options,
try_parse_args_from_with_options,
};
@@ -224,9 +224,11 @@ fn clap_error_to_exit_code(e: &clap::Error) -> ExitCode {
async fn run_corrected_args(cwd: &vite_path::AbsolutePathBuf, raw_args: &[String]) -> ExitCode {
let render_options = RenderOptions { show_header: false };
- let args_with_program = std::iter::once("vp".to_string()).chain(raw_args.iter().cloned());
- let normalized_args = normalize_args(args_with_program.collect());
-
+ let args_with_program: Vec =
+ std::iter::once("vp".to_string()).chain(raw_args.iter().cloned()).collect();
+ // The subcommand as written, taken before `normalize_args` can rewrite it.
+ let raw_subcommand = raw_subcommand(&args_with_program).map(str::to_owned);
+ let normalized_args = normalize_args(args_with_program);
let parsed = match try_parse_args_from_with_options(normalized_args, render_options) {
Ok(args) => args,
Err(e) => {
@@ -235,7 +237,9 @@ async fn run_corrected_args(cwd: &vite_path::AbsolutePathBuf, raw_args: &[String
}
};
- match run_command_with_options(cwd.clone(), parsed, render_options).await {
+ match run_command_with_options(cwd.clone(), parsed, render_options, raw_subcommand.as_deref())
+ .await
+ {
Ok(exit_status) => exit_status_to_exit_code(exit_status),
Err(e) => {
if e.is_user_message() {
@@ -353,6 +357,8 @@ async fn main() -> ExitCode {
// Capture user args (excluding argv0) before normalization.
let raw_args = args[1..].to_vec();
+ // The subcommand as written, taken before `normalize_args` can rewrite it.
+ let raw_subcommand = raw_subcommand(&args).map(str::to_owned);
// Normalize arguments (list/ls aliases, help rewriting)
let normalized_args = normalize_args(args);
@@ -427,7 +433,7 @@ async fn main() -> ExitCode {
clap_error_to_exit_code(&e)
}
}
- Ok(args) => match run_command(cwd.clone(), args).await {
+ Ok(args) => match run_command(cwd.clone(), args, raw_subcommand.as_deref()).await {
Ok(exit_status) => exit_status_to_exit_code(exit_status),
Err(e) => {
if e.is_user_message() {
diff --git a/crates/vite_shared/src/env_vars.rs b/crates/vite_shared/src/env_vars.rs
index 7e3de5b0dd..7f61af626c 100644
--- a/crates/vite_shared/src/env_vars.rs
+++ b/crates/vite_shared/src/env_vars.rs
@@ -74,6 +74,13 @@ pub const VP_SHIM_TOOL: &str = "VP_SHIM_TOOL";
/// before forwarding to the actual tool.
pub const VP_SHIM_WRAPPER: &str = "VP_SHIM_WRAPPER";
+/// The subcommand as the user wrote it, passed from the global CLI to the local
+/// one.
+///
+/// A command runs under its canonical name (`vp format` runs `fmt`), which loses
+/// the spelling. This carries the original alongside it.
+pub const VP_RAW_SUBCOMMAND: &str = "VP_RAW_SUBCOMMAND";
+
/// Path to the vp binary, passed to JS scripts so they can invoke CLI commands.
pub const VP_CLI_BIN: &str = "VP_CLI_BIN";
diff --git a/packages/cli/binding/src/cli/mod.rs b/packages/cli/binding/src/cli/mod.rs
index 5033a24339..294868a3b9 100644
--- a/packages/cli/binding/src/cli/mod.rs
+++ b/packages/cli/binding/src/cli/mod.rs
@@ -7,6 +7,7 @@ mod execution;
mod handler;
mod help;
mod resolver;
+mod script_note;
mod types;
use std::{borrow::Cow, env, ffi::OsStr, sync::Arc};
@@ -25,7 +26,7 @@ pub use types::{
use vite_error::Error;
use vite_path::{AbsolutePath, AbsolutePathBuf};
pub use vite_shared::init_tracing;
-use vite_shared::{PrependOptions, prepend_to_path_env};
+use vite_shared::{PrependOptions, env_vars, prepend_to_path_env};
use vite_str::Str;
use vite_task::{ExitStatus, Session, SessionConfig};
@@ -263,8 +264,13 @@ pub async fn main(
options: Option,
args: Option>,
) -> Result {
- let args_vec: Vec = args.unwrap_or_else(|| env::args().skip(1).collect());
- let args_vec = normalize_help_args(args_vec);
+ let raw_args: Vec = args.unwrap_or_else(|| env::args().skip(1).collect());
+ // The global CLI resolves aliases to their canonical names before
+ // delegating, so prefer the original spelling it forwards. A direct local
+ // invocation can use its first, still-unnormalized argument.
+ let raw_subcommand =
+ env::var(env_vars::VP_RAW_SUBCOMMAND).ok().or_else(|| raw_args.first().cloned());
+ let args_vec = normalize_help_args(raw_args);
if should_print_help(&args_vec) {
print_help();
return Ok(ExitStatus::SUCCESS);
@@ -277,7 +283,15 @@ pub async fn main(
};
match cli_args {
- CLIArgs::Synthesizable(subcmd) => execute_direct_subcommand(subcmd, &cwd, options).await,
+ CLIArgs::Synthesizable(subcmd) => {
+ // Only the built-ins can be mistaken for a script. `run`/`cache`
+ // below are the script path itself; `install` and friends
+ // legitimately trigger a project's `install` lifecycle scripts
+ // through the package manager, so redirecting those to `vpr` would
+ // be wrong; and `exec` names a binary rather than a task.
+ script_note::print(raw_subcommand.as_deref(), &cwd);
+ execute_direct_subcommand(subcmd, &cwd, options).await
+ }
CLIArgs::ViteTask(command) => execute_vite_task_command(command, cwd, options).await,
CLIArgs::PackageManager(pm) => execute_pm_command(pm, &cwd).await,
CLIArgs::Exec(exec_args) => crate::exec::execute(exec_args, &cwd).await,
diff --git a/packages/cli/binding/src/cli/script_note.rs b/packages/cli/binding/src/cli/script_note.rs
new file mode 100644
index 0000000000..8c519fc01a
--- /dev/null
+++ b/packages/cli/binding/src/cli/script_note.rs
@@ -0,0 +1,59 @@
+//! Notes for built-in commands that share a name with a `package.json` script.
+//!
+//! `vp dev` always runs the built-in dev server; the project's `dev` script is
+//! a separate thing, reached with `vpr dev`. Users regularly reach for the
+//! 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 vite_path::AbsolutePath;
+use vite_shared::output;
+use vite_task::MARKER_ENV_NAME;
+
+const NPM_LIFECYCLE_EVENT_ENV_NAME: &str = "npm_lifecycle_event";
+
+/// Point a built-in command at `vpr ` when a script of that name
+/// exists.
+///
+/// `command` is the built-in as the user spelled it: the global binary forwards
+/// the invoked subcommand, so `vp format` stays `format` here. A direct local
+/// invocation takes the first argument before help normalization.
+pub(super) fn print(command: Option<&str>, cwd: &AbsolutePath) {
+ let Some(command) = command else { return };
+ // A task spawned this command, so the user is already on a script-running
+ // path. npm-compatible runners set `npm_lifecycle_event`; Vite Task uses
+ // its own marker.
+ if std::env::var_os(MARKER_ENV_NAME).is_some()
+ || std::env::var_os(NPM_LIFECYCLE_EVENT_ENV_NAME).is_some_and(|event| !event.is_empty())
+ {
+ return;
+ }
+ if !has_package_json_script(cwd, command) {
+ return;
+ }
+
+ let built_in = format!("`vp {command}`").bright_blue().to_string();
+ let via_run = format!("`vpr {command}`").bright_blue().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."
+ ));
+}
+
+/// Whether the package enclosing `cwd` defines a `` script.
+///
+/// Walks up to the nearest `package.json`, which is the package `vp run`
+/// resolves the task from, so the note holds when a built-in runs from a
+/// subdirectory. It stops there rather than climbing to a package that happens
+/// to define the script: `vpr ` would not reach that one either.
+fn has_package_json_script(cwd: &AbsolutePath, name: &str) -> bool {
+ let Ok(package) = vite_workspace::find_package_root(cwd) else { return false };
+ serde_json::from_slice::(package.package_json.content()).is_ok_and(
+ |manifest| {
+ manifest
+ .get("scripts")
+ .and_then(|scripts| scripts.get(name))
+ .is_some_and(serde_json::Value::is_string)
+ },
+ )
+}
diff --git a/packages/cli/src/bin.ts b/packages/cli/src/bin.ts
index 86f0d6f255..d5f5e5effb 100644
--- a/packages/cli/src/bin.ts
+++ b/packages/cli/src/bin.ts
@@ -42,7 +42,8 @@ function getErrorMessage(err: unknown): string {
}
// Parse command line arguments
-let args = process.argv.slice(2);
+const typedArgs = process.argv.slice(2);
+let args = typedArgs;
// Transform `vp help [command]` into `vp [command] --help`
if (args[0] === 'help' && args[1]) {
@@ -87,7 +88,9 @@ if (command === 'create') {
test,
doc,
resolveUniversalViteConfig,
- args: process.argv.slice(2),
+ // The Rust CLI applies the `help [command]` transform itself, and needs
+ // the untransformed list to tell `vp help fmt` apart from `vp fmt --help`.
+ args: typedArgs,
});
let finalExitCode = exitCode;