55
66use std:: process:: { ExitStatus , Output } ;
77
8+ use node_semver:: { Range , Version } ;
89use tokio:: process:: Command ;
910use vite_js_runtime:: {
1011 JsRuntime , JsRuntimeType , download_runtime, download_runtime_for_project, is_valid_version,
@@ -187,10 +188,8 @@ impl JsExecutor {
187188 config:: read_session_version ( ) . await
188189 } ;
189190 if let Some ( version) = session_version {
191+ self . check_runtime_compatibility ( & version) . await ?;
190192 let runtime = download_runtime ( JsRuntimeType :: Node , & version) . await ?;
191- if let Some ( req) = self . get_cli_engines_requirement ( ) . await {
192- check_runtime_compatibility ( & runtime, & req) ?;
193- }
194193 return Ok ( self . project_runtime . insert ( runtime) ) ;
195194 }
196195
@@ -205,20 +204,54 @@ impl JsExecutor {
205204 // At least one valid project source exists — delegate to
206205 // download_runtime_for_project for cache-aware range resolution
207206 // and intra-project fallback chain
208- download_runtime_for_project ( project_path) . await ?
207+ let runtime = download_runtime_for_project ( project_path) . await ?;
208+ self . check_runtime_compatibility ( & runtime. version ) . await ?;
209+ runtime
209210 } else {
210211 // No valid project source — check user default from config, then LTS
211212 let resolution = config:: resolve_version ( project_path) . await ?;
213+ self . check_runtime_compatibility ( & resolution. version ) . await ?;
212214 download_runtime ( JsRuntimeType :: Node , & resolution. version ) . await ?
213215 } ;
214- if let Some ( req) = self . get_cli_engines_requirement ( ) . await {
215- check_runtime_compatibility ( & runtime, & req) ?;
216- }
217216 self . project_runtime = Some ( runtime) ;
218217 }
219218 Ok ( self . project_runtime . as_ref ( ) . unwrap ( ) )
220219 }
221220
221+ /// Check that a resolved runtime's version satisfies vp's engine requirements.
222+ ///
223+ /// Skips silently when:
224+ /// - The runtime is a system install (version == `"system"`)
225+ /// - The version or requirement strings cannot be parsed as semver
226+ ///
227+ /// Returns [`Error::NodeVersionIncompatible`] when the version is parseable but
228+ /// outside the required range.
229+ async fn check_runtime_compatibility ( & self , version : & str ) -> Result < ( ) , Error > {
230+ let Some ( requirement) = self . get_cli_engines_requirement ( ) . await else { return Ok ( ( ) ) } ;
231+
232+ // System runtimes report "system" — we cannot inspect the actual version cheaply,
233+ // and the user has explicitly opted in via `vp env off`.
234+ if version == "system" {
235+ return Ok ( ( ) ) ;
236+ }
237+
238+ let normalized = version. strip_prefix ( 'v' ) . unwrap_or ( version) ;
239+ let Ok ( version) = Version :: parse ( normalized) else {
240+ return Ok ( ( ) ) ; // unparsable version — skip silently
241+ } ;
242+ let Ok ( range) = Range :: parse ( & requirement) else {
243+ return Ok ( ( ) ) ; // invalid range in package.json — skip silently
244+ } ;
245+
246+ if !range. satisfies ( & version) {
247+ return Err ( Error :: NodeVersionIncompatible {
248+ version : version. to_string ( ) ,
249+ requirement : requirement. to_string ( ) ,
250+ } ) ;
251+ }
252+ Ok ( ( ) )
253+ }
254+
222255 /// Download a specific Node.js version.
223256 ///
224257 /// This is used when we need a specific version regardless of
@@ -422,42 +455,6 @@ async fn has_valid_version_source(
422455 Ok ( engines_valid || dev_engines_valid)
423456}
424457
425- /// Check that a resolved runtime's version satisfies vp's engine requirements.
426- ///
427- /// Skips silently when:
428- /// - The runtime is a system install (version == `"system"`)
429- /// - The version or requirement strings cannot be parsed as semver
430- ///
431- /// Returns [`Error::NodeVersionIncompatible`] when the version is parseable but
432- /// outside the required range.
433- fn check_runtime_compatibility ( runtime : & JsRuntime , requirement : & str ) -> Result < ( ) , Error > {
434- use node_semver:: { Range , Version } ;
435-
436- let version_str = runtime. version ( ) ;
437-
438- // System runtimes report "system" — we cannot inspect the actual version cheaply,
439- // and the user has explicitly opted in via `vp env off`.
440- if version_str == "system" {
441- return Ok ( ( ) ) ;
442- }
443-
444- let normalized = version_str. strip_prefix ( 'v' ) . unwrap_or ( version_str) ;
445- let Ok ( version) = Version :: parse ( normalized) else {
446- return Ok ( ( ) ) ; // unparsable version — skip silently
447- } ;
448- let Ok ( range) = Range :: parse ( requirement) else {
449- return Ok ( ( ) ) ; // invalid range in package.json — skip silently
450- } ;
451-
452- if !range. satisfies ( & version) {
453- return Err ( Error :: NodeVersionIncompatible {
454- version : version_str. to_string ( ) ,
455- requirement : requirement. to_string ( ) ,
456- } ) ;
457- }
458- Ok ( ( ) )
459- }
460-
461458/// Try to find system Node.js when in system-first mode (`vp env off`).
462459///
463460/// Returns `Some(JsRuntime)` when both conditions are met:
0 commit comments