Skip to content

feat(js_runtime): add vite_js_runtime crate for JavaScript runtime management#508

Merged
fengmk2 merged 25 commits intomainfrom
nodejs-version-manager
Jan 29, 2026
Merged

feat(js_runtime): add vite_js_runtime crate for JavaScript runtime management#508
fengmk2 merged 25 commits intomainfrom
nodejs-version-manager

Conversation

@fengmk2
Copy link
Copy Markdown
Member

@fengmk2 fengmk2 commented Jan 26, 2026

This crate provides functionality to download and cache JavaScript runtimes
like Node.js. Features include:

  • Runtime spec parsing (e.g., "node@22.13.1")
  • Automatic platform detection (Linux/macOS/Windows + x64/arm64)
  • Download with retry logic and SHASUMS256.txt integrity verification
  • Caching to ~/.cache/vite/js_runtime/{runtime}/{version}/{platform}/
  • Atomic operations for concurrent-safe downloads

Also includes RFC document at rfcs/js-runtime.md describing the design.


Note

Adds a new vite_js_runtime crate to download, verify, extract, and cache JavaScript runtimes (initially Node.js) with an extensible provider architecture.

  • Implements NodeProvider with platform detection, SHASUMS256 verification, retrying downloads, ZIP/TAR.GZ extraction, and file-lock–based atomic installs into ~/.cache/vite/js_runtime/{runtime}/{version}
  • Supports semver range resolution via node-semver, ETag/TTL-cached version index, and uses cached versions when possible
  • Adds project integration helpers: parse devEngines.runtime from package.json, resolve version, download, and optionally write back the resolved version while preserving JSON formatting
  • Wires crate into workspace and dependencies; adds RFC at rfcs/js-runtime.md and minor config updates (typos, Cargo.lock)

Written by Cursor Bugbot for commit 8922738. This will update automatically on new commits. Configure here.

@fengmk2 fengmk2 self-assigned this Jan 26, 2026
Copy link
Copy Markdown
Member Author

fengmk2 commented Jan 26, 2026

Comment thread crates/vite_js_runtime/src/runtime.rs Outdated
@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented Jan 27, 2026

Deploying vite-plus with  Cloudflare Pages  Cloudflare Pages

Latest commit: a6745a0
Status:⚡️  Build in progress...

View logs

@fengmk2 fengmk2 force-pushed the nodejs-version-manager branch 2 times, most recently from 6e4df5c to b064bbf Compare January 27, 2026 03:20
@fengmk2 fengmk2 marked this pull request as ready for review January 27, 2026 09:15
@fengmk2 fengmk2 requested review from Brooooooklyn, branchseer and Copilot and removed request for Copilot January 27, 2026 09:15
@fengmk2 fengmk2 force-pushed the nodejs-version-manager branch from d4885e8 to 8922738 Compare January 27, 2026 09:23
Copilot AI review requested due to automatic review settings January 27, 2026 09:23
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a new vite_js_runtime crate for managing JavaScript runtime downloads and caching, specifically for Node.js with a design that allows future support for Bun and Deno. The implementation provides automatic version resolution from package.json's devEngines.runtime field, integrity verification via SHA256 checksums, and concurrent-safe caching.

Changes:

  • Adds vite_js_runtime crate with provider-based architecture for runtime management
  • Implements Node.js download support with version resolution (exact versions and semver ranges)
  • Includes comprehensive RFC document describing the design and architecture

Reviewed changes

Copilot reviewed 14 out of 15 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
rfcs/js-runtime.md Comprehensive RFC documenting the design, architecture, and implementation details
crates/vite_js_runtime/src/runtime.rs Main runtime download orchestration and project-based configuration support
crates/vite_js_runtime/src/providers/node.rs Node.js-specific provider with version resolution and caching
crates/vite_js_runtime/src/download.rs Generic download utilities with retry logic and hash verification
crates/vite_js_runtime/src/dev_engines.rs Package.json parsing with formatting preservation
crates/vite_js_runtime/src/provider.rs Provider trait definition for runtime abstraction
crates/vite_js_runtime/src/platform.rs Platform detection for OS and architecture
crates/vite_js_runtime/src/lib.rs Public API exports
crates/vite_js_runtime/src/error.rs Error type definitions
crates/vite_js_runtime/Cargo.toml Crate dependencies and configuration
Cargo.toml Workspace configuration with new dependencies
Cargo.lock Dependency lock file updates
CLAUDE.md Documentation on path type usage
.typos.toml Typo dictionary for "Jod" LTS codename

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread crates/vite_js_runtime/src/download.rs Outdated
Comment thread crates/vite_js_runtime/src/platform.rs
Comment thread crates/vite_js_runtime/src/download.rs
Comment thread crates/vite_js_runtime/src/dev_engines.rs
Comment thread crates/vite_js_runtime/src/runtime.rs
Comment thread crates/vite_js_runtime/src/download.rs
Comment thread crates/vite_js_runtime/src/providers/node.rs
@Brooooooklyn
Copy link
Copy Markdown
Member

cursor review

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 3 potential issues.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

Comment thread crates/vite_js_runtime/src/download.rs
Comment thread crates/vite_js_runtime/src/dev_engines.rs
Comment thread crates/vite_js_runtime/src/download.rs
…nagement

This crate provides functionality to download and cache JavaScript runtimes
like Node.js. Features include:

- Runtime spec parsing (e.g., "node@22.13.1")
- Automatic platform detection (Linux/macOS/Windows + x64/arm64)
- Download with retry logic and SHASUMS256.txt integrity verification
- Caching to ~/.cache/vite/js_runtime/{runtime}/{version}/{platform}/
- Atomic operations for concurrent-safe downloads

Also includes RFC document at rfcs/js-runtime.md describing the design.
The vite_js_runtime crate is a pure Rust library and does not need
NAPI bindings for JavaScript CLI usage.
Remove #[ignore] from integration tests so they run with the regular
test suite. The tests download a real Node.js version and verify
cache behavior.
…ection

Implement file-based locking in move_to_cache() to prevent race conditions
when multiple processes/threads try to install the same runtime version
concurrently. This follows the same pattern used in vite_install's
package_manager.rs.

Changes:
- Add version-specific lock file ({version}.lock) acquisition before move
- Use tokio::task::spawn_blocking to wrap blocking File::lock() call
- Re-check target existence after acquiring lock (another process may complete)
- Add test_concurrent_downloads() to verify no corruption with 4 parallel tasks
Remove unused dependencies detected by cargo-shear:
- serde from dependencies
- httpmock from dev-dependencies
- test-log from dev-dependencies

Add cargo-shear ignore for vite_js_runtime workspace dependency
(pending integration with vite_install).
Replace blanket #![allow(clippy::disallowed_types)] with targeted
#[expect(clippy::disallowed_types)] on specific functions that require
std::path::Path for external crate interop (tempfile, tar, zip).

- Change return types to use vite_str::Str instead of String
- Use vite_str::format! instead of std::format!
- Add targeted expects only where std::path::Path is needed
…ePath

Use project-standard path types instead of std::path::Path in helper
functions, eliminating most #[expect(clippy::disallowed_types)] attributes.

Changes:
- Convert TempDir::path() to AbsolutePathBuf at the call site
- Update download_file, verify_file_hash, extract_archive, extract_tar_gz,
  extract_zip, and move_to_cache to use &AbsolutePath
- Keep copy_dir_recursive with std::path::Path due to recursive entry.path()
- Update copy_dir_recursive to use &AbsolutePath instead of &std::path::Path
- Document vite_path usage patterns in CLAUDE.md
Remove unnecessary runtime spec parsing functionality:
- Remove parse_runtime_spec function and related tests
- Remove InvalidRuntimeSpec and UnsupportedRuntime error types
- Update RFC to reflect simplified API
Add support for VITE_NODE_DIST_MIRROR environment variable to override
the default Node.js distribution URL. This is useful for corporate
environments or regions where nodejs.org might be slow or blocked.

Example: VITE_NODE_DIST_MIRROR=https://npmmirror.com/mirrors/node
- Add JsRuntimeProvider trait for runtime-specific logic
- Create providers/ module with NodeProvider implementation
- Extract generic download utilities to download.rs
- Simplify platform.rs by moving runtime-specific logic to providers
- Update JsRuntime to store relative paths from provider
- Enable zip crate on all platforms for future Bun/Deno support
- Update RFC to document the new trait-based architecture

This refactoring separates concerns between generic download logic
and runtime-specific details, making it straightforward to add
support for Bun and Deno in the future.
Just throw error if fs::rename fails instead of falling back to
recursive copy. Also removes the unused copy_dir_recursive function.
If the install directory exists but the binary doesn't, it indicates
an incomplete or corrupted installation. Now we detect this and remove
the incomplete directory before downloading again.

Added test_incomplete_installation_cleanup to verify this behavior.
The platform is redundant in the cache path since a runtime always runs
on the current OS. Simplifies the structure from
`{runtime}/{version}/{platform}/` to `{runtime}/{version}/`.
…e download

Add a new function `download_runtime_for_project()` that reads `devEngines.runtime`
from a project's package.json and downloads the appropriate Node.js version.

Features:
- Parse devEngines.runtime as single object or array of runtimes
- Resolve semver ranges (^, ~, etc.) using node-semver crate
- Fetch and cache version index from nodejs.org/dist/index.json (1-hour TTL)
- Fall back to latest Node.js if no configuration found
- Support custom mirrors via VITE_NODE_DIST_MIRROR env var

New public API:
- `download_runtime_for_project(project_path)` - project-based download
- `NodeProvider::fetch_version_index()` - fetch cached version list
- `NodeProvider::resolve_version(version_req)` - resolve semver range
- `NodeProvider::resolve_latest_version()` - get latest version

New error variants:
- VersionIndexParseFailed, NoMatchingVersion, Json, SemverRange
…olution

- Add `fetch_with_cache_headers()` for conditional requests with If-None-Match
- Handle 304 Not Modified responses to refresh cache TTL without re-downloading
- Parse Cache-Control max-age header for dynamic TTL
- Fix `resolve_version()` to return highest matching version, not first
- Fix `resolve_latest_version()` to return highest LTS version, not first entry
- Extract logic into testable functions with comprehensive unit tests using mock data
After downloading a runtime without a specified version, write the
resolved version to devEngines.runtime in package.json. This allows
subsequent executions to skip version resolution.

- Add update_runtime_version() to update package.json with resolved version
- Detect and preserve original indentation (2/4 spaces or tabs)
- Handle all runtime config formats (single object, array, missing)
- Only write back when no version was specified (skip when version range exists)
- Add serde_json preserve_order feature to maintain key order
Add documentation for the new feature that writes resolved runtime
version back to package.json when no version was specified.
- Skip network resolution for exact versions (e.g., "20.18.0")
- Check locally cached versions first for range versions (e.g., "^20.18.0")
- Normalize 'v' prefix in exact versions to avoid double 'v' in URLs

This reduces unnecessary network requests when:
1. An exact version is specified - use it directly without validation
2. A range is specified and a cached version satisfies it - use cached version
- Skip network resolution for exact versions (e.g., "20.18.0")
- Check locally cached versions first for range versions (e.g., "^20.18.0")
- Normalize 'v' prefix in exact versions to avoid double 'v' in URLs
- Use semver max() instead of sort() for finding highest matching version

This reduces unnecessary network requests when:
1. An exact version is specified - use it directly without validation
2. A range is specified and a cached version satisfies it - use cached version
…ntime name

When devEngines.runtime is a single object with a different runtime name
(e.g., "deno"), calling update_or_create_runtime with "node" now correctly
converts to array format instead of overwriting the existing runtime's version.

This prevents corruption of user configuration data where a deno runtime
with version "^2.0.0" would incorrectly have its version replaced.
The move_to_cache function was acquiring a file lock but immediately
dropping it after spawn_blocking completed. This caused the lock to be
released before the actual rename operation, defeating the concurrent
download protection.

Now the lock_file handle is stored and kept alive until after the
fs::rename call completes, ensuring the critical section is protected.
… error

On Linux systems where /tmp is mounted as tmpfs and the cache directory
is on a different filesystem, fs::rename fails with EXDEV (cross-device
link) error. This is common on many Linux distributions.

Use TempDir::new_in(&cache_dir) instead of TempDir::new() to ensure the
temp directory and cache directory are on the same filesystem, allowing
the atomic rename operation to succeed.
Add compile_error! for unsupported operating systems and CPU architectures
to provide clear error messages during compilation instead of cryptic
"missing return value" errors.

Supported platforms:
- OS: Linux, macOS, Windows
- Arch: x86_64, aarch64
@fengmk2 fengmk2 force-pushed the nodejs-version-manager branch from 8922738 to a6745a0 Compare January 28, 2026 02:11
@Brooooooklyn
Copy link
Copy Markdown
Member

cursor review

@Brooooooklyn
Copy link
Copy Markdown
Member

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a6745a08d3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/vite_js_runtime/src/runtime.rs
Comment thread crates/vite_js_runtime/src/runtime.rs
@fengmk2 fengmk2 merged commit 79194a2 into main Jan 29, 2026
18 checks passed
Copy link
Copy Markdown
Member Author

fengmk2 commented Jan 29, 2026

Merge activity

@fengmk2 fengmk2 deleted the nodejs-version-manager branch January 29, 2026 08:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants