├── .gitignore ├── rustfmt.toml ├── .editorconfig ├── src ├── subcommand │ ├── mod.rs │ ├── uninstall.rs │ ├── parse_version.rs │ ├── is_installed.rs │ ├── switch.rs │ ├── install.rs │ └── list.rs ├── constants.rs ├── files │ ├── package_json.rs │ └── mod.rs ├── archives.rs ├── main.rs └── node_version.rs ├── .github ├── renovate.json5 └── workflows │ ├── release.yml │ └── ci.yml ├── test-data └── node-versions.json ├── LICENSE-MIT ├── Taskfile.yml ├── Cargo.toml ├── tests ├── switch_test.rs ├── install_test.rs ├── uninstall_test.rs └── utils.rs ├── README.md ├── LICENSE-APACHE └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .task 3 | /target 4 | integration 5 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | newline_style = "Unix" 2 | use_field_init_shorthand = true 3 | 4 | ### Mine 5 | trailing_comma = "Vertical" 6 | match_block_trailing_comma = true 7 | 8 | imports_granularity = "Crate" 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset=utf-8 3 | end_of_line=lf 4 | trim_trailing_whitespace=true 5 | insert_final_newline=true 6 | indent_style=space 7 | indent_size=2 8 | tab_width=2 9 | 10 | [*.rs] 11 | indent_size=4 12 | tab_width=4 13 | -------------------------------------------------------------------------------- /src/subcommand/mod.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | 3 | use crate::Config; 4 | 5 | pub mod install; 6 | pub mod is_installed; 7 | pub mod list; 8 | pub mod parse_version; 9 | pub mod switch; 10 | pub mod uninstall; 11 | 12 | pub trait Action { 13 | fn run(config: &Config, options: &T) -> Result<()>; 14 | } 15 | -------------------------------------------------------------------------------- /.github/renovate.json5: -------------------------------------------------------------------------------- 1 | { 2 | $schema: "https://docs.renovatebot.com/renovate-schema.json", 3 | extends: ["config:base", ":scheduleMonthly"], 4 | prHourlyLimit: 5, 5 | prConcurrentLimit: 5, 6 | branchConcurrentLimit: 5, 7 | labels: ["dependencies"], 8 | baseBranches: ["main"], 9 | packageRules: [ 10 | { 11 | matchUpdateTypes: ["patch", "minor"], 12 | matchManagers: ["cargo"], 13 | automerge: true, 14 | }, 15 | ], 16 | } 17 | -------------------------------------------------------------------------------- /src/constants.rs: -------------------------------------------------------------------------------- 1 | #[cfg(windows)] 2 | pub const EXEC_EXT: &str = ".cmd"; 3 | #[cfg(not(windows))] 4 | pub const EXEC_EXT: &str = ""; 5 | 6 | #[cfg(target_os = "windows")] 7 | pub const PLATFORM: &str = "win"; 8 | #[cfg(target_os = "macos")] 9 | pub const PLATFORM: &str = "darwin"; 10 | #[cfg(target_os = "linux")] 11 | pub const PLATFORM: &str = "linux"; 12 | 13 | #[cfg(target_os = "windows")] 14 | pub const EXT: &str = ".zip"; 15 | #[cfg(target_os = "macos")] 16 | pub const EXT: &str = ".tar.gz"; 17 | #[cfg(target_os = "linux")] 18 | pub const EXT: &str = ".tar.gz"; 19 | 20 | #[cfg(target_arch = "x86_64")] 21 | pub const ARCH: &str = "x64"; 22 | #[cfg(target_arch = "x86")] 23 | pub const ARCH: &str = "x86"; 24 | #[cfg(target_arch = "aarch64")] 25 | pub const ARCH: &str = "arm64"; 26 | 27 | pub const X64: &str = "x64"; 28 | -------------------------------------------------------------------------------- /test-data/node-versions.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "version": "14.6.0", 4 | "date": "2020-07-15", 5 | "files": [] 6 | }, 7 | { 8 | "version": "14.5.0", 9 | "date": "2020-06-30", 10 | "files": [] 11 | }, 12 | { 13 | "version": "13.14.0", 14 | "date": "2020-04-28", 15 | "files": [] 16 | }, 17 | { 18 | "version": "13.13.0", 19 | "date": "2020-04-14", 20 | "files": [] 21 | }, 22 | { 23 | "version": "12.18.3", 24 | "date": "2020-07-22", 25 | "files": [] 26 | }, 27 | { 28 | "version": "12.18.2", 29 | "date": "2020-06-30", 30 | "files": [] 31 | }, 32 | { 33 | "version": "11.15.0", 34 | "date": "2019-04-30", 35 | "files": [] 36 | }, 37 | { 38 | "version": "11.14.0", 39 | "date": "2019-04-10", 40 | "files": [] 41 | } 42 | ] 43 | -------------------------------------------------------------------------------- /src/files/package_json.rs: -------------------------------------------------------------------------------- 1 | use std::{fs, path::PathBuf}; 2 | 3 | use node_semver::Range; 4 | use serde::Deserialize; 5 | 6 | #[derive(Clone, Deserialize, Debug, Eq, PartialEq)] 7 | pub struct PackageJson { 8 | #[serde()] 9 | pub name: Option, 10 | #[serde()] 11 | pub version: Option, 12 | #[serde()] 13 | pub engines: Option, 14 | } 15 | 16 | #[derive(Clone, Deserialize, Debug, Eq, PartialEq)] 17 | pub struct PackageJsonEngines { 18 | #[serde()] 19 | pub node: Option, 20 | } 21 | 22 | impl TryFrom for PackageJson { 23 | type Error = anyhow::Error; 24 | 25 | fn try_from(path: PathBuf) -> Result { 26 | let contents = fs::read_to_string(path)?; 27 | let package_json: PackageJson = serde_json::from_str(&contents)?; 28 | 29 | Ok(package_json) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright 2022 Adam Haglund 2 | Copyright (c) 2014 The Rust Project Developers 3 | 4 | Permission is hereby granted, free of charge, to any 5 | person obtaining a copy of this software and associated 6 | documentation files (the "Software"), to deal in the 7 | Software without restriction, including without 8 | limitation the rights to use, copy, modify, merge, 9 | publish, distribute, sublicense, and/or sell copies of 10 | the Software, and to permit persons to whom the Software 11 | is furnished to do so, subject to the following 12 | conditions: 13 | 14 | The above copyright notice and this permission notice 15 | shall be included in all copies or substantial portions 16 | of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 19 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 20 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 21 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 22 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 23 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 24 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 25 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 26 | DEALINGS IN THE SOFTWARE. 27 | -------------------------------------------------------------------------------- /Taskfile.yml: -------------------------------------------------------------------------------- 1 | version: 3 2 | 3 | tasks: 4 | lint: 5 | desc: Lint code 6 | cmds: 7 | - cargo clippy --all-targets {{.CLI_ARGS}} 8 | - cargo fmt --check --all 9 | lint:fix: 10 | desc: Lint code and fix problems with autofixes 11 | cmds: 12 | - cargo clippy --fix --allow-staged --allow-dirty 13 | - task: format 14 | 15 | format: 16 | desc: Format code 17 | cmds: 18 | - cargo fmt --all {{.CLI_ARGS}} 19 | 20 | test: 21 | desc: Run tests 22 | sources: 23 | - Cargo.* 24 | - src/** 25 | - test-data/** 26 | - tests/** 27 | cmds: 28 | - cargo test {{.CLI_ARGS}} 29 | 30 | run: 31 | desc: "Run the CLI with a debug build: task run -- <...args>" 32 | cmds: 33 | - cargo run {{.CLI_ARGS}} 34 | 35 | build: 36 | desc: Build debug artifacts 37 | sources: 38 | - Cargo.* 39 | - src/** 40 | generates: 41 | - target/debug/** 42 | cmds: 43 | - cargo build {{.CLI_ARGS}} 44 | 45 | build:release: 46 | desc: Build release artifacts 47 | sources: 48 | - Cargo.* 49 | - src/** 50 | generates: 51 | - target/release/** 52 | cmds: 53 | - cargo build --release --locked {{.CLI_ARGS}} 54 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nvm-rust" 3 | version = "0.4.3" 4 | description = "A node version manager that doesn't suck" 5 | authors = ["BeeeQueue "] 6 | repository = "https://github.com/BeeeQueue/nvm-rust" 7 | homepage = "https://github.com/BeeeQueue/nvm-rust" 8 | license = "MIT OR Apache-2.0" 9 | readme = "README.md" 10 | keywords = ["node", "version", "manager", "nvm"] 11 | categories = ["command-line-utilities"] 12 | edition = "2021" 13 | 14 | exclude = [ 15 | ".github/", 16 | "tests/", 17 | "test-data/", 18 | ] 19 | 20 | [profile.release] 21 | strip = true 22 | lto = true 23 | 24 | [[bin]] 25 | name = "nvm" 26 | path = "src/main.rs" 27 | 28 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 29 | 30 | [dependencies] 31 | anyhow = "1.0.100" 32 | clap = { version = "4.5.48", features = ["derive", "env", "cargo"] } 33 | dialoguer = "0.12.0" 34 | dirs = "6.0.0" 35 | itertools = "0.14.0" 36 | node-semver = "2.2.0" 37 | serde = { version = "1.0.228", features = ["derive"] } 38 | serde_json = "1.0.145" 39 | ureq = { version = "3.1.2", features = ["native-tls", "json"] } 40 | 41 | [target.'cfg(unix)'.dependencies] 42 | flate2 = "1.1.4" 43 | tar = "0.4.44" 44 | 45 | [target.'cfg(windows)'.dependencies] 46 | zip = "6.0.0" 47 | 48 | [dev-dependencies] 49 | assert_cmd = "2.0.17" 50 | assert_fs = "1.1.3" 51 | predicates = "3.1.3" 52 | speculoos = "0.13.0" 53 | -------------------------------------------------------------------------------- /src/subcommand/uninstall.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::Parser; 3 | use node_semver::Range; 4 | 5 | use crate::{ 6 | node_version::{parse_range, InstalledNodeVersion, NodeVersion}, 7 | subcommand::Action, 8 | Config, 9 | }; 10 | 11 | #[derive(Parser, Clone, Debug)] 12 | #[command( 13 | about = "Uninstall a version", 14 | alias = "r", 15 | alias = "rm", 16 | alias = "remove" 17 | )] 18 | pub struct UninstallCommand { 19 | /// A semver range. The latest version matching this range will be installed 20 | #[arg(value_parser = parse_range)] 21 | pub version: Range, 22 | } 23 | 24 | impl Action for UninstallCommand { 25 | fn run(config: &Config, options: &UninstallCommand) -> Result<()> { 26 | let version = InstalledNodeVersion::find_matching(config, &options.version); 27 | if version.is_none() { 28 | anyhow::bail!("{} is not installed.", &options.version.to_string()) 29 | } 30 | 31 | let version = version.unwrap(); 32 | if version.is_selected(config) { 33 | println!("{} is currently selected.", version.version()); 34 | 35 | if !config.force 36 | && !(dialoguer::Confirm::new() 37 | .with_prompt("Are you sure you want to uninstall it?") 38 | .interact()?) 39 | { 40 | return Ok(()); 41 | } 42 | 43 | InstalledNodeVersion::deselect(config)?; 44 | } 45 | 46 | version.uninstall(config) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tests/switch_test.rs: -------------------------------------------------------------------------------- 1 | mod utils; 2 | 3 | mod switch { 4 | use anyhow::Result; 5 | 6 | use crate::utils; 7 | 8 | #[test] 9 | fn can_switch_version_with_no_previous_one() -> Result<()> { 10 | let (temp_dir, mut cmd) = utils::setup_integration_test()?; 11 | 12 | let version_str = "12.18.3"; 13 | utils::install_mock_version(&temp_dir, version_str)?; 14 | let result = cmd.arg("use").arg("12").assert(); 15 | 16 | let output = String::from_utf8(result.get_output().to_owned().stdout)?; 17 | let output = output.trim(); 18 | 19 | assert_eq!(output, "Switched to 12.18.3"); 20 | assert_eq!( 21 | utils::get_selected_version(&temp_dir), 22 | Some(version_str.to_string()) 23 | ); 24 | 25 | temp_dir.close().map_err(anyhow::Error::from) 26 | } 27 | 28 | #[test] 29 | fn can_switch_version_with_previous_version() -> Result<()> { 30 | let (temp_dir, mut cmd) = utils::setup_integration_test()?; 31 | let old_version = "12.18.3"; 32 | let new_version = "14.5.0"; 33 | 34 | utils::install_mock_version(&temp_dir, old_version)?; 35 | utils::install_mock_version(&temp_dir, new_version)?; 36 | utils::create_shim(&temp_dir, old_version)?; 37 | 38 | let result = cmd.arg("use").arg("14").assert(); 39 | 40 | assert_eq!( 41 | utils::get_selected_version(&temp_dir), 42 | Some(new_version.to_string()) 43 | ); 44 | utils::assert_outputs_contain(&result, "Switched to 14.5.0", "")?; 45 | 46 | temp_dir.close().map_err(anyhow::Error::from) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/subcommand/parse_version.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::Parser; 3 | use node_semver::Range; 4 | 5 | use crate::{files, node_version::parse_range, subcommand::Action, Config}; 6 | 7 | #[derive(Parser, Clone, Debug)] 8 | #[command( 9 | about = "Echo what a version string will be parsed to", 10 | alias = "pv", 11 | hide(true) 12 | )] 13 | pub struct ParseVersionCommand { 14 | /// The semver range to echo the parsed result of 15 | #[arg(value_parser = parse_range)] 16 | pub version: Option, 17 | } 18 | 19 | impl Action for ParseVersionCommand { 20 | fn run(_: &Config, options: &ParseVersionCommand) -> Result<()> { 21 | let version = options.version.clone(); 22 | 23 | if version.is_none() { 24 | if let Some(version_from_files) = files::get_version_file() { 25 | println!("{}", version_from_files.range()); 26 | 27 | return Ok(()); 28 | } 29 | } 30 | 31 | if version.is_none() { 32 | anyhow::bail!("Did not get a version"); 33 | } 34 | let version = version.unwrap(); 35 | 36 | match Range::parse(&version) { 37 | Ok(result) => { 38 | println!( 39 | "{:^pad$}\n{:^pad$}\n{}", 40 | version, 41 | "⬇", 42 | result, 43 | pad = result.to_string().len() 44 | ); 45 | Ok(()) 46 | }, 47 | Err(err) => { 48 | println!("Failed to parse `{}`", err.input()); 49 | Ok(()) 50 | }, 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_dispatch: 3 | push: 4 | tags: 5 | - v* 6 | 7 | name: release 8 | 9 | env: 10 | FORCE_COLOR: 3 11 | TERM: xterm-256color 12 | 13 | jobs: 14 | create-release: 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | 20 | - uses: dtolnay/rust-toolchain@master 21 | with: 22 | toolchain: nightly 23 | 24 | - uses: Swatinem/rust-cache@v2 25 | 26 | - run: cargo publish 27 | env: 28 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} 29 | 30 | - uses: softprops/action-gh-release@v1 31 | with: 32 | draft: true 33 | 34 | 35 | build: 36 | name: build (${{ matrix.os }}) 37 | 38 | needs: [create-release] 39 | 40 | strategy: 41 | fail-fast: false 42 | matrix: 43 | include: 44 | - os: macos-latest 45 | file-name: nvm 46 | display-name: nvm-macos 47 | - os: ubuntu-latest 48 | file-name: nvm 49 | display-name: nvm-linux 50 | - os: windows-latest 51 | file-name: nvm.exe 52 | display-name: nvm-win.exe 53 | 54 | runs-on: ${{ matrix.os }} 55 | 56 | steps: 57 | - uses: actions/checkout@v4 58 | 59 | - uses: arduino/setup-task@v1 60 | with: 61 | repo-token: ${{ secrets.CUSTOM_GITHUB_TOKEN }} 62 | 63 | - uses: dtolnay/rust-toolchain@master 64 | with: 65 | toolchain: nightly 66 | 67 | - uses: Swatinem/rust-cache@v2 68 | 69 | - run: task build:release 70 | 71 | - run: mv target/release/${{ matrix.file-name }} target/release/${{ matrix.display-name }} 72 | 73 | - uses: softprops/action-gh-release@v1 74 | with: 75 | draft: true 76 | files: target/release/${{ matrix.display-name }} 77 | -------------------------------------------------------------------------------- /src/subcommand/is_installed.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::Parser; 3 | use node_semver::Range; 4 | 5 | use crate::{ 6 | files, 7 | node_version::{parse_range, InstalledNodeVersion, NodeVersion}, 8 | subcommand::Action, 9 | Config, 10 | }; 11 | 12 | #[derive(Parser, Clone, Debug)] 13 | #[command( 14 | about = "Check if a version is installed", 15 | alias = "isi", 16 | alias = "installed" 17 | )] 18 | pub struct IsInstalledCommand { 19 | /// A semver range. Will be matched against installed all installed versions. 20 | #[arg(value_parser = parse_range)] 21 | pub version: Option, 22 | /// Which exit code to use when a version is not installed. 23 | #[arg(long, short = 'e', default_value = "1")] 24 | pub exit_code: i32, 25 | /// Silence output. 26 | #[arg(long, short = 'q')] 27 | pub quiet: bool, 28 | } 29 | 30 | impl Action for IsInstalledCommand { 31 | fn run(config: &Config, options: &IsInstalledCommand) -> Result<()> { 32 | let version_filter = options 33 | .version 34 | .clone() 35 | .or_else(|| files::get_version_file().map(|version_file| version_file.range())); 36 | 37 | if version_filter.is_none() { 38 | anyhow::bail!("You did not pass a version and we did not find any version files (package.json#engines, .nvmrc) in the current directory."); 39 | } 40 | let version_filter = version_filter.unwrap(); 41 | 42 | let installed_versions = InstalledNodeVersion::list(config); 43 | for installed_version in installed_versions { 44 | if !version_filter.satisfies(installed_version.version()) { 45 | continue; 46 | } 47 | 48 | if !options.quiet { 49 | println!( 50 | "✅ A version matching {version_filter} is installed ({})!", 51 | installed_version 52 | ); 53 | } 54 | return Ok(()); 55 | } 56 | 57 | if !options.quiet { 58 | println!("❌ A version matching {version_filter} is not installed."); 59 | } 60 | 61 | std::process::exit(options.exit_code) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: [ push ] 2 | 3 | name: ci 4 | 5 | env: 6 | FORCE_COLOR: 3 7 | TERM: xterm-256color 8 | 9 | jobs: 10 | build: 11 | name: build (${{ matrix.os }}) 12 | 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | include: 17 | - os: macos-latest 18 | file-name: nvm 19 | - os: ubuntu-latest 20 | file-name: nvm 21 | - os: windows-latest 22 | file-name: nvm.exe 23 | 24 | runs-on: ${{ matrix.os }} 25 | 26 | steps: 27 | - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 28 | 29 | - uses: arduino/setup-task@b91d5d2c96a56797b48ac1e0e89220bf64044611 # v2 30 | with: 31 | repo-token: ${{ secrets.CUSTOM_GITHUB_TOKEN }} 32 | 33 | - uses: dtolnay/rust-toolchain@6d653acede28d24f02e3cd41383119e8b1b35921 34 | with: 35 | toolchain: nightly 36 | 37 | - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2 38 | 39 | - run: task build:release 40 | 41 | - name: Upload artifacts 42 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 43 | with: 44 | name: nvm-${{ matrix.os }} 45 | path: target/release/${{ matrix.file-name }} 46 | 47 | test: 48 | timeout-minutes: 15 49 | continue-on-error: true 50 | strategy: 51 | matrix: 52 | os: [ macos-latest, ubuntu-latest, windows-latest ] 53 | 54 | runs-on: ${{ matrix.os }} 55 | 56 | steps: 57 | - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 58 | 59 | - uses: arduino/setup-task@b91d5d2c96a56797b48ac1e0e89220bf64044611 # v2 60 | with: 61 | repo-token: ${{ secrets.CUSTOM_GITHUB_TOKEN }} 62 | 63 | - uses: dtolnay/rust-toolchain@6d653acede28d24f02e3cd41383119e8b1b35921 64 | with: 65 | toolchain: nightly 66 | 67 | - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2 68 | 69 | - run: task test 70 | 71 | clippy: 72 | runs-on: ubuntu-latest 73 | 74 | steps: 75 | - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 76 | 77 | - uses: arduino/setup-task@b91d5d2c96a56797b48ac1e0e89220bf64044611 # v2 78 | with: 79 | repo-token: ${{ secrets.CUSTOM_GITHUB_TOKEN }} 80 | 81 | - uses: dtolnay/rust-toolchain@6d653acede28d24f02e3cd41383119e8b1b35921 82 | with: 83 | toolchain: nightly 84 | components: rustfmt, clippy 85 | 86 | - run: task lint 87 | -------------------------------------------------------------------------------- /src/files/mod.rs: -------------------------------------------------------------------------------- 1 | use std::{fs, path::PathBuf}; 2 | 3 | use itertools::Itertools; 4 | use node_semver::Range; 5 | 6 | pub mod package_json; 7 | 8 | const PACKAGE_JSON_FILE_NAME: &str = "package.json"; 9 | const NVMRC_FILE_NAME: &str = ".nvmrc"; 10 | const NODE_VERSION_FILE_NAME: &str = ".node-version"; 11 | const ASDF_FILE_NAME: &str = ".tool-versions"; 12 | 13 | pub enum VersionFile { 14 | Nvmrc(Range), 15 | PackageJson(Range), 16 | Asdf(Range), 17 | } 18 | 19 | impl VersionFile { 20 | pub fn range(self) -> Range { 21 | match self { 22 | VersionFile::Nvmrc(range) => range, 23 | VersionFile::PackageJson(range) => range, 24 | VersionFile::Asdf(range) => range, 25 | } 26 | } 27 | } 28 | 29 | pub fn get_version_file() -> Option { 30 | if PathBuf::from(PACKAGE_JSON_FILE_NAME).exists() { 31 | let parse_result = 32 | package_json::PackageJson::try_from(PathBuf::from(PACKAGE_JSON_FILE_NAME)); 33 | 34 | if let Ok(parse_result) = parse_result { 35 | return parse_result 36 | .engines 37 | .and_then(|engines| engines.node) 38 | .map(VersionFile::PackageJson); 39 | } else { 40 | println!( 41 | "Failed to parse package.json: {}", 42 | parse_result.unwrap_err() 43 | ); 44 | } 45 | } 46 | 47 | if let Some(existing_file) = [NVMRC_FILE_NAME, NODE_VERSION_FILE_NAME] 48 | .iter() 49 | .find_or_first(|&path| PathBuf::from(path).exists()) 50 | { 51 | let contents = fs::read_to_string(existing_file); 52 | 53 | if let Ok(contents) = contents { 54 | let parse_result = Range::parse(contents.trim()); 55 | 56 | if let Ok(parse_result) = parse_result { 57 | return Some(VersionFile::Nvmrc(parse_result)); 58 | } else { 59 | println!( 60 | "Failed to parse {}: '{}'", 61 | existing_file, 62 | parse_result.unwrap_err().input(), 63 | ); 64 | } 65 | } 66 | } 67 | 68 | if PathBuf::from(ASDF_FILE_NAME).exists() { 69 | let contents = fs::read_to_string(ASDF_FILE_NAME); 70 | 71 | if let Ok(contents) = contents { 72 | let version_string = contents 73 | .lines() 74 | .find(|line| line.starts_with("nodejs")) 75 | .and_then(|line| line.split(' ').nth(1)); 76 | 77 | if let Some(version_string) = version_string { 78 | let parse_result = Range::parse(version_string); 79 | 80 | if let Ok(parse_result) = parse_result { 81 | return Some(VersionFile::Asdf(parse_result)); 82 | } else { 83 | println!( 84 | "Failed to parse {}: '{}'", 85 | ASDF_FILE_NAME, 86 | parse_result.unwrap_err().input(), 87 | ); 88 | } 89 | } 90 | } 91 | } 92 | 93 | None 94 | } 95 | -------------------------------------------------------------------------------- /tests/install_test.rs: -------------------------------------------------------------------------------- 1 | mod utils; 2 | 3 | mod install { 4 | use crate::utils; 5 | use anyhow::Result; 6 | 7 | #[test] 8 | fn can_install_version_matching_range() -> Result<()> { 9 | let (temp_dir, mut cmd) = utils::setup_integration_test()?; 10 | 11 | let version_range = ">=14, <14.21"; 12 | let result = cmd 13 | .arg("install") 14 | .arg("--force") 15 | .arg(version_range) 16 | .assert(); 17 | 18 | utils::assert_outputs_contain( 19 | &result, 20 | "Downloading from https://nodejs.org/dist/v14.20.1/node-v14.20.1-", 21 | "", 22 | )?; 23 | utils::assert_version_installed(&temp_dir, "14.20.1", true)?; 24 | 25 | temp_dir.close().map_err(anyhow::Error::from) 26 | } 27 | 28 | #[test] 29 | fn can_install_version_matching_exact_version() -> Result<()> { 30 | let (temp_dir, mut cmd) = utils::setup_integration_test()?; 31 | 32 | let version_str = "14.21.2"; 33 | let result = cmd.arg("install").arg("--force").arg(version_str).assert(); 34 | 35 | utils::assert_outputs_contain( 36 | &result, 37 | "Downloading from https://nodejs.org/dist/v14.21.2/node-v14.21.2-", 38 | "", 39 | )?; 40 | utils::assert_version_installed(&temp_dir, version_str, true)?; 41 | 42 | temp_dir.close().map_err(anyhow::Error::from) 43 | } 44 | 45 | #[test] 46 | fn stops_when_installing_installed_version() -> Result<()> { 47 | let (temp_dir, mut cmd) = utils::setup_integration_test()?; 48 | 49 | let version_str = "14.21.2"; 50 | utils::install_mock_version(&temp_dir, version_str)?; 51 | 52 | let result = cmd.arg("install").arg(version_str).assert(); 53 | 54 | utils::assert_outputs_contain(&result, "14.21.2 is already installed - skipping...", "")?; 55 | 56 | temp_dir.close().map_err(anyhow::Error::from) 57 | } 58 | 59 | #[test] 60 | fn force_forces_install_of_installed_version() -> Result<()> { 61 | let (temp_dir, mut cmd) = utils::setup_integration_test()?; 62 | 63 | let version_str = "14.21.2"; 64 | let result = cmd.arg("install").arg("--force").arg(version_str).assert(); 65 | 66 | utils::assert_outputs_contain( 67 | &result, 68 | "Downloading from https://nodejs.org/dist/v14.21.2/node-v14.21.2-", 69 | "", 70 | )?; 71 | utils::assert_outputs_contain(&result, "Extracting...", "")?; 72 | utils::assert_version_installed(&temp_dir, version_str, true)?; 73 | 74 | temp_dir.close().map_err(anyhow::Error::from) 75 | } 76 | 77 | #[test] 78 | fn exits_gracefully_if_no_version_is_found() -> Result<()> { 79 | let (temp_dir, mut cmd) = utils::setup_integration_test()?; 80 | 81 | let result = cmd.arg("install").arg("--force").arg("12.99.99").assert(); 82 | 83 | utils::assert_outputs_contain( 84 | &result, 85 | "", 86 | "Error: Did not find a version matching `12.99.99`!", 87 | )?; 88 | 89 | temp_dir.close().map_err(anyhow::Error::from) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/subcommand/switch.rs: -------------------------------------------------------------------------------- 1 | use std::fs::read_link; 2 | #[cfg(windows)] 3 | use std::fs::remove_dir; 4 | #[cfg(unix)] 5 | use std::fs::remove_file; 6 | #[cfg(unix)] 7 | use std::os::unix::fs::symlink; 8 | #[cfg(windows)] 9 | use std::os::windows::fs::symlink_dir; 10 | 11 | use anyhow::Result; 12 | use clap::Parser; 13 | use node_semver::{Range, Version}; 14 | 15 | use crate::{ 16 | files, 17 | node_version::{parse_range, InstalledNodeVersion, NodeVersion}, 18 | subcommand::Action, 19 | Config, 20 | }; 21 | 22 | #[derive(Parser, Clone, Debug)] 23 | #[command(about = "Switch to an installed node version", alias = "switch")] 24 | pub struct SwitchCommand { 25 | /// A semver range. The latest version matching this range will be switched to. 26 | #[arg(value_parser = parse_range)] 27 | pub version: Option, 28 | } 29 | 30 | impl Action for SwitchCommand { 31 | fn run(config: &Config, options: &SwitchCommand) -> Result<()> { 32 | let version_filter = options 33 | .clone() 34 | .version 35 | .or_else(|| files::get_version_file().map(|version_file| version_file.range())); 36 | 37 | if version_filter.is_none() { 38 | anyhow::bail!("You did not pass a version and we did not find any version files (package.json#engines, .nvmrc) in the current directory."); 39 | } 40 | let version_filter = version_filter.unwrap(); 41 | 42 | let version = InstalledNodeVersion::find_matching(config, &version_filter); 43 | if version.is_none() { 44 | anyhow::bail!("No version matching the version range was found.") 45 | } 46 | 47 | let version = version.unwrap(); 48 | 49 | if !InstalledNodeVersion::is_installed(config, version.version()) { 50 | anyhow::bail!("{} is not installed", version); 51 | } 52 | 53 | let result = set_shims(config, version.version()); 54 | if let Ok(()) = result { 55 | println!("Switched to {}", version); 56 | } 57 | 58 | result 59 | } 60 | } 61 | 62 | #[cfg(windows)] 63 | fn set_shims(config: &Config, version: &Version) -> Result<()> { 64 | let shims_dir = config.get_shims_dir(); 65 | 66 | if !InstalledNodeVersion::is_installed(config, version) { 67 | anyhow::bail!("{version} is not installed"); 68 | } 69 | 70 | if read_link(&shims_dir).is_ok() { 71 | if let Err(err) = remove_dir(&shims_dir) { 72 | anyhow::bail!("Could not remove old symlink at {shims_dir:?}: {err}",); 73 | } 74 | } 75 | 76 | symlink_dir( 77 | config.get_versions_dir().join(version.to_string()), 78 | shims_dir, 79 | ) 80 | .map_err(anyhow::Error::from) 81 | } 82 | 83 | #[cfg(unix)] 84 | fn set_shims(config: &Config, version: &Version) -> Result<()> { 85 | let shims_dir = config.get_shims_dir(); 86 | 87 | if read_link(&shims_dir).is_ok() { 88 | remove_file(&shims_dir)?; 89 | } 90 | 91 | symlink( 92 | config 93 | .get_versions_dir() 94 | .join(version.to_string()) 95 | .join("bin"), 96 | shims_dir, 97 | ) 98 | .map_err(anyhow::Error::from) 99 | } 100 | -------------------------------------------------------------------------------- /src/archives.rs: -------------------------------------------------------------------------------- 1 | #[cfg(unix)] 2 | use std::fs::remove_dir_all; 3 | #[cfg(windows)] 4 | use std::fs::File; 5 | #[cfg(windows)] 6 | use std::io::copy; 7 | #[cfg(unix)] 8 | use std::path::PathBuf; 9 | use std::{fs::create_dir_all, io::Cursor, path::Path}; 10 | 11 | use anyhow::Result; 12 | #[cfg(unix)] 13 | use flate2::read::GzDecoder; 14 | #[cfg(unix)] 15 | use tar::{Archive, Unpacked}; 16 | #[cfg(target_os = "windows")] 17 | use zip::ZipArchive; 18 | 19 | #[cfg(target_os = "windows")] 20 | pub fn extract_archive(bytes: Vec, path: &Path) -> Result<()> { 21 | let reader = Cursor::new(bytes); 22 | let mut archive = ZipArchive::new(reader).unwrap(); 23 | 24 | println!("Extracting..."); 25 | 26 | for i in 0..archive.len() { 27 | let mut item = archive.by_index(i).unwrap(); 28 | let file_path = item.mangled_name(); 29 | let file_path = file_path.to_string_lossy(); 30 | 31 | let mut new_path = path.to_owned(); 32 | if let Some(index) = file_path.find('\\') { 33 | new_path.push(&file_path[index + 1..]); 34 | } 35 | 36 | if item.is_dir() && !new_path.exists() { 37 | create_dir_all(&new_path) 38 | .unwrap_or_else(|_| panic!("Could not create new folder: {new_path:?}")); 39 | } 40 | 41 | if item.is_file() { 42 | let mut file = File::create(&*new_path)?; 43 | copy(&mut item, &mut file).unwrap_or_else(|_| panic!("Couldn't write to {new_path:?}")); 44 | } 45 | } 46 | 47 | println!( 48 | "Extracted to {}", 49 | // Have to remove \\?\ prefix 🤮 50 | path.to_str() 51 | .unwrap() 52 | .strip_prefix("\\\\?\\") 53 | .unwrap_or_else(|| path.to_str().unwrap()) 54 | ); 55 | 56 | Ok(()) 57 | } 58 | 59 | #[cfg(unix)] 60 | pub fn extract_archive(bytes: Vec, path: &Path) -> Result<()> { 61 | let reader = Cursor::new(bytes); 62 | let tar = GzDecoder::new(reader); 63 | let mut archive = Archive::new(tar); 64 | 65 | let version_dir_path = path.to_owned(); 66 | create_dir_all(&version_dir_path).expect("fuck"); 67 | 68 | println!("Extracting..."); 69 | 70 | let result = archive 71 | .entries() 72 | .map_err(anyhow::Error::from)? 73 | .filter_map(|e| e.ok()) 74 | .map(|mut entry| -> Result { 75 | let file_path = entry.path()?; 76 | let file_path = file_path.to_str().unwrap(); 77 | 78 | let new_path: PathBuf = if let Some(index) = file_path.find('/') { 79 | path.to_owned().join(&file_path[index + 1..]) 80 | } else { 81 | // This happens if it's the root index, the base folder 82 | path.to_owned() 83 | }; 84 | 85 | entry.set_preserve_permissions(false); 86 | entry.unpack(&new_path).map_err(anyhow::Error::from) 87 | }); 88 | 89 | let errors: Vec = result 90 | .into_iter() 91 | .filter(|result| result.is_err()) 92 | .map(|result| result.unwrap_err()) 93 | .collect(); 94 | 95 | if !errors.is_empty() { 96 | remove_dir_all(version_dir_path).expect("Couldn't clean up version."); 97 | 98 | return Err(anyhow::anyhow!( 99 | "Failed to extract all files:\n{:?}", 100 | errors 101 | .into_iter() 102 | .map(|err| err.to_string()) 103 | .collect::>() 104 | .join("/n") 105 | )); 106 | } 107 | 108 | println!("Extracted to {version_dir_path:?}"); 109 | 110 | Ok(()) 111 | } 112 | -------------------------------------------------------------------------------- /tests/uninstall_test.rs: -------------------------------------------------------------------------------- 1 | mod utils; 2 | 3 | mod uninstall { 4 | use anyhow::Result; 5 | use std::path::Path; 6 | 7 | use crate::utils; 8 | 9 | fn setup_versions(temp_dir: &Path, versions: Vec<&str>) -> Result<()> { 10 | for version_str in versions.iter().copied() { 11 | utils::install_mock_version(temp_dir, version_str)?; 12 | } 13 | 14 | utils::create_shim(temp_dir, versions.first().unwrap()) 15 | } 16 | 17 | #[test] 18 | fn can_uninstall_version_matching_range() -> Result<()> { 19 | let (temp_dir, mut cmd) = utils::setup_integration_test()?; 20 | 21 | let version_str = "12.18.3"; 22 | setup_versions(&temp_dir, vec!["14.5.0", version_str])?; 23 | 24 | let result = cmd.arg("uninstall").arg("12").assert(); 25 | 26 | utils::assert_outputs_contain(&result, "Uninstalled 12.18.3!", "")?; 27 | utils::assert_version_installed(&temp_dir, version_str, false)?; 28 | assert_eq!( 29 | utils::get_selected_version(&temp_dir), 30 | Some("14.5.0".to_string()) 31 | ); 32 | 33 | temp_dir.close().map_err(anyhow::Error::from) 34 | } 35 | 36 | #[test] 37 | fn can_uninstall_version_matching_exact_version() -> Result<()> { 38 | let (temp_dir, mut cmd) = utils::setup_integration_test()?; 39 | 40 | let version_str = "12.18.3"; 41 | setup_versions(&temp_dir, vec!["14.5.0", version_str])?; 42 | 43 | let result = cmd.arg("uninstall").arg(version_str).assert(); 44 | 45 | utils::assert_outputs_contain(&result, "Uninstalled 12.18.3!", "")?; 46 | utils::assert_version_installed(&temp_dir, version_str, false)?; 47 | assert_eq!( 48 | utils::get_selected_version(&temp_dir), 49 | Some("14.5.0".to_string()) 50 | ); 51 | 52 | temp_dir.close().map_err(anyhow::Error::from) 53 | } 54 | 55 | // #[test] 56 | // #[serial] 57 | // fn prompts_when_uninstalling_selected_version() -> Result<()> { 58 | // let version_str = "12.18.3"; 59 | // setup_versions(vec![version_str])?; 60 | // 61 | // let mut cmd = Command::cargo_bin("nvm-rust").unwrap(); 62 | // 63 | // let result = cmd.arg("uninstall").arg(version_str).assert(); 64 | // assert_outputs_contain( 65 | // &result, 66 | // "12.18.3 is currently selected.\nAre you sure you want to uninstall it? (y/N)", 67 | // "", 68 | // )?; 69 | // 70 | // cmd.write_stdin("y\n"); 71 | // 72 | // let result = cmd.assert(); 73 | // assert_outputs_contain( 74 | // &result, 75 | // "12.18.3 is currently selected.\nAre you sure you want to uninstall it? (y/N)\nUninstalled 12.18.3!", 76 | // "", 77 | // )?; 78 | // 79 | // assert_version_installed(version_str, false)?; 80 | // assert_version_selected(version_str, false)?; 81 | // 82 | // temp_dir.close().map_err(anyhow::Error::from) 83 | // } 84 | 85 | #[test] 86 | fn force_skips_prompt() -> Result<()> { 87 | let (temp_dir, mut cmd) = utils::setup_integration_test()?; 88 | 89 | let version_str = "12.18.3"; 90 | setup_versions(&temp_dir, vec![version_str])?; 91 | 92 | let result = cmd 93 | .arg("uninstall") 94 | .arg(version_str) 95 | .arg("--force") 96 | .assert(); 97 | 98 | utils::assert_outputs_contain( 99 | &result, 100 | "12.18.3 is currently selected.\nUninstalled 12.18.3!", 101 | "", 102 | )?; 103 | 104 | utils::assert_version_installed(&temp_dir, version_str, false)?; 105 | assert_eq!(utils::get_selected_version(&temp_dir), None); 106 | 107 | temp_dir.close().map_err(anyhow::Error::from) 108 | } 109 | 110 | #[test] 111 | fn exits_gracefully_if_no_version_is_found() -> Result<()> { 112 | let (temp_dir, mut cmd) = utils::setup_integration_test()?; 113 | 114 | setup_versions(&temp_dir, vec!["14.5.0"])?; 115 | 116 | let result = cmd.arg("uninstall").arg("12").assert(); 117 | 118 | utils::assert_outputs_contain(&result, "", "Error: >=12.0.0 <13.0.0-0 is not installed.")?; 119 | 120 | temp_dir.close().map_err(anyhow::Error::from) 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /tests/utils.rs: -------------------------------------------------------------------------------- 1 | #[cfg(unix)] 2 | use std::os::unix::fs::symlink; 3 | #[cfg(windows)] 4 | use std::os::windows::fs::symlink_dir; 5 | use std::{fs, path::Path}; 6 | 7 | use anyhow::Result; 8 | use assert_cmd::{assert::Assert, Command}; 9 | use assert_fs::{prelude::*, TempDir}; 10 | use predicates::prelude::*; 11 | 12 | #[cfg(unix)] 13 | pub fn required_files<'a>() -> [&'a str; 3] { 14 | ["node", "npm", "npx"] 15 | } 16 | 17 | #[cfg(windows)] 18 | pub fn required_files<'a>() -> [&'a str; 5] { 19 | ["node.exe", "npm", "npm.cmd", "npx", "npx.cmd"] 20 | } 21 | 22 | fn integration_dir() -> TempDir { 23 | let dir = TempDir::new().expect("Could not create temp dir"); 24 | 25 | println!("{:#?}", dir.path()); 26 | dir 27 | } 28 | 29 | pub fn setup_integration_test() -> Result<(TempDir, Command)> { 30 | let temp_dir = integration_dir(); 31 | 32 | let mut cmd = Command::cargo_bin("nvm").expect("Could not create Command"); 33 | cmd.args(["--install-dir", &temp_dir.to_string_lossy()]); 34 | 35 | Ok((temp_dir, cmd)) 36 | } 37 | 38 | pub fn install_mock_version(path: &Path, version_str: &str) -> Result<()> { 39 | let mut to_dir = path.join("versions"); 40 | 41 | to_dir = to_dir.join(version_str); 42 | // Unix shims are under `bin/xxx` 43 | #[cfg(unix)] 44 | { 45 | to_dir = to_dir.join("bin"); 46 | } 47 | 48 | fs::create_dir_all(&to_dir)?; 49 | 50 | for file_name in required_files() { 51 | let file_path = to_dir.join(file_name); 52 | 53 | fs::write(&file_path, version_str) 54 | .unwrap_or_else(|err| panic!("Failed to write to {:#?}: {}", &file_path, err)) 55 | } 56 | 57 | Ok(()) 58 | } 59 | 60 | #[allow(dead_code)] 61 | #[cfg(windows)] 62 | pub fn create_shim(temp_dir: &Path, version_str: &str) -> Result<()> { 63 | symlink_dir( 64 | temp_dir.join("versions").join(version_str), 65 | temp_dir.join("shims"), 66 | ) 67 | .map_err(anyhow::Error::from) 68 | } 69 | 70 | #[cfg(unix)] 71 | pub fn create_shim(temp_dir: &Path, version_str: &str) -> Result<()> { 72 | let mut shims_path = temp_dir.join("versions").join(version_str); 73 | 74 | // Unix shims are under `bin/xxx` 75 | #[cfg(unix)] 76 | { 77 | shims_path = shims_path.join("bin"); 78 | } 79 | 80 | symlink(&shims_path, temp_dir.join("shims")).map_err(anyhow::Error::from) 81 | } 82 | 83 | #[derive(PartialEq, Eq)] 84 | struct OutputResult(bool, bool); 85 | 86 | pub fn assert_outputs_contain(result: &Assert, stdout: &str, stderr: &str) -> Result<()> { 87 | let output = result.get_output().to_owned(); 88 | let output_stderr = String::from_utf8(output.stderr)?; 89 | let output_stdout = String::from_utf8(output.stdout)?; 90 | let result = OutputResult( 91 | output_stdout.trim().contains(stdout), 92 | output_stderr.trim().contains(stderr), 93 | ); 94 | 95 | if result != OutputResult(true, true) { 96 | panic!( 97 | r#"Got incorrect command output: 98 | stdout expected: 99 | "{}" 100 | stdout output: 101 | "{}" 102 | 103 | stderr expected: 104 | "{}" 105 | stderr output: 106 | "{}" 107 | "#, 108 | stdout, 109 | output_stdout.trim(), 110 | stderr, 111 | output_stderr.trim() 112 | ) 113 | } 114 | 115 | Ok(()) 116 | } 117 | 118 | #[allow(dead_code)] 119 | pub fn assert_version_installed( 120 | temp_dir: &TempDir, 121 | version_str: &str, 122 | expect_installed: bool, 123 | ) -> Result<()> { 124 | let versions_dir = temp_dir.child("versions"); 125 | 126 | for filename in required_files().iter() { 127 | let mut file_path = versions_dir.child(version_str); 128 | 129 | // Unix shims are under `bin/xxx` 130 | #[cfg(unix)] 131 | { 132 | file_path = file_path.child("bin"); 133 | } 134 | 135 | file_path = file_path.child(filename); 136 | 137 | if expect_installed { 138 | file_path.assert(predicates::path::exists()); 139 | } else { 140 | file_path.assert(predicates::path::exists().not()); 141 | } 142 | } 143 | 144 | Ok(()) 145 | } 146 | 147 | #[allow(dead_code)] 148 | pub fn get_selected_version(temp_dir: &TempDir) -> Option { 149 | let symlink_path = temp_dir.child("shims"); 150 | 151 | match fs::read_link(symlink_path) { 152 | Ok(shims_dir) => { 153 | let file_path = shims_dir.join(required_files()[0]); 154 | 155 | Some(fs::read_to_string(file_path).unwrap()) 156 | }, 157 | Err(_) => None, 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/subcommand/install.rs: -------------------------------------------------------------------------------- 1 | use std::{io::Read, path::Path, time::Duration}; 2 | 3 | use anyhow::{Context, Result}; 4 | use clap::Parser; 5 | use node_semver::Range; 6 | use ureq::{ 7 | self, 8 | tls::{RootCerts, TlsConfig, TlsProvider}, 9 | }; 10 | 11 | use crate::{ 12 | archives, constants, files, 13 | node_version::{ 14 | filter_version_req, parse_range, InstalledNodeVersion, NodeVersion, OnlineNodeVersion, 15 | }, 16 | subcommand::{switch::SwitchCommand, Action}, 17 | Config, 18 | }; 19 | 20 | #[derive(Parser, Clone, Debug)] 21 | #[command(about = "Install a new node version", alias = "i", alias = "add")] 22 | pub struct InstallCommand { 23 | /// A semver range. The latest version matching this range will be installed 24 | #[arg(value_parser = parse_range)] 25 | pub version: Option, 26 | /// Switch to the new version after installing it 27 | #[arg(long, short, default_value("false"))] 28 | pub switch: bool, 29 | /// Enable corepack after installing the new version 30 | #[arg(long, default_value("true"), hide(true), env("NVM_ENABLE_COREPACK"))] 31 | pub enable_corepack: bool, 32 | } 33 | 34 | impl Action for InstallCommand { 35 | fn run(config: &Config, options: &InstallCommand) -> Result<()> { 36 | let version_filter = options 37 | .version 38 | .clone() 39 | .or_else(|| files::get_version_file().map(|version_file| version_file.range())); 40 | 41 | if version_filter.is_none() { 42 | anyhow::bail!("You did not pass a version and we did not find any version files (package.json#engines, .nvmrc) in the current directory."); 43 | } 44 | let version_filter = version_filter.unwrap(); 45 | 46 | let online_versions = OnlineNodeVersion::fetch_all()?; 47 | let filtered_versions = filter_version_req(online_versions, &version_filter); 48 | 49 | let version_to_install = filtered_versions.first().context(format!( 50 | "Did not find a version matching `{}`!", 51 | &version_filter 52 | ))?; 53 | 54 | if !config.force && InstalledNodeVersion::is_installed(config, version_to_install.version()) 55 | { 56 | println!( 57 | "{} is already installed - skipping...", 58 | version_to_install.version() 59 | ); 60 | 61 | return Ok(()); 62 | } 63 | 64 | let install_path = version_to_install.install_path(config); 65 | download_and_extract_to(version_to_install, &install_path)?; 66 | 67 | if config.force 68 | || (options.switch 69 | && dialoguer::Confirm::new() 70 | .with_prompt(format!("Switch to {}?", version_to_install)) 71 | .default(true) 72 | .interact()?) 73 | { 74 | SwitchCommand::run( 75 | &config.with_force(), 76 | &SwitchCommand { 77 | version: Some(Range::parse(version_to_install.to_string())?), 78 | }, 79 | )?; 80 | } 81 | 82 | if options.enable_corepack { 83 | if let Err(e) = std::process::Command::new( 84 | install_path 85 | .join("bin") 86 | .join(format!("corepack{}", constants::EXEC_EXT)), 87 | ) 88 | .arg("enable") 89 | .output() 90 | { 91 | println!("⚠️ Failed to automatically enable corepack!\n{e}",) 92 | } 93 | } 94 | 95 | Ok(()) 96 | } 97 | } 98 | 99 | fn download_and_extract_to(version: &OnlineNodeVersion, path: &Path) -> Result<()> { 100 | let url = version.download_url(); 101 | let agent = ureq::config::Config::builder() 102 | .timeout_connect(Some(Duration::from_secs(30))) 103 | .timeout_recv_body(Some(Duration::from_secs(180))) 104 | .timeout_send_request(Some(Duration::from_secs(120))) 105 | .user_agent("beequeue/nvm-rust") 106 | .tls_config( 107 | TlsConfig::builder() 108 | .provider(TlsProvider::NativeTls) 109 | .root_certs(RootCerts::PlatformVerifier) 110 | .build(), 111 | ) 112 | .build() 113 | .new_agent(); 114 | 115 | println!("Downloading from {url}..."); 116 | let mut response = agent 117 | .get(&url) 118 | .call() 119 | .context(format!("Failed to download version: {}", version.version()))?; 120 | 121 | let length: usize = response 122 | .headers() 123 | .get("Content-Length") 124 | .expect("Did not receive Content-Length header in response") 125 | .to_str() 126 | .unwrap() 127 | .parse()?; 128 | 129 | let mut bytes: Vec = Vec::with_capacity(length); 130 | response.body_mut().as_reader().read_to_end(&mut bytes)?; 131 | 132 | archives::extract_archive(bytes, path) 133 | } 134 | -------------------------------------------------------------------------------- /src/subcommand/list.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::Parser; 3 | use itertools::Itertools; 4 | use node_semver::Range; 5 | 6 | use crate::{ 7 | node_version, 8 | node_version::{parse_range, InstalledNodeVersion, NodeVersion, OnlineNodeVersion}, 9 | subcommand::Action, 10 | Config, 11 | }; 12 | 13 | enum VersionStatus<'p> { 14 | Latest, 15 | NotInstalled, 16 | Outdated(&'p OnlineNodeVersion), 17 | } 18 | 19 | impl<'p> VersionStatus<'p> { 20 | fn from(versions: &[&T], latest: &'p OnlineNodeVersion) -> VersionStatus<'p> { 21 | if versions.is_empty() { 22 | VersionStatus::NotInstalled 23 | } else if versions 24 | .iter() 25 | .all(|version| version.version() < latest.version()) 26 | { 27 | VersionStatus::Outdated(latest) 28 | } else { 29 | VersionStatus::Latest 30 | } 31 | } 32 | 33 | fn to_emoji(&self) -> char { 34 | match self { 35 | VersionStatus::Latest => '✅', 36 | VersionStatus::NotInstalled => '〰', 37 | VersionStatus::Outdated(_) => '⏫', 38 | } 39 | } 40 | 41 | fn to_version_string(&self) -> String { 42 | match self { 43 | VersionStatus::Outdated(version) => format!("-> {}", version), 44 | _ => "".to_string(), 45 | } 46 | } 47 | } 48 | 49 | #[derive(Parser, Clone, Debug)] 50 | #[command(about = "List installed and released node versions", alias = "ls")] 51 | pub struct ListCommand { 52 | /// Only display installed versions 53 | #[arg(short, long, alias = "installed")] 54 | pub local: bool, 55 | /// Filter by semantic versions. 56 | /// 57 | /// `12`, `^10.9`, `>=8.10`, `>=8, <9` 58 | #[arg(short('F'), long, value_parser = parse_range)] 59 | pub filter: Option, 60 | } 61 | 62 | impl Action for ListCommand { 63 | fn run(config: &Config, options: &ListCommand) -> Result<()> { 64 | let mut installed_versions = InstalledNodeVersion::list(config); 65 | 66 | // Use filter option if it was passed 67 | if let Some(filter) = &options.filter { 68 | installed_versions = node_version::filter_version_req(installed_versions, filter); 69 | } 70 | 71 | if options.local { 72 | println!( 73 | "{}", 74 | installed_versions 75 | .iter() 76 | .map(|version| version.to_string()) 77 | .join("\n") 78 | ); 79 | 80 | return Ok(()); 81 | } 82 | 83 | // Get available versions, extract only the latest for each major version 84 | let mut latest_per_major = Vec::<&OnlineNodeVersion>::new(); 85 | let online_versions = OnlineNodeVersion::fetch_all()?; 86 | if !online_versions.is_empty() { 87 | latest_per_major = node_version::get_latest_of_each_major(&online_versions); 88 | latest_per_major.sort(); 89 | latest_per_major.reverse(); 90 | } 91 | 92 | let majors_and_installed_versions: Vec<(&OnlineNodeVersion, Vec<&InstalledNodeVersion>)> = 93 | latest_per_major 94 | .into_iter() 95 | .map(|latest| { 96 | ( 97 | latest, 98 | installed_versions 99 | .iter() 100 | .filter(|installed| installed.version().major == latest.version().major) 101 | .collect(), 102 | ) 103 | }) 104 | .collect(); 105 | 106 | // Show the latest X major versions by default 107 | // and show any older, installed versions as well 108 | let mut versions_to_show = Vec::<(&OnlineNodeVersion, &Vec<&InstalledNodeVersion>)>::new(); 109 | for (i, (latest, installed)) in majors_and_installed_versions.iter().enumerate() { 110 | if i < 5 || !installed.is_empty() { 111 | versions_to_show.push((latest, installed)); 112 | } 113 | } 114 | 115 | let output = versions_to_show 116 | .iter() 117 | .map(|(online_version, installed_versions)| { 118 | let version_status = VersionStatus::from(installed_versions, online_version); 119 | 120 | let version_to_show = if installed_versions.is_empty() { 121 | online_version.to_string() 122 | } else { 123 | installed_versions[0].to_string() 124 | }; 125 | 126 | format!( 127 | "{} {} {}", 128 | &version_status.to_emoji(), 129 | version_to_show, 130 | &version_status.to_version_string(), 131 | ) 132 | }) 133 | .join("\n"); 134 | 135 | println!("{output}"); 136 | Ok(()) 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ⚠️ This tool is deprecated, I recommend using **mise** instead as it has excellent UX and works on all platforms! 2 | 3 | I might still do some maintenance and release a version with updated dependencies but new features are unlikely to say the least. 4 | 5 | --- 6 | 7 | # nvm(-rust) 8 | 9 | Cross platform nvm that doesn't suck™ 10 | 11 | ## Installation 12 | 13 | ### Binaries 14 | 15 | 1. Download binary for your OS from the [Releases](https://github.com/BeeeQueue/nvm-rust/releases) 16 | 2. Rename the file to `nvm` and place it somewhere in your `$PATH` 17 | 3. Add `path/to/nvm-home/shims` to your PATH _(TODO: document this)_ 18 | 4. Enjoy? 19 | 20 | ### Cargo 21 | 22 | ```shell 23 | cargo install nvm-rust 24 | ``` 25 | 26 | #### Note for Windows 27 | 28 | _It does not allow creating the symlinks this program uses without either Admin access or Developer Mode._ 29 | 30 | _Either run the program as Administrator or [enable Developer Mode](https://docs.microsoft.com/en-us/windows/apps/get-started/enable-your-device-for-development#active-developer-mode)_ 31 | 32 | _[Read more about it here](https://blogs.windows.com/windowsdeveloper/2016/12/02/symlinks-windows-10)_ 33 | 34 | ## Feature Comparison 35 | 36 | | | **nvm-rust** | [nvm-windows](https://github.com/coreybutler/nvm-windows) | [nvm](https://github.com/nvm-sh/nvm) | 37 | |-----------------------------------------------------------------------:|:---------------:|:---------------------------------------------------------:|:------------------------------------:| 38 | | Platforms | Win, Mac, Linux | Windows | POSIX | 39 | | [Range matching](#range-matching) | ✅ | ❌ | ✅ | 40 | | [Version files](#version-files-packagejsonengines-nvmrc-tool-versions) | ✅ | ❌ | ✅ | 41 | | [Default global packages](#default-global-packages) | ❌ | ❌ | ✅ | 42 | | Node <4 | ✅* | ✅ | ✅ | 43 | | Disabling nvm temporarily | ❌ | ✅ | ✅ | 44 | | Caching | ❌ | ❌ | ✅ | 45 | | Aliases | ❌ | ❌ | ✅ | 46 | 47 | **not supported, might work? 48 | 49 | ### Range Matching 50 | 51 | Allowing you to not have to write out the full versions when running a command. 52 | 53 | For example: 54 | 55 | - `nvm install 12` will install the latest version matching `12`, instead of `12.0.0`. 56 | - `nvm install "12 <12.18"` will install the latest `12.17.x` version, instead of just giving you an error. 57 | - `nvm use 12` switch use the newest installed `12.x.x` version instead of `12.0.0` (and most likely giving you an error, who has that version installed?). 58 | 59 | ### Version files (`package.json#engines`, `.nvmrc`, `.tool-versions`) 60 | 61 | If a version is not specified for the `use` and `install` commands nvm-rust will look for and parse any files containing Node version specifications amd use that! 62 | 63 | nvm-rust handles files containing ranges, unlike [nvm](https://github.com/nvm-sh/nvm). 64 | 65 | e.g. 66 | 67 | ``` 68 | // package.json 69 | { 70 | ... 71 | "engines": { 72 | "node": "^14.17" 73 | } 74 | ... 75 | } 76 | 77 | # Installs 14.19.3 as of the time of writing 78 | $ nvm install 79 | ``` 80 | 81 | The program will use the following file priority: 82 | 83 | 1. `package.json#engines` 84 | 2. `.nvmrc` 85 | 3. `.node-version` 86 | 4. [`.tool-versions` from `asdf`](https://asdf-vm.com/guide/getting-started.html#local) 87 | 88 | ### Default global packages 89 | 90 | 91 | ## Development 92 | 93 | This project uses [Task](https://taskfile.dev/installation) to execute various development commands. 94 | 95 | e.g. to run a command via a debug build, run: 96 | 97 | ```shell 98 | task run -- install 12 99 | ``` 100 | 101 | To build a release artifact, run: 102 | 103 | ```shell 104 | task build:release 105 | ``` 106 | 107 | You can find all the commands in the [Taskfile](./Taskfile.yml). 108 | 109 | ## Publish new version 110 | 111 | 1. Up version number in `Cargo.toml` 112 | 2. Create tag on commit updating the version with said version (`vX.X.X`) 113 | 3. Push both 114 | 4. Wait for CI to create draft release for tag 115 | 5. Edit draft release notes 116 | 6. Publish 117 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #[cfg(windows)] 2 | use std::os::windows; 3 | use std::{ 4 | fs, 5 | path::{Path, PathBuf}, 6 | }; 7 | 8 | #[cfg(windows)] 9 | use anyhow::bail; 10 | use anyhow::Result; 11 | use clap::{Parser, ValueHint}; 12 | 13 | use crate::subcommand::{ 14 | install::InstallCommand, is_installed::IsInstalledCommand, list::ListCommand, 15 | parse_version::ParseVersionCommand, switch::SwitchCommand, uninstall::UninstallCommand, Action, 16 | }; 17 | 18 | mod archives; 19 | mod constants; 20 | mod files; 21 | mod node_version; 22 | mod subcommand; 23 | 24 | #[derive(Parser, Clone, Debug)] 25 | enum Subcommands { 26 | List(ListCommand), 27 | IsInstalled(IsInstalledCommand), 28 | Install(InstallCommand), 29 | Uninstall(UninstallCommand), 30 | Use(SwitchCommand), 31 | ParseVersion(ParseVersionCommand), 32 | } 33 | 34 | #[derive(Parser, Debug)] 35 | #[command( 36 | name = "nvm(-rust)", 37 | author, 38 | about, 39 | version, 40 | about = "Node Version Manager (but better, and in Rust)" 41 | )] 42 | pub struct Config { 43 | /// Installation directory 44 | #[arg( 45 | id("install-dir"), 46 | global(true), 47 | long, 48 | value_hint(ValueHint::DirPath), 49 | env("NVM_DIR") 50 | )] 51 | dir: Option, 52 | /// bin directory 53 | #[arg( 54 | global(true), 55 | long, 56 | value_hint(ValueHint::DirPath), 57 | env("NVM_SHIMS_DIR") 58 | )] 59 | shims_dir: Option, 60 | /// Accept any prompts needed for the command to complete 61 | #[arg(global(true), short, long)] 62 | force: bool, 63 | 64 | #[command(subcommand)] 65 | command: Subcommands, 66 | } 67 | 68 | impl Config { 69 | pub fn get_dir(&self) -> PathBuf { 70 | self.dir 71 | .as_ref() 72 | .map_or_else(Config::default_dir, |r| r.clone()) 73 | } 74 | 75 | pub fn get_shims_dir(&self) -> PathBuf { 76 | self.shims_dir 77 | .as_ref() 78 | .map_or_else(|| self.get_dir().join("shims"), |r| r.clone()) 79 | } 80 | 81 | /// Path to directory containing node versions 82 | fn get_versions_dir(&self) -> PathBuf { 83 | self.get_dir().join("versions") 84 | } 85 | 86 | fn with_force(&self) -> Self { 87 | Self { 88 | force: true, 89 | dir: Some(self.get_dir()), 90 | shims_dir: Some(self.get_shims_dir()), 91 | command: self.command.clone(), 92 | } 93 | } 94 | 95 | #[cfg(windows)] 96 | fn default_dir() -> PathBuf { 97 | dirs::data_local_dir().unwrap().join("nvm-rust") 98 | } 99 | 100 | #[cfg(unix)] 101 | fn default_dir() -> PathBuf { 102 | dirs::home_dir().unwrap().join("nvm-rust") 103 | } 104 | } 105 | 106 | fn ensure_dir_exists(path: &Path) { 107 | if !path.exists() { 108 | fs::create_dir_all(path).unwrap_or_else(|err| panic!("Could not create {path:?} - {err}")); 109 | 110 | println!("Created nvm dir at {path:?}"); 111 | } 112 | 113 | if !path.is_dir() { 114 | panic!("{path:?} is not a directory! Please rename it.") 115 | } 116 | } 117 | 118 | #[cfg(windows)] 119 | const SYMLINK_ERROR: &str = "You do not seem to have permissions to create symlinks. 120 | This is most likely due to Windows requiring Admin access for it unless you enable Developer Mode. 121 | 122 | Either run the program as Administrator or enable Developer Mode: 123 | https://docs.microsoft.com/en-us/windows/apps/get-started/enable-your-device-for-development#active-developer-mode 124 | 125 | Read more: 126 | https://blogs.windows.com/windowsdeveloper/2016/12/02/symlinks-windows-10"; 127 | 128 | #[cfg(windows)] 129 | fn ensure_symlinks_work(config: &Config) -> Result<()> { 130 | let target_path = &config.get_dir().join("test"); 131 | 132 | if windows::fs::symlink_dir(config.get_shims_dir(), target_path).is_err() { 133 | bail!("{SYMLINK_ERROR}"); 134 | } 135 | 136 | fs::remove_dir(target_path).expect("Could not remove test symlink..."); 137 | 138 | Ok(()) 139 | } 140 | 141 | fn main() -> Result<()> { 142 | let config: Config = Config::parse(); 143 | #[cfg(windows)] 144 | let is_initial_run = !config.get_dir().exists(); 145 | 146 | ensure_dir_exists(&config.get_dir()); 147 | ensure_dir_exists(&config.get_versions_dir()); 148 | 149 | #[cfg(windows)] 150 | if is_initial_run { 151 | let result = ensure_symlinks_work(&config); 152 | result?; 153 | } 154 | 155 | match config.command { 156 | Subcommands::List(ref options) => ListCommand::run(&config, options), 157 | Subcommands::IsInstalled(ref options) => IsInstalledCommand::run(&config, options), 158 | Subcommands::Install(ref options) => InstallCommand::run(&config, options), 159 | Subcommands::Uninstall(ref options) => UninstallCommand::run(&config, options), 160 | Subcommands::Use(ref options) => SwitchCommand::run(&config, options), 161 | Subcommands::ParseVersion(ref options) => ParseVersionCommand::run(&config, options), 162 | #[allow(unreachable_patterns)] 163 | _ => Ok(()), 164 | } 165 | } 166 | 167 | #[test] 168 | fn verify_cli() { 169 | use clap::CommandFactory; 170 | 171 | Config::command().debug_assert() 172 | } 173 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | https://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /src/node_version.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | cmp::Ordering, 3 | collections::HashMap, 4 | fs::{read_link, remove_dir_all}, 5 | path::PathBuf, 6 | }; 7 | 8 | use anyhow::{Context, Result}; 9 | use node_semver::{Range, Version}; 10 | use serde::Deserialize; 11 | 12 | use crate::{ 13 | constants, 14 | constants::{ARCH, EXT, PLATFORM, X64}, 15 | Config, 16 | }; 17 | 18 | pub trait NodeVersion { 19 | fn version(&self) -> &Version; 20 | } 21 | 22 | impl PartialEq for dyn NodeVersion { 23 | fn eq(&self, other: &Self) -> bool { 24 | self.version().eq(other.version()) 25 | } 26 | } 27 | 28 | impl Eq for dyn NodeVersion {} 29 | 30 | impl Ord for dyn NodeVersion { 31 | fn cmp(&self, other: &Self) -> Ordering { 32 | self.version().cmp(other.version()) 33 | } 34 | } 35 | 36 | impl PartialOrd for dyn NodeVersion { 37 | fn partial_cmp(&self, other: &Self) -> Option { 38 | Some(self.cmp(other)) 39 | } 40 | } 41 | 42 | pub fn parse_range(value: &str) -> Result { 43 | Range::parse(value).context(value.to_string()) 44 | } 45 | 46 | pub fn filter_version_req(versions: Vec, version_range: &Range) -> Vec { 47 | versions 48 | .into_iter() 49 | .filter(|version| version_range.satisfies(version.version())) 50 | .collect() 51 | } 52 | 53 | pub fn get_latest_of_each_major(versions: &[V]) -> Vec<&V> { 54 | let mut map: HashMap = HashMap::new(); 55 | 56 | for version in versions.iter() { 57 | let entry = map.get_mut(&version.version().major); 58 | if entry.is_some() && version.version().lt(entry.unwrap().version()) { 59 | continue; 60 | } 61 | 62 | map.insert(version.version().major, version); 63 | } 64 | 65 | map.values().cloned().collect() 66 | } 67 | 68 | /// Handles `vX.X.X` prefixes 69 | fn parse_version_str(version_str: &str) -> Result { 70 | // Required since the versions are prefixed with 'v' which `semver` can't handle 71 | let clean_version = if version_str.starts_with('v') { 72 | version_str.get(1..).unwrap() 73 | } else { 74 | version_str 75 | }; 76 | 77 | Version::parse(clean_version).context(version_str.to_owned()) 78 | } 79 | 80 | #[derive(Clone, Deserialize, Debug, Eq, PartialEq, Ord, PartialOrd)] 81 | #[serde(rename_all(deserialize = "snake_case"))] 82 | pub struct OnlineNodeVersion { 83 | #[serde()] 84 | version: Version, 85 | #[serde(alias = "date")] 86 | pub release_date: String, 87 | 88 | files: Vec, 89 | } 90 | 91 | impl OnlineNodeVersion { 92 | pub fn fetch_all() -> Result> { 93 | let mut response = ureq::get("https://nodejs.org/dist/index.json").call()?; 94 | 95 | response 96 | .body_mut() 97 | .read_json() 98 | .context("Failed to parse versions list from nodejs.org") 99 | } 100 | 101 | pub fn install_path(&self, config: &Config) -> PathBuf { 102 | config.get_versions_dir().join(self.to_string()) 103 | } 104 | 105 | pub fn download_url(&self) -> String { 106 | let file_name: String; 107 | 108 | #[cfg(target_os = "macos")] 109 | { 110 | let has_arm = self.has_arm(); 111 | 112 | file_name = self.file(!has_arm); 113 | } 114 | 115 | #[cfg(not(target_os = "macos"))] 116 | { 117 | file_name = self.file(false); 118 | } 119 | 120 | format!("https://nodejs.org/dist/v{}/{}", self.version, file_name) 121 | } 122 | 123 | fn file(&self, force_x64: bool) -> String { 124 | format!( 125 | "node-v{VERSION}-{PLATFORM}-{ARCH}{EXT}", 126 | VERSION = self.version(), 127 | ARCH = if force_x64 { X64 } else { ARCH }, 128 | ) 129 | } 130 | 131 | #[cfg(target_os = "macos")] 132 | fn has_arm(&self) -> bool { 133 | for file in self.files.iter() { 134 | // We check for both ARM _and_ OSX since we don't want to fall back to x64 on other platforms. 135 | if file.contains("osx") && file.contains("arm64") { 136 | return true; 137 | } 138 | } 139 | 140 | false 141 | } 142 | } 143 | 144 | impl std::fmt::Display for OnlineNodeVersion { 145 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 146 | write!(f, "{}", self.version) 147 | } 148 | } 149 | 150 | impl NodeVersion for OnlineNodeVersion { 151 | fn version(&self) -> &Version { 152 | &self.version 153 | } 154 | } 155 | 156 | #[derive(Clone, Deserialize, Debug, Eq, PartialEq)] 157 | pub struct InstalledNodeVersion { 158 | version: Version, 159 | path: PathBuf, 160 | } 161 | 162 | impl InstalledNodeVersion { 163 | // Properties 164 | 165 | pub fn get_dir_path(&self, config: &Config) -> PathBuf { 166 | config.get_versions_dir().join(self.version().to_string()) 167 | } 168 | 169 | pub fn is_installed(config: &Config, version: &Version) -> bool { 170 | Self::list(config).iter().any(|v| v.version().eq(version)) 171 | } 172 | 173 | pub fn is_selected(&self, config: &Config) -> bool { 174 | let path = config.get_shims_dir(); 175 | let real_path = read_link(path); 176 | 177 | if real_path.is_err() { 178 | return false; 179 | } 180 | 181 | let real_path = real_path.unwrap(); 182 | 183 | real_path 184 | .to_string_lossy() 185 | .contains(&self.version().to_string()) 186 | } 187 | 188 | // Functions 189 | 190 | pub fn uninstall(self, config: &Config) -> Result<()> { 191 | remove_dir_all(self.get_dir_path(config))?; 192 | 193 | println!("Uninstalled {}!", self.version()); 194 | Ok(()) 195 | } 196 | 197 | /// Checks that all the required files are present in the installation dir 198 | #[allow(dead_code)] 199 | pub fn validate(&self, config: &Config) -> Result<()> { 200 | let version_dir = 201 | read_link(config.get_shims_dir()).expect("Could not read installation dir"); 202 | 203 | let mut required_files = vec![version_dir; 2]; 204 | required_files[0].set_file_name(format!("node{}", constants::EXEC_EXT)); 205 | required_files[1].set_file_name(format!("npm{}", constants::EXEC_EXT)); 206 | 207 | if let Some(missing_file) = required_files.iter().find(|file| !file.exists()) { 208 | anyhow::bail!( 209 | "{:?} is not preset for {:?}", 210 | missing_file, 211 | self.version.to_string() 212 | ); 213 | } 214 | 215 | Ok(()) 216 | } 217 | 218 | // Static functions 219 | 220 | pub fn deselect(config: &Config) -> Result<()> { 221 | remove_dir_all(config.get_shims_dir()).map_err(anyhow::Error::from) 222 | } 223 | 224 | pub fn list(config: &Config) -> Vec { 225 | let mut version_dirs: Vec = vec![]; 226 | 227 | for entry in config 228 | .get_versions_dir() 229 | .read_dir() 230 | .expect("Failed to read nvm dir") 231 | { 232 | if entry.is_err() { 233 | println!("Could not read {entry:?}"); 234 | continue; 235 | } 236 | 237 | let entry = entry.unwrap(); 238 | let result = parse_version_str(entry.file_name().to_string_lossy().as_ref()); 239 | 240 | if let Ok(version) = result { 241 | version_dirs.push(version); 242 | } 243 | } 244 | 245 | version_dirs.sort(); 246 | version_dirs.reverse(); 247 | 248 | version_dirs 249 | .iter() 250 | .map(|version| { 251 | let version_str = version.to_string(); 252 | 253 | InstalledNodeVersion { 254 | version: parse_version_str(&version_str) 255 | .expect("Got bad version into InstalledNodeVersion."), 256 | path: config.get_versions_dir().join(&version_str), 257 | } 258 | }) 259 | .collect() 260 | } 261 | 262 | /// Returns the latest, installed version matching the version range 263 | pub fn find_matching(config: &Config, range: &Range) -> Option { 264 | Self::list(config) 265 | .iter() 266 | .find(|inv| range.satisfies(inv.version())) 267 | .map(|inv| inv.to_owned()) 268 | } 269 | } 270 | 271 | impl std::fmt::Display for InstalledNodeVersion { 272 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 273 | write!(f, "{}", self.version) 274 | } 275 | } 276 | 277 | impl NodeVersion for InstalledNodeVersion { 278 | fn version(&self) -> &Version { 279 | &self.version 280 | } 281 | } 282 | 283 | #[cfg(test)] 284 | mod tests { 285 | mod online_version { 286 | use anyhow::Result; 287 | use node_semver::Version; 288 | 289 | use speculoos::prelude::*; 290 | 291 | use crate::node_version::OnlineNodeVersion; 292 | 293 | #[test] 294 | fn can_parse_version_data() -> Result<()> { 295 | let expected = OnlineNodeVersion { 296 | version: Version { 297 | major: 14, 298 | minor: 18, 299 | patch: 0, 300 | build: vec![], 301 | pre_release: vec![], 302 | }, 303 | release_date: "2021-09-28".to_string(), 304 | files: vec![ 305 | "aix-ppc64".to_string(), 306 | "headers".to_string(), 307 | "linux-arm64".to_string(), 308 | "linux-armv7l".to_string(), 309 | "linux-ppc64le".to_string(), 310 | "linux-s390x".to_string(), 311 | "linux-x64".to_string(), 312 | "osx-x64-pkg".to_string(), 313 | "osx-x64-tar".to_string(), 314 | "src".to_string(), 315 | "win-x64-7z".to_string(), 316 | "win-x64-exe".to_string(), 317 | "win-x64-msi".to_string(), 318 | "win-x64-zip".to_string(), 319 | "win-x86-7z".to_string(), 320 | "win-x86-exe".to_string(), 321 | "win-x86-msi".to_string(), 322 | "win-x86-zip".to_string(), 323 | ], 324 | }; 325 | 326 | let json_str = r#" 327 | { 328 | "version": "v14.18.0", 329 | "date": "2021-09-28", 330 | "files": [ 331 | "aix-ppc64", 332 | "headers", 333 | "linux-arm64", 334 | "linux-armv7l", 335 | "linux-ppc64le", 336 | "linux-s390x", 337 | "linux-x64", 338 | "osx-x64-pkg", 339 | "osx-x64-tar", 340 | "src", 341 | "win-x64-7z", 342 | "win-x64-exe", 343 | "win-x64-msi", 344 | "win-x64-zip", 345 | "win-x86-7z", 346 | "win-x86-exe", 347 | "win-x86-msi", 348 | "win-x86-zip" 349 | ], 350 | "npm": "6.14.15", 351 | "v8": "8.4.371.23", 352 | "uv": "1.42.0", 353 | "zlib": "1.2.11", 354 | "openssl": "1.1.1l", 355 | "modules": "83", 356 | "lts": "Fermium", 357 | "security": false 358 | } 359 | "# 360 | .trim(); 361 | 362 | let result: OnlineNodeVersion = serde_json::from_str(json_str) 363 | .expect("Failed to parse version data from nodejs.org"); 364 | 365 | assert_that!(expected).is_equal_to(result); 366 | 367 | Ok(()) 368 | } 369 | } 370 | } 371 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "adler2" 7 | version = "2.0.1" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" 10 | 11 | [[package]] 12 | name = "aes" 13 | version = "0.8.4" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" 16 | dependencies = [ 17 | "cfg-if", 18 | "cipher", 19 | "cpufeatures", 20 | ] 21 | 22 | [[package]] 23 | name = "aho-corasick" 24 | version = "1.1.3" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 27 | dependencies = [ 28 | "memchr", 29 | ] 30 | 31 | [[package]] 32 | name = "anstream" 33 | version = "0.6.21" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" 36 | dependencies = [ 37 | "anstyle", 38 | "anstyle-parse", 39 | "anstyle-query", 40 | "anstyle-wincon", 41 | "colorchoice", 42 | "is_terminal_polyfill", 43 | "utf8parse", 44 | ] 45 | 46 | [[package]] 47 | name = "anstyle" 48 | version = "1.0.13" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" 51 | 52 | [[package]] 53 | name = "anstyle-parse" 54 | version = "0.2.7" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" 57 | dependencies = [ 58 | "utf8parse", 59 | ] 60 | 61 | [[package]] 62 | name = "anstyle-query" 63 | version = "1.1.4" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" 66 | dependencies = [ 67 | "windows-sys 0.60.2", 68 | ] 69 | 70 | [[package]] 71 | name = "anstyle-wincon" 72 | version = "3.0.10" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" 75 | dependencies = [ 76 | "anstyle", 77 | "once_cell_polyfill", 78 | "windows-sys 0.60.2", 79 | ] 80 | 81 | [[package]] 82 | name = "anyhow" 83 | version = "1.0.100" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" 86 | 87 | [[package]] 88 | name = "arbitrary" 89 | version = "1.4.2" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" 92 | dependencies = [ 93 | "derive_arbitrary", 94 | ] 95 | 96 | [[package]] 97 | name = "assert_cmd" 98 | version = "2.0.17" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "2bd389a4b2970a01282ee455294913c0a43724daedcd1a24c3eb0ec1c1320b66" 101 | dependencies = [ 102 | "anstyle", 103 | "bstr", 104 | "doc-comment", 105 | "libc", 106 | "predicates", 107 | "predicates-core", 108 | "predicates-tree", 109 | "wait-timeout", 110 | ] 111 | 112 | [[package]] 113 | name = "assert_fs" 114 | version = "1.1.3" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "a652f6cb1f516886fcfee5e7a5c078b9ade62cfcb889524efe5a64d682dd27a9" 117 | dependencies = [ 118 | "anstyle", 119 | "doc-comment", 120 | "globwalk", 121 | "predicates", 122 | "predicates-core", 123 | "predicates-tree", 124 | "tempfile", 125 | ] 126 | 127 | [[package]] 128 | name = "autocfg" 129 | version = "1.5.0" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" 132 | 133 | [[package]] 134 | name = "base64" 135 | version = "0.22.1" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 138 | 139 | [[package]] 140 | name = "base64ct" 141 | version = "1.8.0" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" 144 | 145 | [[package]] 146 | name = "bitflags" 147 | version = "2.9.4" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" 150 | 151 | [[package]] 152 | name = "block-buffer" 153 | version = "0.10.4" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 156 | dependencies = [ 157 | "generic-array", 158 | ] 159 | 160 | [[package]] 161 | name = "bstr" 162 | version = "1.12.0" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" 165 | dependencies = [ 166 | "memchr", 167 | "regex-automata", 168 | "serde", 169 | ] 170 | 171 | [[package]] 172 | name = "bumpalo" 173 | version = "3.19.0" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" 176 | 177 | [[package]] 178 | name = "bytecount" 179 | version = "0.6.9" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e" 182 | 183 | [[package]] 184 | name = "bytes" 185 | version = "1.10.1" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" 188 | 189 | [[package]] 190 | name = "bzip2" 191 | version = "0.6.0" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "bea8dcd42434048e4f7a304411d9273a411f647446c1234a65ce0554923f4cff" 194 | dependencies = [ 195 | "libbz2-rs-sys", 196 | ] 197 | 198 | [[package]] 199 | name = "cc" 200 | version = "1.2.41" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7" 203 | dependencies = [ 204 | "find-msvc-tools", 205 | "jobserver", 206 | "libc", 207 | "shlex", 208 | ] 209 | 210 | [[package]] 211 | name = "cfg-if" 212 | version = "1.0.3" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" 215 | 216 | [[package]] 217 | name = "cipher" 218 | version = "0.4.4" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" 221 | dependencies = [ 222 | "crypto-common", 223 | "inout", 224 | ] 225 | 226 | [[package]] 227 | name = "clap" 228 | version = "4.5.48" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae" 231 | dependencies = [ 232 | "clap_builder", 233 | "clap_derive", 234 | ] 235 | 236 | [[package]] 237 | name = "clap_builder" 238 | version = "4.5.48" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9" 241 | dependencies = [ 242 | "anstream", 243 | "anstyle", 244 | "clap_lex", 245 | "strsim", 246 | ] 247 | 248 | [[package]] 249 | name = "clap_derive" 250 | version = "4.5.47" 251 | source = "registry+https://github.com/rust-lang/crates.io-index" 252 | checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c" 253 | dependencies = [ 254 | "heck", 255 | "proc-macro2", 256 | "quote", 257 | "syn", 258 | ] 259 | 260 | [[package]] 261 | name = "clap_lex" 262 | version = "0.7.5" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" 265 | 266 | [[package]] 267 | name = "colorchoice" 268 | version = "1.0.4" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" 271 | 272 | [[package]] 273 | name = "console" 274 | version = "0.16.1" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "b430743a6eb14e9764d4260d4c0d8123087d504eeb9c48f2b2a5e810dd369df4" 277 | dependencies = [ 278 | "encode_unicode", 279 | "libc", 280 | "once_cell", 281 | "unicode-width 0.2.2", 282 | "windows-sys 0.61.2", 283 | ] 284 | 285 | [[package]] 286 | name = "constant_time_eq" 287 | version = "0.3.1" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" 290 | 291 | [[package]] 292 | name = "cookie" 293 | version = "0.18.1" 294 | source = "registry+https://github.com/rust-lang/crates.io-index" 295 | checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" 296 | dependencies = [ 297 | "percent-encoding", 298 | "time", 299 | "version_check", 300 | ] 301 | 302 | [[package]] 303 | name = "cookie_store" 304 | version = "0.22.0" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "3fc4bff745c9b4c7fb1e97b25d13153da2bc7796260141df62378998d070207f" 307 | dependencies = [ 308 | "cookie", 309 | "document-features", 310 | "idna", 311 | "indexmap", 312 | "log", 313 | "serde", 314 | "serde_derive", 315 | "serde_json", 316 | "time", 317 | "url", 318 | ] 319 | 320 | [[package]] 321 | name = "core-foundation" 322 | version = "0.9.4" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 325 | dependencies = [ 326 | "core-foundation-sys", 327 | "libc", 328 | ] 329 | 330 | [[package]] 331 | name = "core-foundation-sys" 332 | version = "0.8.7" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 335 | 336 | [[package]] 337 | name = "cpufeatures" 338 | version = "0.2.17" 339 | source = "registry+https://github.com/rust-lang/crates.io-index" 340 | checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" 341 | dependencies = [ 342 | "libc", 343 | ] 344 | 345 | [[package]] 346 | name = "crc" 347 | version = "3.3.0" 348 | source = "registry+https://github.com/rust-lang/crates.io-index" 349 | checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" 350 | dependencies = [ 351 | "crc-catalog", 352 | ] 353 | 354 | [[package]] 355 | name = "crc-catalog" 356 | version = "2.4.0" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" 359 | 360 | [[package]] 361 | name = "crc32fast" 362 | version = "1.5.0" 363 | source = "registry+https://github.com/rust-lang/crates.io-index" 364 | checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" 365 | dependencies = [ 366 | "cfg-if", 367 | ] 368 | 369 | [[package]] 370 | name = "crossbeam-deque" 371 | version = "0.8.6" 372 | source = "registry+https://github.com/rust-lang/crates.io-index" 373 | checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" 374 | dependencies = [ 375 | "crossbeam-epoch", 376 | "crossbeam-utils", 377 | ] 378 | 379 | [[package]] 380 | name = "crossbeam-epoch" 381 | version = "0.9.18" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" 384 | dependencies = [ 385 | "crossbeam-utils", 386 | ] 387 | 388 | [[package]] 389 | name = "crossbeam-utils" 390 | version = "0.8.21" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" 393 | 394 | [[package]] 395 | name = "crypto-common" 396 | version = "0.1.6" 397 | source = "registry+https://github.com/rust-lang/crates.io-index" 398 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 399 | dependencies = [ 400 | "generic-array", 401 | "typenum", 402 | ] 403 | 404 | [[package]] 405 | name = "deflate64" 406 | version = "0.1.10" 407 | source = "registry+https://github.com/rust-lang/crates.io-index" 408 | checksum = "26bf8fc351c5ed29b5c2f0cbbac1b209b74f60ecd62e675a998df72c49af5204" 409 | 410 | [[package]] 411 | name = "der" 412 | version = "0.7.10" 413 | source = "registry+https://github.com/rust-lang/crates.io-index" 414 | checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" 415 | dependencies = [ 416 | "pem-rfc7468", 417 | "zeroize", 418 | ] 419 | 420 | [[package]] 421 | name = "deranged" 422 | version = "0.5.4" 423 | source = "registry+https://github.com/rust-lang/crates.io-index" 424 | checksum = "a41953f86f8a05768a6cda24def994fd2f424b04ec5c719cf89989779f199071" 425 | dependencies = [ 426 | "powerfmt", 427 | ] 428 | 429 | [[package]] 430 | name = "derive_arbitrary" 431 | version = "1.4.2" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" 434 | dependencies = [ 435 | "proc-macro2", 436 | "quote", 437 | "syn", 438 | ] 439 | 440 | [[package]] 441 | name = "dialoguer" 442 | version = "0.12.0" 443 | source = "registry+https://github.com/rust-lang/crates.io-index" 444 | checksum = "25f104b501bf2364e78d0d3974cbc774f738f5865306ed128e1e0d7499c0ad96" 445 | dependencies = [ 446 | "console", 447 | "shell-words", 448 | "tempfile", 449 | "zeroize", 450 | ] 451 | 452 | [[package]] 453 | name = "difflib" 454 | version = "0.4.0" 455 | source = "registry+https://github.com/rust-lang/crates.io-index" 456 | checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" 457 | 458 | [[package]] 459 | name = "digest" 460 | version = "0.10.7" 461 | source = "registry+https://github.com/rust-lang/crates.io-index" 462 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 463 | dependencies = [ 464 | "block-buffer", 465 | "crypto-common", 466 | "subtle", 467 | ] 468 | 469 | [[package]] 470 | name = "dirs" 471 | version = "6.0.0" 472 | source = "registry+https://github.com/rust-lang/crates.io-index" 473 | checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" 474 | dependencies = [ 475 | "dirs-sys", 476 | ] 477 | 478 | [[package]] 479 | name = "dirs-sys" 480 | version = "0.5.0" 481 | source = "registry+https://github.com/rust-lang/crates.io-index" 482 | checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" 483 | dependencies = [ 484 | "libc", 485 | "option-ext", 486 | "redox_users", 487 | "windows-sys 0.61.2", 488 | ] 489 | 490 | [[package]] 491 | name = "displaydoc" 492 | version = "0.2.5" 493 | source = "registry+https://github.com/rust-lang/crates.io-index" 494 | checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" 495 | dependencies = [ 496 | "proc-macro2", 497 | "quote", 498 | "syn", 499 | ] 500 | 501 | [[package]] 502 | name = "doc-comment" 503 | version = "0.3.3" 504 | source = "registry+https://github.com/rust-lang/crates.io-index" 505 | checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" 506 | 507 | [[package]] 508 | name = "document-features" 509 | version = "0.2.11" 510 | source = "registry+https://github.com/rust-lang/crates.io-index" 511 | checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" 512 | dependencies = [ 513 | "litrs", 514 | ] 515 | 516 | [[package]] 517 | name = "either" 518 | version = "1.15.0" 519 | source = "registry+https://github.com/rust-lang/crates.io-index" 520 | checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" 521 | 522 | [[package]] 523 | name = "encode_unicode" 524 | version = "1.0.0" 525 | source = "registry+https://github.com/rust-lang/crates.io-index" 526 | checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" 527 | 528 | [[package]] 529 | name = "equivalent" 530 | version = "1.0.2" 531 | source = "registry+https://github.com/rust-lang/crates.io-index" 532 | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 533 | 534 | [[package]] 535 | name = "errno" 536 | version = "0.3.14" 537 | source = "registry+https://github.com/rust-lang/crates.io-index" 538 | checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" 539 | dependencies = [ 540 | "libc", 541 | "windows-sys 0.61.2", 542 | ] 543 | 544 | [[package]] 545 | name = "fastrand" 546 | version = "2.3.0" 547 | source = "registry+https://github.com/rust-lang/crates.io-index" 548 | checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" 549 | 550 | [[package]] 551 | name = "filetime" 552 | version = "0.2.26" 553 | source = "registry+https://github.com/rust-lang/crates.io-index" 554 | checksum = "bc0505cd1b6fa6580283f6bdf70a73fcf4aba1184038c90902b92b3dd0df63ed" 555 | dependencies = [ 556 | "cfg-if", 557 | "libc", 558 | "libredox", 559 | "windows-sys 0.60.2", 560 | ] 561 | 562 | [[package]] 563 | name = "find-msvc-tools" 564 | version = "0.1.4" 565 | source = "registry+https://github.com/rust-lang/crates.io-index" 566 | checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" 567 | 568 | [[package]] 569 | name = "flate2" 570 | version = "1.1.4" 571 | source = "registry+https://github.com/rust-lang/crates.io-index" 572 | checksum = "dc5a4e564e38c699f2880d3fda590bedc2e69f3f84cd48b457bd892ce61d0aa9" 573 | dependencies = [ 574 | "crc32fast", 575 | "libz-rs-sys", 576 | "miniz_oxide", 577 | ] 578 | 579 | [[package]] 580 | name = "float-cmp" 581 | version = "0.10.0" 582 | source = "registry+https://github.com/rust-lang/crates.io-index" 583 | checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8" 584 | dependencies = [ 585 | "num-traits", 586 | ] 587 | 588 | [[package]] 589 | name = "fnv" 590 | version = "1.0.7" 591 | source = "registry+https://github.com/rust-lang/crates.io-index" 592 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 593 | 594 | [[package]] 595 | name = "foreign-types" 596 | version = "0.3.2" 597 | source = "registry+https://github.com/rust-lang/crates.io-index" 598 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 599 | dependencies = [ 600 | "foreign-types-shared", 601 | ] 602 | 603 | [[package]] 604 | name = "foreign-types-shared" 605 | version = "0.1.1" 606 | source = "registry+https://github.com/rust-lang/crates.io-index" 607 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 608 | 609 | [[package]] 610 | name = "form_urlencoded" 611 | version = "1.2.2" 612 | source = "registry+https://github.com/rust-lang/crates.io-index" 613 | checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" 614 | dependencies = [ 615 | "percent-encoding", 616 | ] 617 | 618 | [[package]] 619 | name = "generic-array" 620 | version = "0.14.7" 621 | source = "registry+https://github.com/rust-lang/crates.io-index" 622 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 623 | dependencies = [ 624 | "typenum", 625 | "version_check", 626 | ] 627 | 628 | [[package]] 629 | name = "getrandom" 630 | version = "0.2.16" 631 | source = "registry+https://github.com/rust-lang/crates.io-index" 632 | checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" 633 | dependencies = [ 634 | "cfg-if", 635 | "libc", 636 | "wasi 0.11.1+wasi-snapshot-preview1", 637 | ] 638 | 639 | [[package]] 640 | name = "getrandom" 641 | version = "0.3.3" 642 | source = "registry+https://github.com/rust-lang/crates.io-index" 643 | checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" 644 | dependencies = [ 645 | "cfg-if", 646 | "libc", 647 | "r-efi", 648 | "wasi 0.14.7+wasi-0.2.4", 649 | ] 650 | 651 | [[package]] 652 | name = "globset" 653 | version = "0.4.16" 654 | source = "registry+https://github.com/rust-lang/crates.io-index" 655 | checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5" 656 | dependencies = [ 657 | "aho-corasick", 658 | "bstr", 659 | "log", 660 | "regex-automata", 661 | "regex-syntax", 662 | ] 663 | 664 | [[package]] 665 | name = "globwalk" 666 | version = "0.9.1" 667 | source = "registry+https://github.com/rust-lang/crates.io-index" 668 | checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757" 669 | dependencies = [ 670 | "bitflags", 671 | "ignore", 672 | "walkdir", 673 | ] 674 | 675 | [[package]] 676 | name = "hashbrown" 677 | version = "0.16.0" 678 | source = "registry+https://github.com/rust-lang/crates.io-index" 679 | checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" 680 | 681 | [[package]] 682 | name = "heck" 683 | version = "0.5.0" 684 | source = "registry+https://github.com/rust-lang/crates.io-index" 685 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 686 | 687 | [[package]] 688 | name = "hmac" 689 | version = "0.12.1" 690 | source = "registry+https://github.com/rust-lang/crates.io-index" 691 | checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" 692 | dependencies = [ 693 | "digest", 694 | ] 695 | 696 | [[package]] 697 | name = "http" 698 | version = "1.3.1" 699 | source = "registry+https://github.com/rust-lang/crates.io-index" 700 | checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" 701 | dependencies = [ 702 | "bytes", 703 | "fnv", 704 | "itoa", 705 | ] 706 | 707 | [[package]] 708 | name = "httparse" 709 | version = "1.10.1" 710 | source = "registry+https://github.com/rust-lang/crates.io-index" 711 | checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" 712 | 713 | [[package]] 714 | name = "icu_collections" 715 | version = "2.0.0" 716 | source = "registry+https://github.com/rust-lang/crates.io-index" 717 | checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" 718 | dependencies = [ 719 | "displaydoc", 720 | "potential_utf", 721 | "yoke", 722 | "zerofrom", 723 | "zerovec", 724 | ] 725 | 726 | [[package]] 727 | name = "icu_locale_core" 728 | version = "2.0.0" 729 | source = "registry+https://github.com/rust-lang/crates.io-index" 730 | checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" 731 | dependencies = [ 732 | "displaydoc", 733 | "litemap", 734 | "tinystr", 735 | "writeable", 736 | "zerovec", 737 | ] 738 | 739 | [[package]] 740 | name = "icu_normalizer" 741 | version = "2.0.0" 742 | source = "registry+https://github.com/rust-lang/crates.io-index" 743 | checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" 744 | dependencies = [ 745 | "displaydoc", 746 | "icu_collections", 747 | "icu_normalizer_data", 748 | "icu_properties", 749 | "icu_provider", 750 | "smallvec", 751 | "zerovec", 752 | ] 753 | 754 | [[package]] 755 | name = "icu_normalizer_data" 756 | version = "2.0.0" 757 | source = "registry+https://github.com/rust-lang/crates.io-index" 758 | checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" 759 | 760 | [[package]] 761 | name = "icu_properties" 762 | version = "2.0.1" 763 | source = "registry+https://github.com/rust-lang/crates.io-index" 764 | checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" 765 | dependencies = [ 766 | "displaydoc", 767 | "icu_collections", 768 | "icu_locale_core", 769 | "icu_properties_data", 770 | "icu_provider", 771 | "potential_utf", 772 | "zerotrie", 773 | "zerovec", 774 | ] 775 | 776 | [[package]] 777 | name = "icu_properties_data" 778 | version = "2.0.1" 779 | source = "registry+https://github.com/rust-lang/crates.io-index" 780 | checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" 781 | 782 | [[package]] 783 | name = "icu_provider" 784 | version = "2.0.0" 785 | source = "registry+https://github.com/rust-lang/crates.io-index" 786 | checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" 787 | dependencies = [ 788 | "displaydoc", 789 | "icu_locale_core", 790 | "stable_deref_trait", 791 | "tinystr", 792 | "writeable", 793 | "yoke", 794 | "zerofrom", 795 | "zerotrie", 796 | "zerovec", 797 | ] 798 | 799 | [[package]] 800 | name = "idna" 801 | version = "1.1.0" 802 | source = "registry+https://github.com/rust-lang/crates.io-index" 803 | checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" 804 | dependencies = [ 805 | "idna_adapter", 806 | "smallvec", 807 | "utf8_iter", 808 | ] 809 | 810 | [[package]] 811 | name = "idna_adapter" 812 | version = "1.2.1" 813 | source = "registry+https://github.com/rust-lang/crates.io-index" 814 | checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" 815 | dependencies = [ 816 | "icu_normalizer", 817 | "icu_properties", 818 | ] 819 | 820 | [[package]] 821 | name = "ignore" 822 | version = "0.4.23" 823 | source = "registry+https://github.com/rust-lang/crates.io-index" 824 | checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b" 825 | dependencies = [ 826 | "crossbeam-deque", 827 | "globset", 828 | "log", 829 | "memchr", 830 | "regex-automata", 831 | "same-file", 832 | "walkdir", 833 | "winapi-util", 834 | ] 835 | 836 | [[package]] 837 | name = "indexmap" 838 | version = "2.11.4" 839 | source = "registry+https://github.com/rust-lang/crates.io-index" 840 | checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" 841 | dependencies = [ 842 | "equivalent", 843 | "hashbrown", 844 | ] 845 | 846 | [[package]] 847 | name = "inout" 848 | version = "0.1.4" 849 | source = "registry+https://github.com/rust-lang/crates.io-index" 850 | checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" 851 | dependencies = [ 852 | "generic-array", 853 | ] 854 | 855 | [[package]] 856 | name = "is_terminal_polyfill" 857 | version = "1.70.1" 858 | source = "registry+https://github.com/rust-lang/crates.io-index" 859 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 860 | 861 | [[package]] 862 | name = "itertools" 863 | version = "0.14.0" 864 | source = "registry+https://github.com/rust-lang/crates.io-index" 865 | checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" 866 | dependencies = [ 867 | "either", 868 | ] 869 | 870 | [[package]] 871 | name = "itoa" 872 | version = "1.0.15" 873 | source = "registry+https://github.com/rust-lang/crates.io-index" 874 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 875 | 876 | [[package]] 877 | name = "jobserver" 878 | version = "0.1.34" 879 | source = "registry+https://github.com/rust-lang/crates.io-index" 880 | checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" 881 | dependencies = [ 882 | "getrandom 0.3.3", 883 | "libc", 884 | ] 885 | 886 | [[package]] 887 | name = "libbz2-rs-sys" 888 | version = "0.2.2" 889 | source = "registry+https://github.com/rust-lang/crates.io-index" 890 | checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7" 891 | 892 | [[package]] 893 | name = "libc" 894 | version = "0.2.177" 895 | source = "registry+https://github.com/rust-lang/crates.io-index" 896 | checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" 897 | 898 | [[package]] 899 | name = "libredox" 900 | version = "0.1.10" 901 | source = "registry+https://github.com/rust-lang/crates.io-index" 902 | checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" 903 | dependencies = [ 904 | "bitflags", 905 | "libc", 906 | "redox_syscall", 907 | ] 908 | 909 | [[package]] 910 | name = "libz-rs-sys" 911 | version = "0.5.2" 912 | source = "registry+https://github.com/rust-lang/crates.io-index" 913 | checksum = "840db8cf39d9ec4dd794376f38acc40d0fc65eec2a8f484f7fd375b84602becd" 914 | dependencies = [ 915 | "zlib-rs", 916 | ] 917 | 918 | [[package]] 919 | name = "linux-raw-sys" 920 | version = "0.11.0" 921 | source = "registry+https://github.com/rust-lang/crates.io-index" 922 | checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" 923 | 924 | [[package]] 925 | name = "litemap" 926 | version = "0.8.0" 927 | source = "registry+https://github.com/rust-lang/crates.io-index" 928 | checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" 929 | 930 | [[package]] 931 | name = "litrs" 932 | version = "0.4.2" 933 | source = "registry+https://github.com/rust-lang/crates.io-index" 934 | checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed" 935 | 936 | [[package]] 937 | name = "log" 938 | version = "0.4.28" 939 | source = "registry+https://github.com/rust-lang/crates.io-index" 940 | checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" 941 | 942 | [[package]] 943 | name = "lzma-rust2" 944 | version = "0.13.0" 945 | source = "registry+https://github.com/rust-lang/crates.io-index" 946 | checksum = "c60a23ffb90d527e23192f1246b14746e2f7f071cb84476dd879071696c18a4a" 947 | dependencies = [ 948 | "crc", 949 | "sha2", 950 | ] 951 | 952 | [[package]] 953 | name = "memchr" 954 | version = "2.7.6" 955 | source = "registry+https://github.com/rust-lang/crates.io-index" 956 | checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" 957 | 958 | [[package]] 959 | name = "miette" 960 | version = "7.6.0" 961 | source = "registry+https://github.com/rust-lang/crates.io-index" 962 | checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7" 963 | dependencies = [ 964 | "cfg-if", 965 | "miette-derive", 966 | "unicode-width 0.1.14", 967 | ] 968 | 969 | [[package]] 970 | name = "miette-derive" 971 | version = "7.6.0" 972 | source = "registry+https://github.com/rust-lang/crates.io-index" 973 | checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b" 974 | dependencies = [ 975 | "proc-macro2", 976 | "quote", 977 | "syn", 978 | ] 979 | 980 | [[package]] 981 | name = "minimal-lexical" 982 | version = "0.2.1" 983 | source = "registry+https://github.com/rust-lang/crates.io-index" 984 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 985 | 986 | [[package]] 987 | name = "miniz_oxide" 988 | version = "0.8.9" 989 | source = "registry+https://github.com/rust-lang/crates.io-index" 990 | checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" 991 | dependencies = [ 992 | "adler2", 993 | "simd-adler32", 994 | ] 995 | 996 | [[package]] 997 | name = "native-tls" 998 | version = "0.2.14" 999 | source = "registry+https://github.com/rust-lang/crates.io-index" 1000 | checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" 1001 | dependencies = [ 1002 | "libc", 1003 | "log", 1004 | "openssl", 1005 | "openssl-probe", 1006 | "openssl-sys", 1007 | "schannel", 1008 | "security-framework", 1009 | "security-framework-sys", 1010 | "tempfile", 1011 | ] 1012 | 1013 | [[package]] 1014 | name = "node-semver" 1015 | version = "2.2.0" 1016 | source = "registry+https://github.com/rust-lang/crates.io-index" 1017 | checksum = "3b1a233ea5dc37d2cfba31cfc87a5a56cc2a9c04e3672c15d179ca118dae40a7" 1018 | dependencies = [ 1019 | "bytecount", 1020 | "miette", 1021 | "nom", 1022 | "serde", 1023 | "thiserror 1.0.69", 1024 | ] 1025 | 1026 | [[package]] 1027 | name = "nom" 1028 | version = "7.1.3" 1029 | source = "registry+https://github.com/rust-lang/crates.io-index" 1030 | checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 1031 | dependencies = [ 1032 | "memchr", 1033 | "minimal-lexical", 1034 | ] 1035 | 1036 | [[package]] 1037 | name = "normalize-line-endings" 1038 | version = "0.3.0" 1039 | source = "registry+https://github.com/rust-lang/crates.io-index" 1040 | checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" 1041 | 1042 | [[package]] 1043 | name = "num" 1044 | version = "0.4.3" 1045 | source = "registry+https://github.com/rust-lang/crates.io-index" 1046 | checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" 1047 | dependencies = [ 1048 | "num-bigint", 1049 | "num-complex", 1050 | "num-integer", 1051 | "num-iter", 1052 | "num-rational", 1053 | "num-traits", 1054 | ] 1055 | 1056 | [[package]] 1057 | name = "num-bigint" 1058 | version = "0.4.6" 1059 | source = "registry+https://github.com/rust-lang/crates.io-index" 1060 | checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" 1061 | dependencies = [ 1062 | "num-integer", 1063 | "num-traits", 1064 | ] 1065 | 1066 | [[package]] 1067 | name = "num-complex" 1068 | version = "0.4.6" 1069 | source = "registry+https://github.com/rust-lang/crates.io-index" 1070 | checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" 1071 | dependencies = [ 1072 | "num-traits", 1073 | ] 1074 | 1075 | [[package]] 1076 | name = "num-conv" 1077 | version = "0.1.0" 1078 | source = "registry+https://github.com/rust-lang/crates.io-index" 1079 | checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 1080 | 1081 | [[package]] 1082 | name = "num-integer" 1083 | version = "0.1.46" 1084 | source = "registry+https://github.com/rust-lang/crates.io-index" 1085 | checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" 1086 | dependencies = [ 1087 | "num-traits", 1088 | ] 1089 | 1090 | [[package]] 1091 | name = "num-iter" 1092 | version = "0.1.45" 1093 | source = "registry+https://github.com/rust-lang/crates.io-index" 1094 | checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" 1095 | dependencies = [ 1096 | "autocfg", 1097 | "num-integer", 1098 | "num-traits", 1099 | ] 1100 | 1101 | [[package]] 1102 | name = "num-rational" 1103 | version = "0.4.2" 1104 | source = "registry+https://github.com/rust-lang/crates.io-index" 1105 | checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" 1106 | dependencies = [ 1107 | "num-bigint", 1108 | "num-integer", 1109 | "num-traits", 1110 | ] 1111 | 1112 | [[package]] 1113 | name = "num-traits" 1114 | version = "0.2.19" 1115 | source = "registry+https://github.com/rust-lang/crates.io-index" 1116 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 1117 | dependencies = [ 1118 | "autocfg", 1119 | ] 1120 | 1121 | [[package]] 1122 | name = "nvm-rust" 1123 | version = "0.4.3" 1124 | dependencies = [ 1125 | "anyhow", 1126 | "assert_cmd", 1127 | "assert_fs", 1128 | "clap", 1129 | "dialoguer", 1130 | "dirs", 1131 | "flate2", 1132 | "itertools", 1133 | "node-semver", 1134 | "predicates", 1135 | "serde", 1136 | "serde_json", 1137 | "speculoos", 1138 | "tar", 1139 | "ureq", 1140 | "zip", 1141 | ] 1142 | 1143 | [[package]] 1144 | name = "once_cell" 1145 | version = "1.21.3" 1146 | source = "registry+https://github.com/rust-lang/crates.io-index" 1147 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 1148 | 1149 | [[package]] 1150 | name = "once_cell_polyfill" 1151 | version = "1.70.1" 1152 | source = "registry+https://github.com/rust-lang/crates.io-index" 1153 | checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" 1154 | 1155 | [[package]] 1156 | name = "openssl" 1157 | version = "0.10.73" 1158 | source = "registry+https://github.com/rust-lang/crates.io-index" 1159 | checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" 1160 | dependencies = [ 1161 | "bitflags", 1162 | "cfg-if", 1163 | "foreign-types", 1164 | "libc", 1165 | "once_cell", 1166 | "openssl-macros", 1167 | "openssl-sys", 1168 | ] 1169 | 1170 | [[package]] 1171 | name = "openssl-macros" 1172 | version = "0.1.1" 1173 | source = "registry+https://github.com/rust-lang/crates.io-index" 1174 | checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" 1175 | dependencies = [ 1176 | "proc-macro2", 1177 | "quote", 1178 | "syn", 1179 | ] 1180 | 1181 | [[package]] 1182 | name = "openssl-probe" 1183 | version = "0.1.6" 1184 | source = "registry+https://github.com/rust-lang/crates.io-index" 1185 | checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" 1186 | 1187 | [[package]] 1188 | name = "openssl-sys" 1189 | version = "0.9.109" 1190 | source = "registry+https://github.com/rust-lang/crates.io-index" 1191 | checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" 1192 | dependencies = [ 1193 | "cc", 1194 | "libc", 1195 | "pkg-config", 1196 | "vcpkg", 1197 | ] 1198 | 1199 | [[package]] 1200 | name = "option-ext" 1201 | version = "0.2.0" 1202 | source = "registry+https://github.com/rust-lang/crates.io-index" 1203 | checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" 1204 | 1205 | [[package]] 1206 | name = "pbkdf2" 1207 | version = "0.12.2" 1208 | source = "registry+https://github.com/rust-lang/crates.io-index" 1209 | checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" 1210 | dependencies = [ 1211 | "digest", 1212 | "hmac", 1213 | ] 1214 | 1215 | [[package]] 1216 | name = "pem-rfc7468" 1217 | version = "0.7.0" 1218 | source = "registry+https://github.com/rust-lang/crates.io-index" 1219 | checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" 1220 | dependencies = [ 1221 | "base64ct", 1222 | ] 1223 | 1224 | [[package]] 1225 | name = "percent-encoding" 1226 | version = "2.3.2" 1227 | source = "registry+https://github.com/rust-lang/crates.io-index" 1228 | checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" 1229 | 1230 | [[package]] 1231 | name = "pkg-config" 1232 | version = "0.3.32" 1233 | source = "registry+https://github.com/rust-lang/crates.io-index" 1234 | checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" 1235 | 1236 | [[package]] 1237 | name = "potential_utf" 1238 | version = "0.1.3" 1239 | source = "registry+https://github.com/rust-lang/crates.io-index" 1240 | checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" 1241 | dependencies = [ 1242 | "zerovec", 1243 | ] 1244 | 1245 | [[package]] 1246 | name = "powerfmt" 1247 | version = "0.2.0" 1248 | source = "registry+https://github.com/rust-lang/crates.io-index" 1249 | checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 1250 | 1251 | [[package]] 1252 | name = "ppmd-rust" 1253 | version = "1.2.1" 1254 | source = "registry+https://github.com/rust-lang/crates.io-index" 1255 | checksum = "c834641d8ad1b348c9ee86dec3b9840d805acd5f24daa5f90c788951a52ff59b" 1256 | 1257 | [[package]] 1258 | name = "predicates" 1259 | version = "3.1.3" 1260 | source = "registry+https://github.com/rust-lang/crates.io-index" 1261 | checksum = "a5d19ee57562043d37e82899fade9a22ebab7be9cef5026b07fda9cdd4293573" 1262 | dependencies = [ 1263 | "anstyle", 1264 | "difflib", 1265 | "float-cmp", 1266 | "normalize-line-endings", 1267 | "predicates-core", 1268 | "regex", 1269 | ] 1270 | 1271 | [[package]] 1272 | name = "predicates-core" 1273 | version = "1.0.9" 1274 | source = "registry+https://github.com/rust-lang/crates.io-index" 1275 | checksum = "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa" 1276 | 1277 | [[package]] 1278 | name = "predicates-tree" 1279 | version = "1.0.12" 1280 | source = "registry+https://github.com/rust-lang/crates.io-index" 1281 | checksum = "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c" 1282 | dependencies = [ 1283 | "predicates-core", 1284 | "termtree", 1285 | ] 1286 | 1287 | [[package]] 1288 | name = "proc-macro2" 1289 | version = "1.0.101" 1290 | source = "registry+https://github.com/rust-lang/crates.io-index" 1291 | checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" 1292 | dependencies = [ 1293 | "unicode-ident", 1294 | ] 1295 | 1296 | [[package]] 1297 | name = "quote" 1298 | version = "1.0.41" 1299 | source = "registry+https://github.com/rust-lang/crates.io-index" 1300 | checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" 1301 | dependencies = [ 1302 | "proc-macro2", 1303 | ] 1304 | 1305 | [[package]] 1306 | name = "r-efi" 1307 | version = "5.3.0" 1308 | source = "registry+https://github.com/rust-lang/crates.io-index" 1309 | checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" 1310 | 1311 | [[package]] 1312 | name = "redox_syscall" 1313 | version = "0.5.18" 1314 | source = "registry+https://github.com/rust-lang/crates.io-index" 1315 | checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" 1316 | dependencies = [ 1317 | "bitflags", 1318 | ] 1319 | 1320 | [[package]] 1321 | name = "redox_users" 1322 | version = "0.5.2" 1323 | source = "registry+https://github.com/rust-lang/crates.io-index" 1324 | checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" 1325 | dependencies = [ 1326 | "getrandom 0.2.16", 1327 | "libredox", 1328 | "thiserror 2.0.17", 1329 | ] 1330 | 1331 | [[package]] 1332 | name = "regex" 1333 | version = "1.12.1" 1334 | source = "registry+https://github.com/rust-lang/crates.io-index" 1335 | checksum = "4a52d8d02cacdb176ef4678de6c052efb4b3da14b78e4db683a4252762be5433" 1336 | dependencies = [ 1337 | "aho-corasick", 1338 | "memchr", 1339 | "regex-automata", 1340 | "regex-syntax", 1341 | ] 1342 | 1343 | [[package]] 1344 | name = "regex-automata" 1345 | version = "0.4.12" 1346 | source = "registry+https://github.com/rust-lang/crates.io-index" 1347 | checksum = "722166aa0d7438abbaa4d5cc2c649dac844e8c56d82fb3d33e9c34b5cd268fc6" 1348 | dependencies = [ 1349 | "aho-corasick", 1350 | "memchr", 1351 | "regex-syntax", 1352 | ] 1353 | 1354 | [[package]] 1355 | name = "regex-syntax" 1356 | version = "0.8.7" 1357 | source = "registry+https://github.com/rust-lang/crates.io-index" 1358 | checksum = "c3160422bbd54dd5ecfdca71e5fd59b7b8fe2b1697ab2baf64f6d05dcc66d298" 1359 | 1360 | [[package]] 1361 | name = "ring" 1362 | version = "0.17.14" 1363 | source = "registry+https://github.com/rust-lang/crates.io-index" 1364 | checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" 1365 | dependencies = [ 1366 | "cc", 1367 | "cfg-if", 1368 | "getrandom 0.2.16", 1369 | "libc", 1370 | "untrusted", 1371 | "windows-sys 0.52.0", 1372 | ] 1373 | 1374 | [[package]] 1375 | name = "rustix" 1376 | version = "1.1.2" 1377 | source = "registry+https://github.com/rust-lang/crates.io-index" 1378 | checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" 1379 | dependencies = [ 1380 | "bitflags", 1381 | "errno", 1382 | "libc", 1383 | "linux-raw-sys", 1384 | "windows-sys 0.61.2", 1385 | ] 1386 | 1387 | [[package]] 1388 | name = "rustls" 1389 | version = "0.23.32" 1390 | source = "registry+https://github.com/rust-lang/crates.io-index" 1391 | checksum = "cd3c25631629d034ce7cd9940adc9d45762d46de2b0f57193c4443b92c6d4d40" 1392 | dependencies = [ 1393 | "log", 1394 | "once_cell", 1395 | "ring", 1396 | "rustls-pki-types", 1397 | "rustls-webpki", 1398 | "subtle", 1399 | "zeroize", 1400 | ] 1401 | 1402 | [[package]] 1403 | name = "rustls-pemfile" 1404 | version = "2.2.0" 1405 | source = "registry+https://github.com/rust-lang/crates.io-index" 1406 | checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" 1407 | dependencies = [ 1408 | "rustls-pki-types", 1409 | ] 1410 | 1411 | [[package]] 1412 | name = "rustls-pki-types" 1413 | version = "1.12.0" 1414 | source = "registry+https://github.com/rust-lang/crates.io-index" 1415 | checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" 1416 | dependencies = [ 1417 | "zeroize", 1418 | ] 1419 | 1420 | [[package]] 1421 | name = "rustls-webpki" 1422 | version = "0.103.7" 1423 | source = "registry+https://github.com/rust-lang/crates.io-index" 1424 | checksum = "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf" 1425 | dependencies = [ 1426 | "ring", 1427 | "rustls-pki-types", 1428 | "untrusted", 1429 | ] 1430 | 1431 | [[package]] 1432 | name = "ryu" 1433 | version = "1.0.20" 1434 | source = "registry+https://github.com/rust-lang/crates.io-index" 1435 | checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 1436 | 1437 | [[package]] 1438 | name = "same-file" 1439 | version = "1.0.6" 1440 | source = "registry+https://github.com/rust-lang/crates.io-index" 1441 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 1442 | dependencies = [ 1443 | "winapi-util", 1444 | ] 1445 | 1446 | [[package]] 1447 | name = "schannel" 1448 | version = "0.1.28" 1449 | source = "registry+https://github.com/rust-lang/crates.io-index" 1450 | checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" 1451 | dependencies = [ 1452 | "windows-sys 0.61.2", 1453 | ] 1454 | 1455 | [[package]] 1456 | name = "security-framework" 1457 | version = "2.11.1" 1458 | source = "registry+https://github.com/rust-lang/crates.io-index" 1459 | checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" 1460 | dependencies = [ 1461 | "bitflags", 1462 | "core-foundation", 1463 | "core-foundation-sys", 1464 | "libc", 1465 | "security-framework-sys", 1466 | ] 1467 | 1468 | [[package]] 1469 | name = "security-framework-sys" 1470 | version = "2.15.0" 1471 | source = "registry+https://github.com/rust-lang/crates.io-index" 1472 | checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" 1473 | dependencies = [ 1474 | "core-foundation-sys", 1475 | "libc", 1476 | ] 1477 | 1478 | [[package]] 1479 | name = "serde" 1480 | version = "1.0.228" 1481 | source = "registry+https://github.com/rust-lang/crates.io-index" 1482 | checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" 1483 | dependencies = [ 1484 | "serde_core", 1485 | "serde_derive", 1486 | ] 1487 | 1488 | [[package]] 1489 | name = "serde_core" 1490 | version = "1.0.228" 1491 | source = "registry+https://github.com/rust-lang/crates.io-index" 1492 | checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" 1493 | dependencies = [ 1494 | "serde_derive", 1495 | ] 1496 | 1497 | [[package]] 1498 | name = "serde_derive" 1499 | version = "1.0.228" 1500 | source = "registry+https://github.com/rust-lang/crates.io-index" 1501 | checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" 1502 | dependencies = [ 1503 | "proc-macro2", 1504 | "quote", 1505 | "syn", 1506 | ] 1507 | 1508 | [[package]] 1509 | name = "serde_json" 1510 | version = "1.0.145" 1511 | source = "registry+https://github.com/rust-lang/crates.io-index" 1512 | checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" 1513 | dependencies = [ 1514 | "itoa", 1515 | "memchr", 1516 | "ryu", 1517 | "serde", 1518 | "serde_core", 1519 | ] 1520 | 1521 | [[package]] 1522 | name = "sha1" 1523 | version = "0.10.6" 1524 | source = "registry+https://github.com/rust-lang/crates.io-index" 1525 | checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" 1526 | dependencies = [ 1527 | "cfg-if", 1528 | "cpufeatures", 1529 | "digest", 1530 | ] 1531 | 1532 | [[package]] 1533 | name = "sha2" 1534 | version = "0.10.9" 1535 | source = "registry+https://github.com/rust-lang/crates.io-index" 1536 | checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" 1537 | dependencies = [ 1538 | "cfg-if", 1539 | "cpufeatures", 1540 | "digest", 1541 | ] 1542 | 1543 | [[package]] 1544 | name = "shell-words" 1545 | version = "1.1.0" 1546 | source = "registry+https://github.com/rust-lang/crates.io-index" 1547 | checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" 1548 | 1549 | [[package]] 1550 | name = "shlex" 1551 | version = "1.3.0" 1552 | source = "registry+https://github.com/rust-lang/crates.io-index" 1553 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 1554 | 1555 | [[package]] 1556 | name = "simd-adler32" 1557 | version = "0.3.7" 1558 | source = "registry+https://github.com/rust-lang/crates.io-index" 1559 | checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" 1560 | 1561 | [[package]] 1562 | name = "smallvec" 1563 | version = "1.15.1" 1564 | source = "registry+https://github.com/rust-lang/crates.io-index" 1565 | checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" 1566 | 1567 | [[package]] 1568 | name = "speculoos" 1569 | version = "0.13.0" 1570 | source = "registry+https://github.com/rust-lang/crates.io-index" 1571 | checksum = "00c84ba5fa63b0de837c0d3cef5373ac1c3c6342053b7f446a210a1dde79a034" 1572 | dependencies = [ 1573 | "num", 1574 | "serde_json", 1575 | ] 1576 | 1577 | [[package]] 1578 | name = "stable_deref_trait" 1579 | version = "1.2.1" 1580 | source = "registry+https://github.com/rust-lang/crates.io-index" 1581 | checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" 1582 | 1583 | [[package]] 1584 | name = "strsim" 1585 | version = "0.11.1" 1586 | source = "registry+https://github.com/rust-lang/crates.io-index" 1587 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 1588 | 1589 | [[package]] 1590 | name = "subtle" 1591 | version = "2.6.1" 1592 | source = "registry+https://github.com/rust-lang/crates.io-index" 1593 | checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 1594 | 1595 | [[package]] 1596 | name = "syn" 1597 | version = "2.0.106" 1598 | source = "registry+https://github.com/rust-lang/crates.io-index" 1599 | checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" 1600 | dependencies = [ 1601 | "proc-macro2", 1602 | "quote", 1603 | "unicode-ident", 1604 | ] 1605 | 1606 | [[package]] 1607 | name = "synstructure" 1608 | version = "0.13.2" 1609 | source = "registry+https://github.com/rust-lang/crates.io-index" 1610 | checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" 1611 | dependencies = [ 1612 | "proc-macro2", 1613 | "quote", 1614 | "syn", 1615 | ] 1616 | 1617 | [[package]] 1618 | name = "tar" 1619 | version = "0.4.44" 1620 | source = "registry+https://github.com/rust-lang/crates.io-index" 1621 | checksum = "1d863878d212c87a19c1a610eb53bb01fe12951c0501cf5a0d65f724914a667a" 1622 | dependencies = [ 1623 | "filetime", 1624 | "libc", 1625 | "xattr", 1626 | ] 1627 | 1628 | [[package]] 1629 | name = "tempfile" 1630 | version = "3.23.0" 1631 | source = "registry+https://github.com/rust-lang/crates.io-index" 1632 | checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" 1633 | dependencies = [ 1634 | "fastrand", 1635 | "getrandom 0.3.3", 1636 | "once_cell", 1637 | "rustix", 1638 | "windows-sys 0.61.2", 1639 | ] 1640 | 1641 | [[package]] 1642 | name = "termtree" 1643 | version = "0.5.1" 1644 | source = "registry+https://github.com/rust-lang/crates.io-index" 1645 | checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" 1646 | 1647 | [[package]] 1648 | name = "thiserror" 1649 | version = "1.0.69" 1650 | source = "registry+https://github.com/rust-lang/crates.io-index" 1651 | checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 1652 | dependencies = [ 1653 | "thiserror-impl 1.0.69", 1654 | ] 1655 | 1656 | [[package]] 1657 | name = "thiserror" 1658 | version = "2.0.17" 1659 | source = "registry+https://github.com/rust-lang/crates.io-index" 1660 | checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" 1661 | dependencies = [ 1662 | "thiserror-impl 2.0.17", 1663 | ] 1664 | 1665 | [[package]] 1666 | name = "thiserror-impl" 1667 | version = "1.0.69" 1668 | source = "registry+https://github.com/rust-lang/crates.io-index" 1669 | checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 1670 | dependencies = [ 1671 | "proc-macro2", 1672 | "quote", 1673 | "syn", 1674 | ] 1675 | 1676 | [[package]] 1677 | name = "thiserror-impl" 1678 | version = "2.0.17" 1679 | source = "registry+https://github.com/rust-lang/crates.io-index" 1680 | checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" 1681 | dependencies = [ 1682 | "proc-macro2", 1683 | "quote", 1684 | "syn", 1685 | ] 1686 | 1687 | [[package]] 1688 | name = "time" 1689 | version = "0.3.44" 1690 | source = "registry+https://github.com/rust-lang/crates.io-index" 1691 | checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" 1692 | dependencies = [ 1693 | "deranged", 1694 | "itoa", 1695 | "num-conv", 1696 | "powerfmt", 1697 | "serde", 1698 | "time-core", 1699 | "time-macros", 1700 | ] 1701 | 1702 | [[package]] 1703 | name = "time-core" 1704 | version = "0.1.6" 1705 | source = "registry+https://github.com/rust-lang/crates.io-index" 1706 | checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" 1707 | 1708 | [[package]] 1709 | name = "time-macros" 1710 | version = "0.2.24" 1711 | source = "registry+https://github.com/rust-lang/crates.io-index" 1712 | checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" 1713 | dependencies = [ 1714 | "num-conv", 1715 | "time-core", 1716 | ] 1717 | 1718 | [[package]] 1719 | name = "tinystr" 1720 | version = "0.8.1" 1721 | source = "registry+https://github.com/rust-lang/crates.io-index" 1722 | checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" 1723 | dependencies = [ 1724 | "displaydoc", 1725 | "zerovec", 1726 | ] 1727 | 1728 | [[package]] 1729 | name = "typenum" 1730 | version = "1.19.0" 1731 | source = "registry+https://github.com/rust-lang/crates.io-index" 1732 | checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" 1733 | 1734 | [[package]] 1735 | name = "unicode-ident" 1736 | version = "1.0.19" 1737 | source = "registry+https://github.com/rust-lang/crates.io-index" 1738 | checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" 1739 | 1740 | [[package]] 1741 | name = "unicode-width" 1742 | version = "0.1.14" 1743 | source = "registry+https://github.com/rust-lang/crates.io-index" 1744 | checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" 1745 | 1746 | [[package]] 1747 | name = "unicode-width" 1748 | version = "0.2.2" 1749 | source = "registry+https://github.com/rust-lang/crates.io-index" 1750 | checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" 1751 | 1752 | [[package]] 1753 | name = "untrusted" 1754 | version = "0.9.0" 1755 | source = "registry+https://github.com/rust-lang/crates.io-index" 1756 | checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 1757 | 1758 | [[package]] 1759 | name = "ureq" 1760 | version = "3.1.2" 1761 | source = "registry+https://github.com/rust-lang/crates.io-index" 1762 | checksum = "99ba1025f18a4a3fc3e9b48c868e9beb4f24f4b4b1a325bada26bd4119f46537" 1763 | dependencies = [ 1764 | "base64", 1765 | "cookie_store", 1766 | "der", 1767 | "flate2", 1768 | "log", 1769 | "native-tls", 1770 | "percent-encoding", 1771 | "rustls", 1772 | "rustls-pemfile", 1773 | "rustls-pki-types", 1774 | "serde", 1775 | "serde_json", 1776 | "ureq-proto", 1777 | "utf-8", 1778 | "webpki-root-certs", 1779 | "webpki-roots", 1780 | ] 1781 | 1782 | [[package]] 1783 | name = "ureq-proto" 1784 | version = "0.5.2" 1785 | source = "registry+https://github.com/rust-lang/crates.io-index" 1786 | checksum = "60b4531c118335662134346048ddb0e54cc86bd7e81866757873055f0e38f5d2" 1787 | dependencies = [ 1788 | "base64", 1789 | "http", 1790 | "httparse", 1791 | "log", 1792 | ] 1793 | 1794 | [[package]] 1795 | name = "url" 1796 | version = "2.5.7" 1797 | source = "registry+https://github.com/rust-lang/crates.io-index" 1798 | checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" 1799 | dependencies = [ 1800 | "form_urlencoded", 1801 | "idna", 1802 | "percent-encoding", 1803 | "serde", 1804 | ] 1805 | 1806 | [[package]] 1807 | name = "utf-8" 1808 | version = "0.7.6" 1809 | source = "registry+https://github.com/rust-lang/crates.io-index" 1810 | checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" 1811 | 1812 | [[package]] 1813 | name = "utf8_iter" 1814 | version = "1.0.4" 1815 | source = "registry+https://github.com/rust-lang/crates.io-index" 1816 | checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 1817 | 1818 | [[package]] 1819 | name = "utf8parse" 1820 | version = "0.2.2" 1821 | source = "registry+https://github.com/rust-lang/crates.io-index" 1822 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 1823 | 1824 | [[package]] 1825 | name = "vcpkg" 1826 | version = "0.2.15" 1827 | source = "registry+https://github.com/rust-lang/crates.io-index" 1828 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 1829 | 1830 | [[package]] 1831 | name = "version_check" 1832 | version = "0.9.5" 1833 | source = "registry+https://github.com/rust-lang/crates.io-index" 1834 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 1835 | 1836 | [[package]] 1837 | name = "wait-timeout" 1838 | version = "0.2.1" 1839 | source = "registry+https://github.com/rust-lang/crates.io-index" 1840 | checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" 1841 | dependencies = [ 1842 | "libc", 1843 | ] 1844 | 1845 | [[package]] 1846 | name = "walkdir" 1847 | version = "2.5.0" 1848 | source = "registry+https://github.com/rust-lang/crates.io-index" 1849 | checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 1850 | dependencies = [ 1851 | "same-file", 1852 | "winapi-util", 1853 | ] 1854 | 1855 | [[package]] 1856 | name = "wasi" 1857 | version = "0.11.1+wasi-snapshot-preview1" 1858 | source = "registry+https://github.com/rust-lang/crates.io-index" 1859 | checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" 1860 | 1861 | [[package]] 1862 | name = "wasi" 1863 | version = "0.14.7+wasi-0.2.4" 1864 | source = "registry+https://github.com/rust-lang/crates.io-index" 1865 | checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" 1866 | dependencies = [ 1867 | "wasip2", 1868 | ] 1869 | 1870 | [[package]] 1871 | name = "wasip2" 1872 | version = "1.0.1+wasi-0.2.4" 1873 | source = "registry+https://github.com/rust-lang/crates.io-index" 1874 | checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" 1875 | dependencies = [ 1876 | "wit-bindgen", 1877 | ] 1878 | 1879 | [[package]] 1880 | name = "webpki-root-certs" 1881 | version = "1.0.3" 1882 | source = "registry+https://github.com/rust-lang/crates.io-index" 1883 | checksum = "05d651ec480de84b762e7be71e6efa7461699c19d9e2c272c8d93455f567786e" 1884 | dependencies = [ 1885 | "rustls-pki-types", 1886 | ] 1887 | 1888 | [[package]] 1889 | name = "webpki-roots" 1890 | version = "1.0.3" 1891 | source = "registry+https://github.com/rust-lang/crates.io-index" 1892 | checksum = "32b130c0d2d49f8b6889abc456e795e82525204f27c42cf767cf0d7734e089b8" 1893 | dependencies = [ 1894 | "rustls-pki-types", 1895 | ] 1896 | 1897 | [[package]] 1898 | name = "winapi-util" 1899 | version = "0.1.11" 1900 | source = "registry+https://github.com/rust-lang/crates.io-index" 1901 | checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" 1902 | dependencies = [ 1903 | "windows-sys 0.61.2", 1904 | ] 1905 | 1906 | [[package]] 1907 | name = "windows-link" 1908 | version = "0.2.1" 1909 | source = "registry+https://github.com/rust-lang/crates.io-index" 1910 | checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" 1911 | 1912 | [[package]] 1913 | name = "windows-sys" 1914 | version = "0.52.0" 1915 | source = "registry+https://github.com/rust-lang/crates.io-index" 1916 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1917 | dependencies = [ 1918 | "windows-targets 0.52.6", 1919 | ] 1920 | 1921 | [[package]] 1922 | name = "windows-sys" 1923 | version = "0.60.2" 1924 | source = "registry+https://github.com/rust-lang/crates.io-index" 1925 | checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" 1926 | dependencies = [ 1927 | "windows-targets 0.53.5", 1928 | ] 1929 | 1930 | [[package]] 1931 | name = "windows-sys" 1932 | version = "0.61.2" 1933 | source = "registry+https://github.com/rust-lang/crates.io-index" 1934 | checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" 1935 | dependencies = [ 1936 | "windows-link", 1937 | ] 1938 | 1939 | [[package]] 1940 | name = "windows-targets" 1941 | version = "0.52.6" 1942 | source = "registry+https://github.com/rust-lang/crates.io-index" 1943 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 1944 | dependencies = [ 1945 | "windows_aarch64_gnullvm 0.52.6", 1946 | "windows_aarch64_msvc 0.52.6", 1947 | "windows_i686_gnu 0.52.6", 1948 | "windows_i686_gnullvm 0.52.6", 1949 | "windows_i686_msvc 0.52.6", 1950 | "windows_x86_64_gnu 0.52.6", 1951 | "windows_x86_64_gnullvm 0.52.6", 1952 | "windows_x86_64_msvc 0.52.6", 1953 | ] 1954 | 1955 | [[package]] 1956 | name = "windows-targets" 1957 | version = "0.53.5" 1958 | source = "registry+https://github.com/rust-lang/crates.io-index" 1959 | checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" 1960 | dependencies = [ 1961 | "windows-link", 1962 | "windows_aarch64_gnullvm 0.53.1", 1963 | "windows_aarch64_msvc 0.53.1", 1964 | "windows_i686_gnu 0.53.1", 1965 | "windows_i686_gnullvm 0.53.1", 1966 | "windows_i686_msvc 0.53.1", 1967 | "windows_x86_64_gnu 0.53.1", 1968 | "windows_x86_64_gnullvm 0.53.1", 1969 | "windows_x86_64_msvc 0.53.1", 1970 | ] 1971 | 1972 | [[package]] 1973 | name = "windows_aarch64_gnullvm" 1974 | version = "0.52.6" 1975 | source = "registry+https://github.com/rust-lang/crates.io-index" 1976 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 1977 | 1978 | [[package]] 1979 | name = "windows_aarch64_gnullvm" 1980 | version = "0.53.1" 1981 | source = "registry+https://github.com/rust-lang/crates.io-index" 1982 | checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" 1983 | 1984 | [[package]] 1985 | name = "windows_aarch64_msvc" 1986 | version = "0.52.6" 1987 | source = "registry+https://github.com/rust-lang/crates.io-index" 1988 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 1989 | 1990 | [[package]] 1991 | name = "windows_aarch64_msvc" 1992 | version = "0.53.1" 1993 | source = "registry+https://github.com/rust-lang/crates.io-index" 1994 | checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" 1995 | 1996 | [[package]] 1997 | name = "windows_i686_gnu" 1998 | version = "0.52.6" 1999 | source = "registry+https://github.com/rust-lang/crates.io-index" 2000 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 2001 | 2002 | [[package]] 2003 | name = "windows_i686_gnu" 2004 | version = "0.53.1" 2005 | source = "registry+https://github.com/rust-lang/crates.io-index" 2006 | checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" 2007 | 2008 | [[package]] 2009 | name = "windows_i686_gnullvm" 2010 | version = "0.52.6" 2011 | source = "registry+https://github.com/rust-lang/crates.io-index" 2012 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 2013 | 2014 | [[package]] 2015 | name = "windows_i686_gnullvm" 2016 | version = "0.53.1" 2017 | source = "registry+https://github.com/rust-lang/crates.io-index" 2018 | checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" 2019 | 2020 | [[package]] 2021 | name = "windows_i686_msvc" 2022 | version = "0.52.6" 2023 | source = "registry+https://github.com/rust-lang/crates.io-index" 2024 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 2025 | 2026 | [[package]] 2027 | name = "windows_i686_msvc" 2028 | version = "0.53.1" 2029 | source = "registry+https://github.com/rust-lang/crates.io-index" 2030 | checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" 2031 | 2032 | [[package]] 2033 | name = "windows_x86_64_gnu" 2034 | version = "0.52.6" 2035 | source = "registry+https://github.com/rust-lang/crates.io-index" 2036 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 2037 | 2038 | [[package]] 2039 | name = "windows_x86_64_gnu" 2040 | version = "0.53.1" 2041 | source = "registry+https://github.com/rust-lang/crates.io-index" 2042 | checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" 2043 | 2044 | [[package]] 2045 | name = "windows_x86_64_gnullvm" 2046 | version = "0.52.6" 2047 | source = "registry+https://github.com/rust-lang/crates.io-index" 2048 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 2049 | 2050 | [[package]] 2051 | name = "windows_x86_64_gnullvm" 2052 | version = "0.53.1" 2053 | source = "registry+https://github.com/rust-lang/crates.io-index" 2054 | checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" 2055 | 2056 | [[package]] 2057 | name = "windows_x86_64_msvc" 2058 | version = "0.52.6" 2059 | source = "registry+https://github.com/rust-lang/crates.io-index" 2060 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 2061 | 2062 | [[package]] 2063 | name = "windows_x86_64_msvc" 2064 | version = "0.53.1" 2065 | source = "registry+https://github.com/rust-lang/crates.io-index" 2066 | checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" 2067 | 2068 | [[package]] 2069 | name = "wit-bindgen" 2070 | version = "0.46.0" 2071 | source = "registry+https://github.com/rust-lang/crates.io-index" 2072 | checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" 2073 | 2074 | [[package]] 2075 | name = "writeable" 2076 | version = "0.6.1" 2077 | source = "registry+https://github.com/rust-lang/crates.io-index" 2078 | checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" 2079 | 2080 | [[package]] 2081 | name = "xattr" 2082 | version = "1.6.1" 2083 | source = "registry+https://github.com/rust-lang/crates.io-index" 2084 | checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" 2085 | dependencies = [ 2086 | "libc", 2087 | "rustix", 2088 | ] 2089 | 2090 | [[package]] 2091 | name = "yoke" 2092 | version = "0.8.0" 2093 | source = "registry+https://github.com/rust-lang/crates.io-index" 2094 | checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" 2095 | dependencies = [ 2096 | "serde", 2097 | "stable_deref_trait", 2098 | "yoke-derive", 2099 | "zerofrom", 2100 | ] 2101 | 2102 | [[package]] 2103 | name = "yoke-derive" 2104 | version = "0.8.0" 2105 | source = "registry+https://github.com/rust-lang/crates.io-index" 2106 | checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" 2107 | dependencies = [ 2108 | "proc-macro2", 2109 | "quote", 2110 | "syn", 2111 | "synstructure", 2112 | ] 2113 | 2114 | [[package]] 2115 | name = "zerofrom" 2116 | version = "0.1.6" 2117 | source = "registry+https://github.com/rust-lang/crates.io-index" 2118 | checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" 2119 | dependencies = [ 2120 | "zerofrom-derive", 2121 | ] 2122 | 2123 | [[package]] 2124 | name = "zerofrom-derive" 2125 | version = "0.1.6" 2126 | source = "registry+https://github.com/rust-lang/crates.io-index" 2127 | checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" 2128 | dependencies = [ 2129 | "proc-macro2", 2130 | "quote", 2131 | "syn", 2132 | "synstructure", 2133 | ] 2134 | 2135 | [[package]] 2136 | name = "zeroize" 2137 | version = "1.8.2" 2138 | source = "registry+https://github.com/rust-lang/crates.io-index" 2139 | checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" 2140 | dependencies = [ 2141 | "zeroize_derive", 2142 | ] 2143 | 2144 | [[package]] 2145 | name = "zeroize_derive" 2146 | version = "1.4.2" 2147 | source = "registry+https://github.com/rust-lang/crates.io-index" 2148 | checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" 2149 | dependencies = [ 2150 | "proc-macro2", 2151 | "quote", 2152 | "syn", 2153 | ] 2154 | 2155 | [[package]] 2156 | name = "zerotrie" 2157 | version = "0.2.2" 2158 | source = "registry+https://github.com/rust-lang/crates.io-index" 2159 | checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" 2160 | dependencies = [ 2161 | "displaydoc", 2162 | "yoke", 2163 | "zerofrom", 2164 | ] 2165 | 2166 | [[package]] 2167 | name = "zerovec" 2168 | version = "0.11.4" 2169 | source = "registry+https://github.com/rust-lang/crates.io-index" 2170 | checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" 2171 | dependencies = [ 2172 | "yoke", 2173 | "zerofrom", 2174 | "zerovec-derive", 2175 | ] 2176 | 2177 | [[package]] 2178 | name = "zerovec-derive" 2179 | version = "0.11.1" 2180 | source = "registry+https://github.com/rust-lang/crates.io-index" 2181 | checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" 2182 | dependencies = [ 2183 | "proc-macro2", 2184 | "quote", 2185 | "syn", 2186 | ] 2187 | 2188 | [[package]] 2189 | name = "zip" 2190 | version = "6.0.0" 2191 | source = "registry+https://github.com/rust-lang/crates.io-index" 2192 | checksum = "eb2a05c7c36fde6c09b08576c9f7fb4cda705990f73b58fe011abf7dfb24168b" 2193 | dependencies = [ 2194 | "aes", 2195 | "arbitrary", 2196 | "bzip2", 2197 | "constant_time_eq", 2198 | "crc32fast", 2199 | "deflate64", 2200 | "flate2", 2201 | "getrandom 0.3.3", 2202 | "hmac", 2203 | "indexmap", 2204 | "lzma-rust2", 2205 | "memchr", 2206 | "pbkdf2", 2207 | "ppmd-rust", 2208 | "sha1", 2209 | "time", 2210 | "zeroize", 2211 | "zopfli", 2212 | "zstd", 2213 | ] 2214 | 2215 | [[package]] 2216 | name = "zlib-rs" 2217 | version = "0.5.2" 2218 | source = "registry+https://github.com/rust-lang/crates.io-index" 2219 | checksum = "2f06ae92f42f5e5c42443fd094f245eb656abf56dd7cce9b8b263236565e00f2" 2220 | 2221 | [[package]] 2222 | name = "zopfli" 2223 | version = "0.8.2" 2224 | source = "registry+https://github.com/rust-lang/crates.io-index" 2225 | checksum = "edfc5ee405f504cd4984ecc6f14d02d55cfda60fa4b689434ef4102aae150cd7" 2226 | dependencies = [ 2227 | "bumpalo", 2228 | "crc32fast", 2229 | "log", 2230 | "simd-adler32", 2231 | ] 2232 | 2233 | [[package]] 2234 | name = "zstd" 2235 | version = "0.13.3" 2236 | source = "registry+https://github.com/rust-lang/crates.io-index" 2237 | checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" 2238 | dependencies = [ 2239 | "zstd-safe", 2240 | ] 2241 | 2242 | [[package]] 2243 | name = "zstd-safe" 2244 | version = "7.2.4" 2245 | source = "registry+https://github.com/rust-lang/crates.io-index" 2246 | checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" 2247 | dependencies = [ 2248 | "zstd-sys", 2249 | ] 2250 | 2251 | [[package]] 2252 | name = "zstd-sys" 2253 | version = "2.0.16+zstd.1.5.7" 2254 | source = "registry+https://github.com/rust-lang/crates.io-index" 2255 | checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" 2256 | dependencies = [ 2257 | "cc", 2258 | "pkg-config", 2259 | ] 2260 | --------------------------------------------------------------------------------