Skip to content

Support system Node.js for vp commands (not just shims) #977

@qweered

Description

@qweered

Summary

I'd like an option to use system-installed Node.js for all parts of vp — not just shims, but also vp create, vp install, vp build, and every other command that delegates to JS. Currently there's no way to do this.

Problem

vp commands that delegate to JS always download a managed Node.js binary, even when a compatible node is already installed on the system. The existing system-first shim mode (vp env off) only applies to shimmed tools (node/npm/npx wrappers via dispatch.rs:679-703), not to vp commands themselves.

Who's affected

  • NixOS and GNU Guix — downloaded binaries are dynamically linked against /lib64/ld-linux-x86-64.so.2 which doesn't exist on these distros. vp create fails immediately:
    Could not start dynamically linked executable: ~/.vite-plus/js_runtime/node/24.14.0/bin/node
    NixOS cannot run dynamically linked executables intended for generic linux environments out of the box.
    
  • Distro packages (Nix, Homebrew, AUR, Fedora COPR, etc.) — packagers want to use the distro-managed Node.js
  • Air-gapped environments — no network access to download from nodejs.org
  • Container images — Node.js is already installed; downloading a second copy wastes time and space

Code path showing the gap

vp create
  → commands/delegate.rs:execute()
  → JsExecutor::delegate_to_local_cli()
  → js_executor.rs:ensure_project_runtime()
  → download_runtime_for_project()           ← always downloads
  → download_runtime_with_provider()         ← no system check here

The shim dispatch has find_system_tool() which locates system binaries — but JsExecutor never calls it. It goes directly to download_runtime() which only checks its own cache at ~/.vite-plus/js_runtime/node/{version}/.

After resolution, the downloaded Node.js is only used for two things:

  • get_binary_path() → passed to Command::new() to execute JS scripts
  • get_bin_prefix() → prepended to PATH so child processes find npm/npx

A system Node.js satisfies both of these identically.

What I'd like

A way to tell vp "use my system Node.js everywhere" — for shims and for all vp commands. Ideally this would be:

  1. An environment variable like VITE_PLUS_USE_SYSTEM_NODE=1 that makes all Node.js resolution prefer the system node in PATH before downloading
  2. Or extending system-first mode so that vp env off applies to JsExecutor too — if a user opts into system-first, they expect all node usage to prefer system node, not just shims

Suggested implementation

The simplest approach: add a system check in download_runtime_with_provider() (the single bottleneck all download paths funnel through), before the cache check at line 121:

use std::process::Command as StdCommand;

if runtime_type == JsRuntimeType::Node {
    if std::env::var_os("VITE_PLUS_USE_SYSTEM_NODE").is_some() {
        if let Ok(output) = StdCommand::new("node").arg("--version").output() {
            if output.status.success() {
                if let Ok(version_str) = String::from_utf8(output.stdout) {
                    let version_str = version_str.trim().trim_start_matches('v');
                    if let Some(path_var) = std::env::var_os("PATH") {
                        for dir in std::env::split_paths(&path_var) {
                            let candidate = dir.join("node");
                            if candidate.exists() {
                                if let Some(install_dir) = dir.parent() {
                                    if let Some(install_dir) =
                                        AbsolutePathBuf::new(install_dir.to_path_buf())
                                    {
                                        tracing::info!(
                                            "Using system Node.js v{version_str} at {:?}",
                                            candidate
                                        );
                                        return Ok(JsRuntime {
                                            runtime_type,
                                            version: version_str.into(),
                                            install_dir,
                                            binary_relative_path,
                                            bin_dir_relative_path,
                                        });
                                    }
                                }
                                break;
                            }
                        }
                    }
                }
            }
        }
    }
}

This covers all call paths at once:

  • JsExecutor::ensure_project_runtime()download_runtime() / download_runtime_for_project()
  • JsExecutor::ensure_cli_runtime()download_runtime_for_project()
  • Shim dispatch runtime resolution
  • vp env install / vp env use

Distro packager workaround (current)

Nix currently patches download_runtime_with_provider() unconditionally (no env var gate) and wraps the binary with nodejs in PATH. This works but diverges from upstream. An official option would let us drop the patch.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions