├── .gitignore ├── .rustfmt.toml ├── .gitattributes ├── deno.json ├── .vscode ├── extensions.json └── settings.json ├── src ├── commands │ ├── update.rs │ ├── completions.rs │ ├── mod.rs │ ├── info.rs │ ├── activate.rs │ ├── deactivate.rs │ ├── clean.rs │ ├── uninstall.rs │ ├── list.rs │ ├── exec.rs │ ├── doctor.rs │ ├── alias.rs │ ├── registry.rs │ ├── upgrade.rs │ ├── use_version.rs │ └── install.rs ├── consts.rs ├── main.rs ├── utils.rs ├── configrc.rs ├── version.rs ├── cli.rs └── meta.rs ├── .cargo └── config.toml ├── main.ts ├── .github └── workflows │ ├── deno.yml │ └── ci.yml ├── LICENSE ├── install.ps1 ├── Cargo.toml ├── install.sh ├── README_zh-cn.md ├── README.md └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /.cargo_home 3 | /.dvmrc 4 | /.idea 5 | .DS_Store -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 120 2 | tab_spaces = 2 3 | edition = "2021" 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Use Unix line endings in all text files. 2 | * text=auto eol=lf 3 | -------------------------------------------------------------------------------- /deno.json: -------------------------------------------------------------------------------- 1 | { 2 | "fmt": { 3 | "exclude": [ 4 | "target/" 5 | ] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "denoland.vscode-deno" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "deno.enable": true, 3 | "deno.lint": true, 4 | "deno.unstable": false 5 | } 6 | -------------------------------------------------------------------------------- /src/commands/update.rs: -------------------------------------------------------------------------------- 1 | use crate::version::cache_remote_versions; 2 | use crate::DvmMeta; 3 | use anyhow::Result; 4 | 5 | pub fn exec(_meta: &mut DvmMeta) -> Result<()> { 6 | cache_remote_versions()?; 7 | Ok(()) 8 | } 9 | -------------------------------------------------------------------------------- /src/commands/completions.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::Command; 3 | use clap_complete::{generate, Shell}; 4 | 5 | pub fn exec(app: &mut Command, shell: Shell) -> Result<()> { 6 | generate(shell, app, "dvm", &mut std::io::stdout()); 7 | Ok(()) 8 | } 9 | -------------------------------------------------------------------------------- /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.x86_64-pc-windows-msvc] 2 | rustflags = ["-C", "target-feature=+crt-static"] 3 | 4 | [target.aarch64-unknown-linux-gnu] 5 | linker = "aarch64-linux-gnu-gcc" 6 | 7 | [alias] 8 | lint = "clippy -- -D warnings" 9 | r = "run" 10 | rr = "run --release" 11 | b = "build" 12 | br = "build --release" 13 | -------------------------------------------------------------------------------- /src/commands/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod activate; 2 | pub mod alias; 3 | pub mod clean; 4 | pub mod completions; 5 | pub mod deactivate; 6 | pub mod doctor; 7 | pub mod exec; 8 | pub mod info; 9 | pub mod install; 10 | pub mod list; 11 | pub mod registry; 12 | pub mod uninstall; 13 | pub mod update; 14 | pub mod upgrade; 15 | pub mod use_version; 16 | -------------------------------------------------------------------------------- /src/commands/info.rs: -------------------------------------------------------------------------------- 1 | use crate::utils; 2 | use crate::version; 3 | use anyhow::Result; 4 | use std::string::String; 5 | 6 | pub fn exec() -> Result<()> { 7 | println!( 8 | "dvm {}\ndeno {}\ndvm root {}", 9 | version::DVM, 10 | version::current_version().unwrap_or_else(|| String::from("-")), 11 | utils::dvm_root().as_path().to_string_lossy(), 12 | ); 13 | Ok(()) 14 | } 15 | -------------------------------------------------------------------------------- /src/commands/activate.rs: -------------------------------------------------------------------------------- 1 | use crate::commands::use_version; 2 | use crate::utils::check_is_deactivated; 3 | use crate::{dvm_root, DvmMeta}; 4 | use anyhow::Result; 5 | 6 | pub fn exec(meta: &mut DvmMeta) -> Result<()> { 7 | let home = dvm_root(); 8 | if check_is_deactivated() { 9 | std::fs::remove_file(home.join(".deactivated")).unwrap(); 10 | } 11 | 12 | use_version::exec(meta, None, false) 13 | } 14 | -------------------------------------------------------------------------------- /src/commands/deactivate.rs: -------------------------------------------------------------------------------- 1 | use crate::utils::check_is_deactivated; 2 | use crate::{deno_bin_path, dvm_root}; 3 | use anyhow::{Ok, Result}; 4 | 5 | pub fn exec() -> Result<()> { 6 | let home = dvm_root(); 7 | if check_is_deactivated() { 8 | println!("Dvm has already been deactivated, exiting."); 9 | return Ok(()); 10 | } 11 | 12 | std::fs::write(home.join(".deactivated"), "").unwrap(); 13 | std::fs::remove_file(deno_bin_path()).unwrap(); 14 | 15 | println!("Dvm is now deacvated."); 16 | println!("Deno that was previously installed on your system will be activated now."); 17 | Ok(()) 18 | } 19 | -------------------------------------------------------------------------------- /main.ts: -------------------------------------------------------------------------------- 1 | Deno.serve(async (req: Request) => { 2 | const userAgent = req.headers.get("User-Agent") || ""; 3 | 4 | if (userAgent.includes("PowerShell")) { 5 | return new Response(await Deno.readFile("./install.ps1")); 6 | } 7 | 8 | if (userAgent.includes("curl")) { 9 | return new Response(await Deno.readFile("./install.sh")); 10 | } 11 | 12 | return new Response(` 13 | Deno Version Manager - Easy way to manage multiple active deno versions. 14 | 15 | Install With Shell: 16 | 17 | curl -fsSL https://dvm.deno.dev | sh 18 | 19 | Install With PowerShell: 20 | 21 | irm https://dvm.deno.dev | iex 22 | `); 23 | }); 24 | -------------------------------------------------------------------------------- /.github/workflows/deno.yml: -------------------------------------------------------------------------------- 1 | name: deno 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | name: ${{ matrix.kind }} ${{ matrix.os }} 8 | runs-on: ${{ matrix.os }} 9 | strategy: 10 | matrix: 11 | os: [macOS-latest, ubuntu-latest, windows-latest] 12 | 13 | steps: 14 | - name: Checkout repository 15 | uses: actions/checkout@v3 16 | 17 | - name: Set up Deno 18 | uses: denoland/setup-deno@v1 19 | 20 | - name: Format 21 | run: deno fmt --check 22 | 23 | - name: Check 24 | run: deno check --remote main.ts 25 | 26 | - name: Lint 27 | run: deno lint 28 | -------------------------------------------------------------------------------- /src/commands/clean.rs: -------------------------------------------------------------------------------- 1 | use crate::configrc::rc_clean; 2 | use crate::{dvm_root, DvmMeta}; 3 | use anyhow::Result; 4 | 5 | pub fn exec(meta: &mut DvmMeta) -> Result<()> { 6 | let home = dvm_root(); 7 | 8 | let cache_folder = home.join("versions"); 9 | if !cache_folder.exists() { 10 | std::process::exit(0); 11 | } 12 | 13 | let requires = meta 14 | .versions 15 | .iter() 16 | .filter_map(|v| { 17 | if v.is_valid_mapping() { 18 | None 19 | } else { 20 | Some(v.required.clone()) 21 | } 22 | }) 23 | .collect::>(); 24 | 25 | for required in requires { 26 | meta.delete_version_mapping(required.clone()); 27 | } 28 | 29 | meta.clean_files(); 30 | 31 | // clean user-wide rc file 32 | rc_clean(true).expect("clean local rc file failed"); 33 | rc_clean(false).expect("clean user-wide rc file failed"); 34 | 35 | println!("Cleaned successfully"); 36 | Ok(()) 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) justjavac. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/commands/uninstall.rs: -------------------------------------------------------------------------------- 1 | use crate::consts::DVM_CACHE_PATH_PREFIX; 2 | use crate::utils::{deno_version_path, dvm_root}; 3 | use crate::version::current_version; 4 | use anyhow::Result; 5 | use semver::Version; 6 | use std::fs; 7 | use std::process::exit; 8 | 9 | pub fn exec(version: Option) -> Result<()> { 10 | let target_version = match version { 11 | Some(target_version) => match Version::parse(&target_version) { 12 | Ok(ver) => ver, 13 | Err(_) => { 14 | eprintln!("Invalid semver"); 15 | exit(1) 16 | } 17 | }, 18 | None => unimplemented!(), 19 | }; 20 | let target_exe_path = deno_version_path(&target_version); 21 | 22 | println!("{}", target_exe_path.display()); 23 | 24 | if !target_exe_path.exists() { 25 | eprintln!("deno v{} is not installed.", target_version); 26 | exit(1) 27 | } 28 | 29 | let current_version = current_version().unwrap(); 30 | 31 | if current_version == target_version.to_string() { 32 | println!("Failed: deno v{} is in use.", target_version); 33 | exit(1); 34 | } 35 | 36 | let version_dir = dvm_root().join(format!("{}/{}", DVM_CACHE_PATH_PREFIX, target_version)); 37 | 38 | fs::remove_dir_all(version_dir).unwrap(); 39 | println!("deno v{} removed.", target_version); 40 | 41 | Ok(()) 42 | } 43 | -------------------------------------------------------------------------------- /install.ps1: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pwsh 2 | # Copyright 2018 the Deno authors. All rights reserved. MIT license. 3 | # Copyright 2022 justjavac. All rights reserved. MIT license. 4 | # TODO(everyone): Keep this script simple and easily auditable. 5 | 6 | $ErrorActionPreference = 'Stop' 7 | 8 | $DvmDir = $env:DVM_DIR 9 | $BinDir = if ($DvmDir) { 10 | "$DvmDir\bin" 11 | } else { 12 | "$Home\.dvm\bin" 13 | } 14 | 15 | $DvmZip = "$BinDir\dvm.zip" 16 | $DvmExe = "$BinDir\dvm.exe" 17 | $DvmExeOldName = "dvm.exe.old" 18 | $DvmExeOld = "$BinDir\$DvmExeOldName" 19 | $DvmUri = "https://cdn.jsdelivr.net/gh/justjavac/dvm_releases@main/dvm-x86_64-pc-windows-msvc.zip" 20 | 21 | if (!(Test-Path $BinDir)) { 22 | New-Item $BinDir -ItemType Directory | Out-Null 23 | } 24 | 25 | curl.exe -Lo $DvmZip $DvmUri 26 | 27 | # Remove the old dvm.exe.old if it exists 28 | Remove-Item $DvmExeOld -ErrorAction SilentlyContinue 29 | # You cant delete a file that is currently running, so rename it 30 | Rename-Item -Path $DvmExe -NewName $DvmExeOldName -ErrorAction SilentlyContinue 31 | 32 | tar.exe xf $DvmZip -C $BinDir 33 | 34 | Remove-Item $DvmZip 35 | 36 | $User = [EnvironmentVariableTarget]::User 37 | $Path = [Environment]::GetEnvironmentVariable('Path', $User) 38 | 39 | if (!(";$Path;".ToLower() -like "*;$BinDir;*".ToLower())) { 40 | [Environment]::SetEnvironmentVariable('Path', "$Path;$BinDir", $User) 41 | $Env:Path += ";$BinDir" 42 | } 43 | 44 | Write-Output "Dvm was installed successfully to $DvmExe" 45 | Invoke-Expression -Command "dvm doctor" 46 | Write-Output "Run 'dvm --help' to get started" 47 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dvm" 3 | version = "1.9.3" 4 | license = "MIT" 5 | authors = ["迷渡 ", "CGQAQ "] 6 | edition = "2021" 7 | description = "Deno Version Manager - Easy way to manage multiple active deno versions." 8 | repository = "https://github.com/justjavac/dvm" 9 | default-run = "dvm" 10 | keywords = ["deno", "version", "dvm", "utils"] 11 | categories = ["hardware-support"] 12 | documentation = "https://docs.rs/dvm" 13 | readme = "README.md" 14 | 15 | [[bin]] 16 | name = "dvm" 17 | path = "src/main.rs" 18 | 19 | [dependencies] 20 | anyhow = "1.0.75" 21 | asserts-rs = "0.3.0" 22 | cfg-if = "1.0.0" 23 | clap = "4.4.11" 24 | clap_derive = "4.4.7" 25 | clap_complete = "4.4.4" 26 | colored = "2.1.0" 27 | dirs = "5.0.1" 28 | indicatif = "0.17.7" 29 | json_minimal = "0.1.3" 30 | native-tls = { version = "0.2.11", features = ["vendored"] } 31 | phf = { version = "0.11.2", features = ["macros"] } 32 | semver = "1.0.20" 33 | serde = { version = "1.0.19", features = ["derive"] } 34 | serde_json = "1.0.108" 35 | set_env = "1.3.4" 36 | tempfile = "3.8.1" 37 | tinyget = { version = "1.0.1", features = ["https"] } 38 | which = "5.0.0" 39 | 40 | [target.'cfg(windows)'.dependencies] 41 | ctor = "0.2.5" 42 | output_vt100 = "0.1.3" 43 | winapi = { version = "0.3.9", features = ["winnls"] } 44 | 45 | [package.metadata.winres] 46 | # This section defines the metadata that appears in the dvm.exe PE header. 47 | OriginalFilename = "dvm.exe" 48 | LegalCopyright = "© Dvm contributors. MIT licensed." 49 | ProductName = "Dvm" 50 | FileDescription = "Deno Version Manager - Easy way to manage multiple active deno versions." 51 | 52 | [profile.release] 53 | codegen-units = 1 54 | lto = true 55 | opt-level = 'z' # Optimize for size 56 | panic = "abort" 57 | # strip = "symbols" 58 | -------------------------------------------------------------------------------- /src/commands/list.rs: -------------------------------------------------------------------------------- 1 | use crate::version::{current_version, local_versions, remote_versions}; 2 | use anyhow::Result; 3 | use semver::Version; 4 | use std::cmp::Ordering; 5 | 6 | pub fn exec() -> Result<()> { 7 | let versions = local_versions(); 8 | 9 | print_versions(versions); 10 | Ok(()) 11 | } 12 | 13 | pub fn exec_remote() -> Result<()> { 14 | let versions = remote_versions().unwrap(); 15 | 16 | print_versions(versions); 17 | Ok(()) 18 | } 19 | 20 | fn print_versions(mut versions: Vec) { 21 | let current_version = current_version().unwrap_or_default(); 22 | 23 | versions.sort_by(|a, b| sort_semver_version(b, a).reverse()); 24 | 25 | for v in &versions { 26 | if *v == current_version { 27 | // display current used version with bright green 28 | println!("\x1b[0;92m*{}\x1b[0m", v); 29 | } else { 30 | println!(" {}", v) 31 | } 32 | } 33 | } 34 | 35 | fn sort_semver_version(s1: &str, s2: &str) -> Ordering { 36 | let v1 = Version::parse(s1).unwrap(); 37 | let v2 = Version::parse(s2).unwrap(); 38 | 39 | v1.cmp(&v2) 40 | } 41 | 42 | #[cfg(test)] 43 | mod tests { 44 | use super::*; 45 | 46 | #[test] 47 | fn sort_version() { 48 | let v1 = String::from("0.2.10-beta"); 49 | let v2 = String::from("0.2.9"); 50 | let v3 = String::from("0.2.10-alpha1"); 51 | let v4 = String::from("1.10.0"); 52 | let v5 = String::from("1.9.2"); 53 | let v6 = String::from("9.9.0"); 54 | let v7 = String::from("10.9.0"); 55 | 56 | assert_eq!(sort_semver_version(&v1, &v2), Ordering::Greater); 57 | assert_eq!(sort_semver_version(&v1, &v3), Ordering::Greater); 58 | assert_eq!(sort_semver_version(&v4, &v3), Ordering::Greater); 59 | assert_eq!(sort_semver_version(&v5, &v4), Ordering::Less); 60 | assert_eq!(sort_semver_version(&v7, &v6), Ordering::Greater); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Copyright 2019 the Deno authors. All rights reserved. MIT license. 3 | # Copyright 2022 justjavac. All rights reserved. MIT license. 4 | # TODO(everyone): Keep this script simple and easily auditable. 5 | 6 | set -e 7 | 8 | if ! command -v unzip >/dev/null; then 9 | echo "Error: unzip is required to install Dvm (see: https://github.com/justjavac/dvm#unzip-is-required)." 1>&2 10 | exit 1 11 | fi 12 | 13 | if [ "$OS" = "Windows_NT" ]; then 14 | target="x86_64-pc-windows-msvc" 15 | else 16 | case $(uname -sm) in 17 | "Darwin x86_64") target="x86_64-apple-darwin" ;; 18 | "Darwin arm64") target="aarch64-apple-darwin" ;; 19 | "Linux x86_64") target="x86_64-unknown-linux-gnu" ;; 20 | "Linux aarch64") target="aarch64-unknown-linux-gnu.zip" ;; 21 | *) echo "Unsupported OS + CPU combination: $(uname -sm)"; exit 1 ;; 22 | esac 23 | fi 24 | 25 | dvm_uri="https://cdn.jsdelivr.net/gh/justjavac/dvm_releases@main/dvm-${target}.zip" 26 | 27 | dvm_dir="${DVM_DIR:-$HOME/.dvm}" 28 | dvm_bin_dir="$dvm_dir/bin" 29 | exe="$dvm_bin_dir/dvm" 30 | 31 | if [ ! -d "$dvm_bin_dir" ]; then 32 | mkdir -p "$dvm_bin_dir" 33 | fi 34 | 35 | if [ "$1" = "" ]; then 36 | cd "$dvm_bin_dir" 37 | curl --fail --location --progress-bar -k --output "$exe.zip" "$dvm_uri" 38 | unzip -o "$exe.zip" 39 | rm "$exe.zip" 40 | else 41 | echo "Install path override detected: $1" 42 | if [ ! -f "$1" ]; then 43 | echo "File does not exist: $1" 44 | exit 1 45 | fi 46 | cp "$1" "$exe" 47 | fi 48 | cd "$dvm_bin_dir" 49 | chmod +x "$exe" 50 | 51 | case $SHELL in 52 | /bin/zsh) shell_profile=".zshrc" ;; 53 | *) shell_profile=".bash_profile" ;; 54 | esac 55 | 56 | if [ ! $DVM_DIR ];then 57 | EXPORT_DVM_DIR="export DVM_DIR=\"$dvm_dir\"" 58 | EXPORT_PATH="export PATH=\"\$DVM_DIR/bin:\$PATH\"" 59 | command printf "\\n$EXPORT_DVM_DIR\\n$EXPORT_PATH\\n" >> "$HOME/$shell_profile" 60 | fi 61 | 62 | echo "Dvm was installed successfully to $exe" 63 | if command -v dvm >/dev/null; then 64 | command dvm doctor 65 | echo "Run 'dvm --help' to get started." 66 | else 67 | echo "Reopen your shell, or run 'source $HOME/$shell_profile' to get started" 68 | fi 69 | -------------------------------------------------------------------------------- /src/commands/exec.rs: -------------------------------------------------------------------------------- 1 | use std::process::Stdio; 2 | 3 | use crate::{ 4 | consts::DVM_VERSION_LATEST, 5 | meta::DvmMeta, 6 | utils::{best_version, deno_version_path, is_exact_version, prompt_request}, 7 | version::{remote_versions, VersionArg}, 8 | }; 9 | use anyhow::Result; 10 | use colored::Colorize; 11 | use semver::Version; 12 | 13 | use super::install; 14 | 15 | pub fn exec(meta: &mut DvmMeta, version: Option, args: Vec) -> Result<()> { 16 | let versions = remote_versions().expect("Failed to get remote versions"); 17 | let version = version.unwrap_or_else(|| DVM_VERSION_LATEST.to_string()); 18 | let v = version.clone(); 19 | 20 | let Some(version) = is_exact_version(&version).then_some(version).or_else(|| { 21 | meta.has_alias(&v).then(|| { 22 | let version_req = meta.resolve_version_req(&v); 23 | match version_req { 24 | VersionArg::Exact(v) => v.to_string(), 25 | VersionArg::Range(r) => { 26 | let best = best_version(versions.iter().map(AsRef::as_ref), r.clone()); 27 | if let Some(best) = best { 28 | best.to_string() 29 | } else { 30 | eprintln!("No version found for {} in {:?}", r, versions); 31 | std::process::exit(1); 32 | } 33 | } 34 | } 35 | }) 36 | }) else { 37 | eprintln!("{}", "No such alias or version found.".red()); 38 | std::process::exit(1); 39 | }; 40 | 41 | let executable_path = deno_version_path(&Version::parse(&version).unwrap()); 42 | 43 | if !executable_path.exists() { 44 | if prompt_request(format!("deno v{} is not installed. do you want to install it?", version).as_str()) { 45 | install::exec(meta, true, Some(version.clone())).unwrap_or_else(|_| panic!("Failed to install deno {}", version)); 46 | } else { 47 | eprintln!("{}", "No such version found.".red()); 48 | std::process::exit(1); 49 | } 50 | } 51 | 52 | let mut cmd = std::process::Command::new(executable_path) 53 | .args(args) 54 | .stderr(Stdio::inherit()) 55 | .stdout(Stdio::inherit()) 56 | .stdin(Stdio::inherit()) 57 | .spawn() 58 | .unwrap(); 59 | 60 | cmd.wait().unwrap(); 61 | Ok(()) 62 | } 63 | -------------------------------------------------------------------------------- /src/consts.rs: -------------------------------------------------------------------------------- 1 | pub const REGISTRY_OFFICIAL: &str = "https://dl.deno.land/"; 2 | pub const REGISTRY_CN: &str = "https://dl.deno.js.cn/"; 3 | pub const REGISTRY_LIST_OFFICIAL: &str = "https://deno.com/versions.json"; 4 | pub const REGISTRY_LIST_CN: &str = "https://dl.deno.js.cn/versions.json"; 5 | 6 | pub const REGISTRY_LATEST_RELEASE_PATH: &str = "release-latest.txt"; 7 | pub const REGISTRY_LATEST_CANARY_PATH: &str = "canary-latest.txt"; 8 | pub const REGISTRY_NAME_CN: &str = "cn"; 9 | pub const REGISTRY_NAME_OFFICIAL: &str = "official"; 10 | 11 | pub const DVM_CACHE_PATH_PREFIX: &str = "versions"; 12 | pub const DVM_CACHE_REMOTE_PATH: &str = "cached-remote-versions.json"; 13 | pub const DVM_CANARY_PATH_PREFIX: &str = "canary"; 14 | pub const DVM_CACHE_INVALID_TIMEOUT: u128 = 60 * 60 * 24 * 7; 15 | 16 | pub const DVM_CONFIGRC_FILENAME: &str = ".dvmrc"; 17 | pub const DVM_CONFIGRC_KEY_DENO_VERSION: &str = "deno_version"; 18 | pub const DVM_CONFIGRC_KEY_REGISTRY_VERSION: &str = "registry_version"; 19 | pub const DVM_CONFIGRC_KEY_REGISTRY_BINARY: &str = "registry_binary"; 20 | 21 | pub const DVM_VERSION_SELF: &str = "self"; 22 | pub const DVM_VERSION_CANARY: &str = "canary"; 23 | pub const DVM_VERSION_LATEST: &str = "latest"; 24 | pub const DVM_VERSION_SYSTEM: &str = "system"; 25 | pub const DVM_VERSION_INVALID: &str = "N/A"; 26 | 27 | cfg_if::cfg_if! { 28 | if #[cfg(windows)] { 29 | pub const DENO_EXE: &str = "deno.exe"; 30 | } else { 31 | pub const DENO_EXE: &str = "deno"; 32 | } 33 | } 34 | 35 | pub const AFTER_HELP: &str = "\x1b[33mEXAMPLE:\x1b[39m 36 | dvm install 1.3.2 Install v1.3.2 release 37 | dvm install Install the latest available version 38 | dvm use 1.0.0 Use v1.0.0 release 39 | dvm use latest Use the latest alias that comes with dvm, equivalent to * 40 | dvm use canary Use the canary version of the Deno 41 | 42 | \x1b[33mNOTE:\x1b[39m 43 | To remove, delete, or uninstall dvm - just remove the \x1b[36m`$DVM_DIR`\x1b[39m folder (usually \x1b[36m`~/.dvm`\x1b[39m)"; 44 | 45 | pub const COMPLETIONS_HELP: &str = "Output shell completion script to standard output. 46 | \x1b[35m 47 | dvm completions bash > /usr/local/etc/bash_completion.d/dvm.bash 48 | source /usr/local/etc/bash_completion.d/dvm.bash\x1b[39m"; 49 | -------------------------------------------------------------------------------- /src/commands/doctor.rs: -------------------------------------------------------------------------------- 1 | use crate::configrc::{rc_clean, rc_fix}; 2 | use crate::consts::DVM_CACHE_PATH_PREFIX; 3 | use anyhow::Result; 4 | use colored::Colorize; 5 | use std::fs; 6 | 7 | use crate::meta::DvmMeta; 8 | use crate::utils::{deno_bin_path, dvm_root, is_exact_version}; 9 | 10 | pub fn exec(meta: &mut DvmMeta) -> Result<()> { 11 | // Init enviroments if need 12 | // actually set DVM_DIR env var if not exist. 13 | let home_path = dvm_root(); 14 | set_env::check_or_set("DVM_DIR", home_path.to_str().unwrap()).unwrap(); 15 | let path = set_env::get("PATH").unwrap(); 16 | let looking_for = deno_bin_path().parent().unwrap().to_str().unwrap().to_string(); 17 | let current = which::which("deno"); 18 | 19 | if let Ok(current) = current { 20 | if current.to_str().unwrap().starts_with(&looking_for) { 21 | println!("{}", "DVM deno bin is already set correctly.".green()); 22 | } else { 23 | set_env::prepend("PATH", looking_for.as_str()).unwrap(); 24 | println!("{}", "Please restart your shell of choice to take effects.".red()); 25 | } 26 | } else if !path.contains(looking_for.as_str()) { 27 | set_env::prepend("PATH", looking_for.as_str()).unwrap(); 28 | println!("{}", "Please restart your shell of choice to take effects.".red()); 29 | } 30 | 31 | // migrating from old dvm cache. 32 | let cache_folder = home_path.join(DVM_CACHE_PATH_PREFIX); 33 | if !cache_folder.exists() { 34 | fs::create_dir_all(cache_folder)?; 35 | } else if !home_path.join(DVM_CACHE_PATH_PREFIX).is_dir() { 36 | fs::remove_file(cache_folder.clone())?; 37 | fs::create_dir_all(cache_folder)?; 38 | } 39 | let list = fs::read_dir(home_path).unwrap(); 40 | for entry in list { 41 | let entry = entry.unwrap(); 42 | let path = entry.path(); 43 | if path.is_dir() { 44 | let name = path.file_name().unwrap().to_str().unwrap(); 45 | if is_exact_version(name) { 46 | // move to `versions` subdir 47 | println!( 48 | "Found old dvm cache of version `{}`, migrating to new dvm cache location...", 49 | name 50 | ); 51 | fs::rename(path.clone(), path.parent().unwrap().join("versions").join(name)).unwrap(); 52 | } 53 | } 54 | } 55 | 56 | if dvm_root().exists() { 57 | super::use_version::exec(meta, None, false).unwrap(); 58 | } 59 | 60 | // clean user-wide rc file 61 | rc_clean(true).expect("clean local rc file failed"); 62 | rc_clean(false).expect("clean user-wide rc file failed"); 63 | rc_fix().expect("fix rc file failed"); 64 | 65 | println!("{}", "All fixes applied, DVM is ready to use.".green()); 66 | Ok(()) 67 | } 68 | -------------------------------------------------------------------------------- /src/commands/alias.rs: -------------------------------------------------------------------------------- 1 | use crate::cli::AliasCommands; 2 | use crate::version::{find_max_matching_version, local_versions, remote_versions, version_req_parse}; 3 | use crate::{DvmMeta, DEFAULT_ALIAS}; 4 | 5 | use anyhow::Result; 6 | use colored::{ColoredString, Colorize}; 7 | use phf::phf_map; 8 | 9 | const ALIAS_COLORS: phf::Map<&str, (u8, u8, u8)> = phf_map! { 10 | "lighter" => (0xD1, 0xFA, 0xFF), // unused 11 | "norm" => (0x9B, 0xD1, 0xE5), // user add alias 12 | "darker" => (0x6A, 0x8E, 0xAE), // default alias 13 | "highlight" => (0x15, 0x71, 0x45), // latest version 14 | "highlightdark" => (0x57, 0xA7, 0x73), // need upgrade 15 | }; 16 | 17 | fn apply_alias_color(a: &str, c: &str) -> ColoredString { 18 | a.truecolor( 19 | ALIAS_COLORS.get(c).unwrap().0, 20 | ALIAS_COLORS.get(c).unwrap().1, 21 | ALIAS_COLORS.get(c).unwrap().2, 22 | ) 23 | } 24 | 25 | pub fn exec(meta: &mut DvmMeta, command: AliasCommands) -> Result<()> { 26 | match command { 27 | AliasCommands::Set { name, content } => { 28 | version_req_parse(content.as_str()); 29 | meta.set_alias(name, content); 30 | Ok(()) 31 | } 32 | AliasCommands::Unset { name } => { 33 | meta.delete_alias(name); 34 | Ok(()) 35 | } 36 | AliasCommands::List => { 37 | let remote_versions = remote_versions().unwrap(); 38 | let local_versions = local_versions(); 39 | let get_upgrade_version = |version_str: &str| { 40 | let max_remote_version = 41 | find_max_matching_version(version_str, remote_versions.iter().map(AsRef::as_ref)).unwrap(); 42 | 43 | let max_local_version = 44 | find_max_matching_version(version_str, local_versions.iter().map(AsRef::as_ref)).unwrap(); 45 | if let (Some(max_remote), Some(max_local)) = (max_remote_version, max_local_version) { 46 | if max_remote > max_local { 47 | return Some(max_remote); 48 | } 49 | } 50 | None 51 | }; 52 | for (key, val) in DEFAULT_ALIAS.entries() { 53 | let upgrade_version = get_upgrade_version(val); 54 | if let Some(upgrade_version) = upgrade_version { 55 | println!( 56 | "{} -> {} ( -> {})", 57 | apply_alias_color(key, "norm"), 58 | val, 59 | apply_alias_color(upgrade_version.to_string().as_str(), "highlightdark") 60 | ); 61 | continue; 62 | } 63 | println!("{} -> {}", apply_alias_color(key, "darker"), val); 64 | } 65 | for alias in &meta.alias { 66 | let upgrade_version = get_upgrade_version(&alias.required); 67 | if let Some(upgrade_version) = upgrade_version { 68 | println!( 69 | "{} -> {} ( -> {})", 70 | apply_alias_color(&alias.name, "norm"), 71 | &alias.required, 72 | apply_alias_color(upgrade_version.to_string().as_str(), "highlightdark") 73 | ); 74 | continue; 75 | } 76 | println!("{} -> {}", apply_alias_color(&alias.name, "norm"), alias.required); 77 | } 78 | Ok(()) 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate core; 2 | 3 | mod cli; 4 | mod commands; 5 | mod configrc; 6 | mod consts; 7 | mod meta; 8 | mod utils; 9 | pub mod version; 10 | 11 | use cfg_if::cfg_if; 12 | use clap::CommandFactory; 13 | 14 | use cli::{Cli, Commands}; 15 | use colored::Colorize; 16 | use meta::DvmMeta; 17 | use utils::{dvm_root, run_with_spinner}; 18 | 19 | use crate::meta::DEFAULT_ALIAS; 20 | use crate::utils::deno_bin_path; 21 | 22 | cfg_if! { 23 | if #[cfg(windows)] { 24 | use ctor::*; 25 | #[ctor] 26 | fn init() { 27 | output_vt100::try_init().ok(); 28 | } 29 | } 30 | } 31 | 32 | pub fn main() { 33 | let mut meta = DvmMeta::new(); 34 | 35 | let Ok(cli) = cli::cli_parse(&mut meta) else { 36 | return; 37 | }; 38 | 39 | let result = match cli.command { 40 | Commands::Completions { shell } => commands::completions::exec(&mut Cli::command(), shell), 41 | Commands::Info => commands::info::exec(), 42 | Commands::Install { no_use, version } => run_with_spinner( 43 | format!("Installing {}", version.clone().unwrap_or_else(|| "latest".to_string())), 44 | "Installed".to_string(), 45 | |stop_with_error| match commands::install::exec(&meta, no_use, version) { 46 | Ok(ok) => Ok(ok), 47 | Err(err) => stop_with_error(format!("Failed to install: {}", err)), 48 | }, 49 | ), 50 | Commands::List => commands::list::exec(), 51 | Commands::ListRemote => commands::list::exec_remote(), 52 | Commands::Uninstall { version } => commands::uninstall::exec(version), 53 | Commands::Use { version, write_local } => commands::use_version::exec(&mut meta, version, write_local), 54 | Commands::Alias { command } => commands::alias::exec(&mut meta, command), 55 | Commands::Activate => commands::activate::exec(&mut meta), 56 | Commands::Deactivate => commands::deactivate::exec(), 57 | Commands::Doctor => run_with_spinner( 58 | "Fixing...".to_string(), 59 | "All fixes applied, DVM is ready to use.".green().to_string(), 60 | |fail| match commands::doctor::exec(&mut meta) { 61 | Ok(ok) => Ok(ok), 62 | Err(err) => fail(format!("Failed to fix: {}", err)), 63 | }, 64 | ), 65 | Commands::Upgrade { alias } => run_with_spinner( 66 | "Upgrading...".to_string(), 67 | "All alias have been upgraded.".to_string(), 68 | |fail| match commands::upgrade::exec(&mut meta, alias) { 69 | Ok(ok) => Ok(ok), 70 | Err(err) => fail(format!("Failed to upgrade: {}", err)), 71 | }, 72 | ), 73 | 74 | Commands::Exec { command: _, version: _ } => { 75 | /* unused */ 76 | Ok(()) 77 | } 78 | Commands::Clean => { 79 | run_with_spinner( 80 | "Cleaning...".to_string(), 81 | "clean finished".to_string(), 82 | |fail| match commands::clean::exec(&mut meta) { 83 | Ok(ok) => Ok(ok), 84 | Err(err) => fail(format!("Failed to clean: {}", err)), 85 | }, 86 | ) 87 | } 88 | 89 | Commands::Registry { command } => commands::registry::exec(&mut meta, command), 90 | Commands::Update => run_with_spinner("Updating cache...".to_string(), "Update success".to_string(), |fail| { 91 | match commands::update::exec(&mut meta) { 92 | Ok(ok) => Ok(ok), 93 | Err(err) => fail(format!("Failed to update: {}", err)), 94 | } 95 | }), 96 | }; 97 | 98 | if let Err(err) = result { 99 | eprintln!("\x1b[31merror:\x1b[39m: {}", err); 100 | std::process::exit(1); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/commands/registry.rs: -------------------------------------------------------------------------------- 1 | use std::process; 2 | 3 | use crate::cli::{BinaryRegistryCommands, RegistryCommands, VersionRegistryCommands}; 4 | use crate::consts::REGISTRY_NAME_OFFICIAL; 5 | use crate::consts::REGISTRY_OFFICIAL; 6 | use crate::consts::{DVM_CONFIGRC_KEY_REGISTRY_BINARY, DVM_CONFIGRC_KEY_REGISTRY_VERSION, REGISTRY_NAME_CN}; 7 | use crate::consts::{REGISTRY_CN, REGISTRY_LIST_CN, REGISTRY_LIST_OFFICIAL}; 8 | use crate::DvmMeta; 9 | 10 | use crate::configrc::{rc_get_with_fix, rc_update}; 11 | use crate::utils::is_http_like_url; 12 | use anyhow::Result; 13 | use colored::Colorize; 14 | 15 | pub fn exec(meta: &mut DvmMeta, registry: RegistryCommands) -> Result<()> { 16 | let rc_binary_registry = 17 | rc_get_with_fix(DVM_CONFIGRC_KEY_REGISTRY_BINARY).unwrap_or_else(|_| REGISTRY_OFFICIAL.to_string()); 18 | let rc_version_registry = 19 | rc_get_with_fix(DVM_CONFIGRC_KEY_REGISTRY_VERSION).unwrap_or_else(|_| REGISTRY_OFFICIAL.to_string()); 20 | 21 | match registry { 22 | RegistryCommands::List => { 23 | println!("{}:", "official".bright_blue()); 24 | println!(" binary_registry\t{}", REGISTRY_OFFICIAL); 25 | println!(" version_registry\t{}", REGISTRY_LIST_OFFICIAL); 26 | 27 | println!("{}:", "cn".bright_blue()); 28 | println!(" binary_registry\t{}", REGISTRY_CN); 29 | println!(" version_registry\t{}", REGISTRY_LIST_CN); 30 | println!("Use {} to set the registry.", "dvm registry set ".bright_green()); 31 | println!("for example: {}", "dvm registry set official".bright_green()); 32 | } 33 | RegistryCommands::Show => { 34 | println! {"{}: ", "current registry info".bright_blue()}; 35 | println!(" binary_registry\t{}", rc_binary_registry); 36 | println!(" version_registry\t{}", rc_version_registry); 37 | } 38 | RegistryCommands::Set { 39 | predefined, 40 | write_local, 41 | } => { 42 | rc_update( 43 | write_local, 44 | DVM_CONFIGRC_KEY_REGISTRY_BINARY, 45 | &predefined.get_binary_url(), 46 | )?; 47 | rc_update( 48 | write_local, 49 | DVM_CONFIGRC_KEY_REGISTRY_VERSION, 50 | &predefined.get_version_url(), 51 | )?; 52 | } 53 | RegistryCommands::Binary { sub } => match sub { 54 | BinaryRegistryCommands::Show => { 55 | println!("{}: {}", "current binary registry".bright_blue(), rc_binary_registry); 56 | } 57 | BinaryRegistryCommands::Set { custom, write_local } => { 58 | if custom == REGISTRY_NAME_OFFICIAL { 59 | rc_update(write_local, DVM_CONFIGRC_KEY_REGISTRY_BINARY, REGISTRY_OFFICIAL)?; 60 | } else if custom == REGISTRY_NAME_CN { 61 | rc_update(write_local, DVM_CONFIGRC_KEY_REGISTRY_BINARY, REGISTRY_CN)?; 62 | } else if is_http_like_url(&custom) { 63 | rc_update(write_local, DVM_CONFIGRC_KEY_REGISTRY_BINARY, &custom)?; 64 | } else { 65 | println!("{}: {}", "invalid registry".bright_red(), custom); 66 | process::exit(1); 67 | } 68 | } 69 | }, 70 | RegistryCommands::Version { sub } => match sub { 71 | VersionRegistryCommands::Show => { 72 | println!("{}: {}", "current version registry".bright_blue(), rc_version_registry); 73 | } 74 | VersionRegistryCommands::Set { custom, write_local } => { 75 | if custom == REGISTRY_NAME_OFFICIAL { 76 | rc_update(write_local, DVM_CONFIGRC_KEY_REGISTRY_VERSION, REGISTRY_LIST_OFFICIAL)?; 77 | } else if custom == REGISTRY_NAME_CN { 78 | rc_update(write_local, DVM_CONFIGRC_KEY_REGISTRY_VERSION, REGISTRY_LIST_CN)?; 79 | } else if is_http_like_url(&custom) { 80 | rc_update(write_local, DVM_CONFIGRC_KEY_REGISTRY_VERSION, &custom)?; 81 | } else { 82 | eprintln!("The {} is not valid URL, please starts with `http` or `https`", custom); 83 | eprintln!("Registry will not be changed"); 84 | process::exit(1) 85 | } 86 | } 87 | }, 88 | }; 89 | 90 | meta.save(); 91 | Ok(()) 92 | } 93 | -------------------------------------------------------------------------------- /src/commands/upgrade.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | commands::install, 3 | consts::{DVM_VERSION_CANARY, DVM_VERSION_INVALID, DVM_VERSION_SELF}, 4 | utils::best_version, 5 | version::{remote_versions, VersionArg}, 6 | DvmMeta, 7 | }; 8 | use anyhow::{Ok, Result}; 9 | use colored::Colorize; 10 | use std::fs; 11 | use std::str::FromStr; 12 | 13 | pub fn exec(meta: &mut DvmMeta, alias: Option) -> Result<()> { 14 | let versions = remote_versions().expect("Fetching version list failed."); 15 | if let Some(alias) = alias { 16 | if alias == DVM_VERSION_SELF { 17 | upgrade_self()?; 18 | return Ok(()); 19 | } 20 | 21 | if alias == DVM_VERSION_CANARY { 22 | println!("Upgrading {}", alias.bright_black()); 23 | install::exec(meta, true, Some(alias)).unwrap(); 24 | println!("All aliases have been upgraded"); 25 | return Ok(()); 26 | } 27 | 28 | if !meta.has_alias(&alias) { 29 | eprintln!( 30 | "{} is not a valid semver version or tag and will not be upgraded", 31 | alias.bright_black() 32 | ); 33 | std::process::exit(1); 34 | } 35 | println!("Upgrading alias {}", alias.bright_black()); 36 | let current = meta 37 | .get_version_mapping(alias.as_str()) 38 | .unwrap_or_else(|| DVM_VERSION_INVALID.to_string()); 39 | let version_req = meta.resolve_version_req(&alias); 40 | match version_req { 41 | VersionArg::Exact(v) => { 42 | if current == v.to_string() { 43 | println!("{} is already the latest version", alias); 44 | std::process::exit(0); 45 | } else { 46 | install::exec(meta, true, Some(v.to_string())).expect("Install failed"); 47 | } 48 | } 49 | VersionArg::Range(r) => { 50 | let version = best_version(versions.iter().map(AsRef::as_ref), r).unwrap(); 51 | install::exec(meta, true, Some(version.to_string())).expect("Install failed"); 52 | meta.set_version_mapping(alias, version.to_string()); 53 | } 54 | } 55 | } else { 56 | for alias in meta.list_alias() { 57 | let current = meta 58 | .get_version_mapping(alias.name.as_str()) 59 | .unwrap_or_else(|| DVM_VERSION_INVALID.to_string()); 60 | 61 | let latest = match VersionArg::from_str(alias.required.clone().as_str()).unwrap() { 62 | VersionArg::Exact(v) => v.to_string(), 63 | VersionArg::Range(v) => best_version(versions.iter().map(AsRef::as_ref), v).unwrap().to_string(), 64 | }; 65 | 66 | if current == latest { 67 | continue; 68 | } 69 | 70 | println!( 71 | "Upgrading {} from {} to {}", 72 | alias.name.bright_black(), 73 | current.bright_red(), 74 | latest.clone().bright_green() 75 | ); 76 | install::exec(meta, true, Some(latest.clone()))?; 77 | meta.set_version_mapping(alias.name, latest); 78 | 79 | println!("Upgrading {}", DVM_VERSION_CANARY.bright_black()); 80 | install::exec(meta, true, Some(DVM_VERSION_CANARY.to_string())).unwrap(); 81 | } 82 | 83 | println!("All aliases have been upgraded"); 84 | } 85 | 86 | Ok(()) 87 | } 88 | 89 | fn upgrade_self() -> Result<()> { 90 | cfg_if::cfg_if! { 91 | if #[cfg(windows)] { 92 | let url = "https://raw.githubusercontent.com/justjavac/dvm/main/install.ps1"; 93 | let script = tinyget::get(url).send()?; 94 | let script = script.as_str()?; 95 | let tmp = tempfile::tempdir()?; 96 | let tmp = tmp.path().join("install.ps1"); 97 | fs::write(&tmp, script)?; 98 | let mut cmd = std::process::Command::new("powershell"); 99 | cmd.arg("-ExecutionPolicy").arg("Bypass").arg("-File").arg(tmp); 100 | cmd.status()?; 101 | } else { 102 | let url = "https://raw.githubusercontent.com/justjavac/dvm/main/install.sh"; 103 | let script = tinyget::get(url).send()?; 104 | let script = script.as_str()?; 105 | let tmp = tempfile::tempdir()?; 106 | let tmp = tmp.path().join("install.sh"); 107 | fs::write(tmp, script)?; 108 | std::process::Command::new("bash").arg(script).status()?; 109 | } 110 | } 111 | 112 | Ok(()) 113 | } 114 | -------------------------------------------------------------------------------- /README_zh-cn.md: -------------------------------------------------------------------------------- 1 | ![dvm](https://socialify.git.ci/justjavac/dvm/image?description=1&font=Bitter&forks=1&issues=1&language=1&logo=https%3A%2F%2Fdeno.land%2Fimages%2Fartwork%2Fogdeno.png&name=1&owner=1&pattern=Circuit%20Board&pulls=1&stargazers=1&theme=Light) 2 | 3 | [English](https://github.com/qiuquanwu/dvm/) | 简体中文 4 | 5 | ## 安装 6 | 7 | 您可以使用下面的安装程序安装它,或者从[发布页](https://github.com/justjavac/dvm/releases)面下载源文件。 8 | 9 | **Shell 安装:** 10 | 11 | ```sh 12 | curl -fsSL https://deno.land/x/dvm/install.sh | sh 13 | ``` 14 | 15 | **PowerShell 安装:** 16 | 17 | ```powershell 18 | irm https://deno.land/x/dvm/install.ps1 | iex 19 | ``` 20 | 21 | **设置中文镜像:`dvm registry cn`**。 22 | 23 | ## 使用 24 | 25 | ```console 26 | ➜ ~ dvm --help 27 | Deno Version Manager - Easy way to manage multiple active deno versions. 28 | 29 | Usage: dvm.exe 30 | 31 | Commands: 32 | completions Generate shell completions 33 | info Show dvm info. 34 | install Install deno executable to the given version. [aliases: i, add] 35 | list List all installed versions [aliases: ls, ll, la] 36 | list-remote List all released versions [aliases: lr, ls-remote] 37 | uninstall Uninstall a given version [aliases: un, unlink, rm, remove] 38 | use Use a given version or a semver range or a alias to the range. 39 | alias Set or unset an alias 40 | activate Activate Dvm 41 | deactivate Deactivate Dvm 42 | doctor Fixing dvm specific environment variables and other issues 43 | upgrade Upgrade aliases to the latest version, use `self` to upgrade dvm itself 44 | exec Execute deno command with a specific deno version 45 | clean Clean dvm cache 46 | registry Change registry that dvm fetch from 47 | update Update remove version list local cache to the latest 48 | help Print this message or the help of the given subcommand(s) 49 | 50 | Options: 51 | -h, --help Print help information 52 | -V, --version Print version information 53 | 54 | EXAMPLE: 55 | dvm install 1.3.2 Install v1.3.2 release 56 | dvm install Install the latest available version 57 | dvm use 1.0.0 Use v1.0.0 release 58 | dvm use latest Use the latest alias that comes with dvm, equivalent to * 59 | dvm use canary Use the canary version of the Deno 60 | 61 | NOTE: 62 | To remove, delete, or uninstall dvm - just remove the `$DVM_DIR` folder (usually `~/.dvm`) 63 | ``` 64 | 65 | ### 验证 66 | 67 | 要验证 dvm 是否已安装,输入: 68 | 69 | ```bash 70 | dvm -V 71 | ``` 72 | 73 | 如果输出 dvm 的版本,则已经安装成功。 74 | 75 | ### 初始化 76 | 77 | 使用 `dvm` 将创建 `~/.dvm/` 文件夹,并且所有已安装的 deno 版本都将放入中 78 | `~/.dvm`文件夹之中。 79 | 80 | ``` 81 | ➜ ~ dvm 82 | Creating /Users/justjavac/.dvm 83 | ``` 84 | 85 | ### .dvmrc 86 | 87 | 你可以让 dvm 88 | 在当前目录生成配置文件,之后`dvm use`和`dvm install`如果没指定版本号就会优先采用当前目录下的配置文件 89 | 90 | 举个栗子:我们先设置 1.17.0 为默认版本 91 | 92 | ```bash 93 | dvm use --local 1.17.0 94 | ``` 95 | 96 | 然后假设另一个人拿到了你这个项目并且根目录里有你生成的 dvm 97 | 配置文件,并运行`dvm use` 98 | 99 | ```plain 100 | $ dvm use 101 | No version input detect, try to use version in .dvmrc file 102 | Using semver range: 1.17.0 103 | Writing to home folder config 104 | Now using deno 1.17.0 105 | ``` 106 | 107 | ## 举个例子 108 | 109 | ### 查看版本 110 | 111 | 列出所有安装的版本: 112 | 113 | ``` 114 | ➜ ~ dvm list 115 | * 0.1.0 116 | 0.1.1 117 | 0.1.2 118 | ``` 119 | 120 | 带(\*)的版本表示此版本是当前使用的版本。 121 | 122 | ### 切换版本 123 | 124 | ``` 125 | ➜ ~ dvm use 1.1.0 126 | now use deno 1.1.0 127 | ➜ ~ dvm use 1.2.0 128 | deno v1.2.0 is not installed. Use `dvm install 1.2.0` to install it first. 129 | ``` 130 | 131 | ## 兼容性 132 | 133 | - Shell 安装程序可以在带有 134 | [Windows Subsystem for Linux](https://docs.microsoft.com/en-us/windows/wsl/about), 135 | [MSYS](https://www.msys2.org) 或等效工具集的 Windows 上使用。 136 | 137 | ## 常见问题 138 | 139 | ### unzip is required 140 | 141 | 此项目需要依赖 [`unzip`](https://linux.die.net/man/1/unzip) 进行 Shell 安装。 142 | 143 | ```sh 144 | $ curl -fsSL https://deno.land/x/dvm/install.sh | sh 145 | Error: unzip is required to install dvm (see: https://github.com/justjavac/dvm#unzip-is-required). 146 | ``` 147 | 148 | **此问题何时出现?** 149 | 150 | 在运行 `install.sh` 过程中,会使用 `unzip` 提取 zip 文件。 151 | 152 | **如何解决?** 153 | 154 | - MacOs 使用 `brew install unzip` 安装 unzip。 155 | - Ubuntu,Debian 使用`apt-get install unzip -y` 安装 unzip。 156 | - CentOS 使用 `yum install -y unzip zip` 安装 unzip。 157 | 158 | ### Windows 平台 需要使用 Powershell 来运行 dvm 159 | 160 | 目前我们因为一些原因使用的是 Powershell profile 来设置环境变量, 所以 Powershell 161 | 是必需的. 162 | 163 | ## 开源协议 164 | 165 | Deno 版本管理器 (dvm) 遵循 MIT 开源协议。有关详细信息,请参阅 166 | [LICENSE](./LICENSE)。 167 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | name: ${{ matrix.kind }} ${{ matrix.os }} 8 | runs-on: ${{ matrix.os }} 9 | strategy: 10 | matrix: 11 | os: 12 | - ubuntu-latest 13 | - ubuntu-24.04-arm 14 | - macOS-13 # Intel 15 | - macOS-latest # Apple Silicon 16 | - windows-latest 17 | 18 | env: 19 | CARGO_INCREMENTAL: 0 20 | RUST_BACKTRACE: full 21 | CARGO_TERM_COLOR: always 22 | 23 | steps: 24 | - name: Clone repository 25 | uses: actions/checkout@v3 26 | 27 | - name: Install stable toolchain 28 | uses: actions-rs/toolchain@v1 29 | with: 30 | profile: minimal 31 | toolchain: stable 32 | override: true 33 | components: rustfmt, clippy 34 | 35 | - name: Install rust 36 | uses: hecrj/setup-rust-action@v1 37 | 38 | - name: Install clippy and rustfmt 39 | run: | 40 | rustup component add clippy 41 | rustup component add rustfmt 42 | 43 | - name: Log versions 44 | run: | 45 | rustc --version 46 | cargo --version 47 | 48 | - name: Cache 49 | uses: actions/cache@v3 50 | with: 51 | path: |- 52 | ~/.cargo/registry/index/ 53 | ~/.cargo/registry/cache/ 54 | ~/.cargo/git/db/ 55 | target/*/.* 56 | target/*/build 57 | target/*/deps 58 | key: 59 | ${{ matrix.config.os }}-${{ hashFiles('Cargo.lock') }} 60 | restore-keys: | 61 | ${{ matrix.config.os }}- 62 | 63 | - name: Run cargo fmt 64 | run: cargo fmt --all -- --check 65 | 66 | - name: Run cargo check 67 | run: cargo check --locked 68 | 69 | - name: Run cargo clippy 70 | run: cargo clippy -- -D warnings 71 | 72 | - name: Build release 73 | run: cargo build --release --locked 74 | 75 | - name: Run cargo test 76 | run: cargo test --locked 77 | 78 | - name: Test install from local file (bash) 79 | if: startsWith(matrix.os, 'ubuntu') || startsWith(matrix.os, 'macOS') 80 | run: ./install.sh ./target/release/dvm 81 | 82 | - name: Pre-release (linux) 83 | if: startsWith(matrix.os, 'ubuntu') 84 | run: zip -r dvm-x86_64-unknown-linux-gnu.zip dvm 85 | working-directory: target/release 86 | 87 | - name: Pre-release (linux-aarch64) 88 | if: startsWith(matrix.os, 'ubuntu') 89 | run: zip -r dvm-aarch64-unknown-linux-gnu.zip dvm 90 | working-directory: target/release 91 | 92 | - name: Pre-release (mac) 93 | if: matrix.os == 'macOS-13' 94 | run: zip -r dvm-x86_64-apple-darwin.zip dvm 95 | working-directory: target/release 96 | 97 | - name: Pre-release (aarch64-apple-darwin) 98 | if: matrix.os == 'macOS-latest' 99 | run: zip -r dvm-aarch64-apple-darwin.zip dvm 100 | working-directory: target/release 101 | 102 | - name: Pre-release (windows) 103 | if: startsWith(matrix.os, 'windows') 104 | run: | 105 | Compress-Archive -CompressionLevel Optimal -Force -Path target/release/dvm.exe -DestinationPath target/release/dvm-x86_64-pc-windows-msvc.zip 106 | 107 | - name: Release 108 | uses: softprops/action-gh-release@v1 109 | if: | 110 | startsWith(github.repository, 'justjavac') && 111 | startsWith(github.ref, 'refs/tags/') 112 | env: 113 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 114 | with: 115 | files: | 116 | target/release/dvm-x86_64-pc-windows-msvc.zip 117 | target/release/dvm-x86_64-unknown-linux-gnu.zip 118 | target/release/dvm-x86_64-apple-darwin.zip 119 | target/release/dvm-aarch64-apple-darwin.zip 120 | target/release/dvm-aarch64-unknown-linux-gnu.zip 121 | draft: true 122 | 123 | - name: Release DVM 124 | uses: justjavac/action-dvm-release@v1 125 | if: | 126 | startsWith(github.repository, 'justjavac') && 127 | startsWith(github.ref, 'refs/tags/') 128 | env: 129 | GITHUB_TOKEN: ${{ secrets.PERSONAL_TOKEN }} 130 | with: 131 | files: | 132 | target/release/dvm-x86_64-pc-windows-msvc.zip 133 | target/release/dvm-x86_64-unknown-linux-gnu.zip 134 | target/release/dvm-x86_64-apple-darwin.zip 135 | target/release/dvm-aarch64-apple-darwin.zip 136 | target/release/dvm-aarch64-unknown-linux-gnu.zip 137 | 138 | - name: Publish 139 | if: | 140 | startsWith(matrix.os, 'ubuntu') && 141 | startsWith(github.repository, 'justjavac') && 142 | startsWith(github.ref, 'refs/tags/') 143 | env: 144 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} 145 | run: | 146 | cargo publish 147 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![dvm](https://socialify.git.ci/justjavac/dvm/image?description=1&font=Bitter&forks=1&issues=1&language=1&logo=https%3A%2F%2Fdeno.land%2Fimages%2Fartwork%2Fogdeno.png&name=1&owner=1&pattern=Circuit%20Board&pulls=1&stargazers=1&theme=Light) 2 | 3 | English | [简体中文](./README_zh-cn.md) 4 | 5 | ## Installation 6 | 7 | You can install it using the installers below, or download a release binary from 8 | the [releases page](https://github.com/justjavac/dvm/releases). 9 | 10 | **With Shell:** 11 | 12 | ```sh 13 | curl -fsSL https://dvm.deno.dev | sh 14 | ``` 15 | 16 | **With PowerShell:** 17 | 18 | ```powershell 19 | irm https://dvm.deno.dev | iex 20 | ``` 21 | 22 | ## Usage 23 | 24 | ```console 25 | ➜ ~ dvm --help 26 | Deno Version Manager - Easy way to manage multiple active deno versions. 27 | 28 | Usage: dvm.exe 29 | 30 | Commands: 31 | completions Generate shell completions 32 | info Show dvm info. 33 | install Install deno executable to the given version. [aliases: i, add] 34 | list List all installed versions [aliases: ls, ll, la] 35 | list-remote List all released versions [aliases: lr, ls-remote] 36 | uninstall Uninstall a given version [aliases: un, unlink, rm, remove] 37 | use Use a given version or a semver range or a alias to the range. 38 | alias Set or unset an alias 39 | activate Activate Dvm 40 | deactivate Deactivate Dvm 41 | doctor Fixing dvm specific environment variables and other issues 42 | upgrade Upgrade aliases to the latest version, use `self` to upgrade dvm itself 43 | exec Execute deno command with a specific deno version 44 | clean Clean dvm cache 45 | registry Change registry that dvm fetch from 46 | update Update remove version list local cache to the latest 47 | help Print this message or the help of the given subcommand(s) 48 | 49 | Options: 50 | -h, --help Print help information 51 | -V, --version Print version information 52 | 53 | EXAMPLE: 54 | dvm install 1.3.2 Install v1.3.2 release 55 | dvm install Install the latest available version 56 | dvm use 1.0.0 Use v1.0.0 release 57 | dvm use latest Use the latest alias that comes with dvm, equivalent to * 58 | dvm use canary Use the canary version of the Deno 59 | 60 | NOTE: 61 | To remove, delete, or uninstall dvm - just remove the `$DVM_DIR` folder (usually `~/.dvm`) 62 | ``` 63 | 64 | ### Verify installation 65 | 66 | To verify that dvm has been installed, do: 67 | 68 | ```bash 69 | dvm -V 70 | ``` 71 | 72 | which should output dvm's version if the installation was successful. 73 | 74 | ### Initialisation 75 | 76 | Calling `dvm` will creates an `~/.dvm/` directory if it doesn't exist, and all 77 | installed versions of deno will put into `~/.dvm`. 78 | 79 | ``` 80 | ➜ ~ dvm 81 | Creating /Users/justjavac/.dvm 82 | ``` 83 | 84 | ### .dvmrc 85 | 86 | You can let dvm to writing config to current directery by add the `--local` flag 87 | to `dvm use`. Afterwards, `dvm use`, `dvm install` will use the version 88 | specified in the `.dvmrc` file if no version is supplied on the command line. 89 | 90 | For example, to make dvm default to the `1.17.0` release for the current 91 | directory: 92 | 93 | ```bash 94 | dvm use --local 1.17.0 95 | ``` 96 | 97 | Then when someone else with a copy of your project and run dvm: 98 | 99 | ```plain 100 | $ dvm use 101 | No version input detect, try to use version in .dvmrc file 102 | Using semver range: 1.17.0 103 | Writing to home folder config 104 | Now using deno 1.17.0 105 | ``` 106 | 107 | ## Example 108 | 109 | ### Listing versions 110 | 111 | List all installed versions: 112 | 113 | ``` 114 | ➜ ~ dvm list 115 | * 0.1.0 116 | 0.1.1 117 | 0.1.2 118 | ``` 119 | 120 | The version with a asterisk(`*`) means that this version is the version 121 | currently in use. 122 | 123 | ### Switching version 124 | 125 | ``` 126 | ➜ ~ dvm use 1.1.0 127 | now use deno 1.1.0 128 | ➜ ~ dvm use 1.2.0 129 | deno v1.2.0 is not installed. Use `dvm install 1.2.0` to install it first. 130 | ``` 131 | 132 | ## Compatibility 133 | 134 | - The Shell installer can be used on Windows with 135 | [Windows Subsystem for Linux](https://docs.microsoft.com/en-us/windows/wsl/about), 136 | [MSYS](https://www.msys2.org) or equivalent set of tools. 137 | 138 | ## Caveats 139 | 140 | ### unzip is **required** 141 | 142 | The program [`unzip`](https://linux.die.net/man/1/unzip) is a requirement for 143 | the Shell installer. 144 | 145 | ```sh 146 | $ curl -fsSL https://deno.land/x/dvm/install.sh | sh 147 | Error: unzip is required to install dvm (see: https://github.com/justjavac/dvm#unzip-is-required). 148 | ``` 149 | 150 | **When does this issue occur?** 151 | 152 | During the `install.sh` process, `unzip` is used to extract the zip archive. 153 | 154 | **How can this issue be fixed?** 155 | 156 | You can install unzip via `brew install unzip` on MacOS or 157 | `apt-get install unzip -y` on Linux(Ubuntu,Debian,Deepin). 158 | 159 | ### Powershell on Windows is **required** 160 | 161 | Currently, we use PowerShell profile to set environment variables due to various 162 | reasons, so it's required. 163 | 164 | ## License 165 | 166 | Deno Version Manager(dvm) is released under the MIT License. See the bundled 167 | [LICENSE](./LICENSE) file for details. 168 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use crate::configrc::rc_get_with_fix; 2 | use crate::consts::{DENO_EXE, DVM_CACHE_PATH_PREFIX, DVM_CANARY_PATH_PREFIX, DVM_CONFIGRC_KEY_DENO_VERSION}; 3 | use crate::version::VersionArg; 4 | use anyhow::Result; 5 | use dirs::home_dir; 6 | use semver::{Version, VersionReq}; 7 | use std::env; 8 | use std::fs::write; 9 | use std::io::{stdin, stdout, BufReader, Read, Write}; 10 | use std::path::PathBuf; 11 | use std::str::FromStr; 12 | use std::time; 13 | use std::time::{SystemTime, UNIX_EPOCH}; 14 | use tempfile::TempDir; 15 | 16 | pub fn run_with_spinner( 17 | message: String, 18 | finish_message: String, 19 | f: impl FnOnce(Box Result<()>>) -> Result<()>, 20 | ) -> Result<()> { 21 | let spinner = indicatif::ProgressBar::new_spinner().with_message(message); 22 | spinner.set_style( 23 | indicatif::ProgressStyle::default_spinner() 24 | .tick_chars("⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏ ") 25 | .template("{spinner:.green} {msg}") 26 | .unwrap(), 27 | ); 28 | spinner.enable_steady_tick(time::Duration::from_millis(100)); 29 | let result = f(Box::new({ 30 | let spinner = spinner.clone(); 31 | move |err| { 32 | spinner.finish_and_clear(); 33 | eprintln!("{}", err); 34 | std::process::exit(1); 35 | } 36 | })); 37 | spinner.finish_with_message(format!("{} in {:.2}s", finish_message, spinner.elapsed().as_secs_f32())); 38 | 39 | result 40 | } 41 | 42 | pub fn prompt_request(prompt: &str) -> bool { 43 | print!("{} (Y/n)", prompt); 44 | 45 | stdout().flush().unwrap(); 46 | let mut buffer = [0; 1]; 47 | let confirm = BufReader::new(stdin()) 48 | .read(&mut buffer) 49 | .ok() 50 | .map(|_| buffer[0] as char) 51 | .unwrap_or_else(|| 'y'); 52 | confirm == '\n' || confirm == '\r' || confirm.eq_ignore_ascii_case(&'y') 53 | } 54 | 55 | pub fn check_is_deactivated() -> bool { 56 | let mut home = dvm_root(); 57 | home.push(".deactivated"); 58 | home.exists() && home.is_file() 59 | } 60 | 61 | pub fn now() -> u128 { 62 | SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis() 63 | } 64 | 65 | pub fn update_stub(verison: &str) { 66 | let mut home = dvm_versions(); 67 | home.push(verison); 68 | if home.is_dir() { 69 | home.push(".dvmstub"); 70 | write(home, now().to_string()).unwrap(); 71 | } 72 | } 73 | 74 | pub fn is_exact_version(input: &str) -> bool { 75 | Version::parse(input).is_ok() 76 | } 77 | 78 | #[allow(dead_code)] 79 | pub fn is_valid_semver_range(input: &str) -> bool { 80 | VersionReq::parse(input).is_ok() 81 | } 82 | 83 | pub fn best_version<'a, T>(choices: T, required: VersionReq) -> Option 84 | where 85 | T: IntoIterator, 86 | { 87 | choices 88 | .into_iter() 89 | .filter_map(|v| { 90 | let version = Version::parse(v).ok()?; 91 | required.matches(&version).then_some(version) 92 | }) 93 | .max_by(|a, b| a.partial_cmp(b).unwrap()) 94 | } 95 | 96 | /// 97 | /// Find and load the dvmrc 98 | /// local -> user -> default 99 | pub fn load_dvmrc() -> VersionArg { 100 | rc_get_with_fix(DVM_CONFIGRC_KEY_DENO_VERSION) 101 | .map(|v| VersionArg::from_str(&v).unwrap()) 102 | .unwrap_or_else(|_| VersionArg::from_str("*").unwrap()) 103 | } 104 | 105 | pub fn dvm_root() -> PathBuf { 106 | env::var_os("DVM_DIR").map(PathBuf::from).unwrap_or_else(|| { 107 | // Note: on Windows, the $HOME environment variable may be set by users or by 108 | // third party software, but it is non-standard and should not be relied upon. 109 | home_dir() 110 | .map(|it| it.join(".dvm")) 111 | .unwrap_or_else(|| TempDir::new().unwrap().keep().join(".dvm")) 112 | }) 113 | } 114 | 115 | pub fn dvm_versions() -> PathBuf { 116 | let mut home = dvm_root(); 117 | home.push(DVM_CACHE_PATH_PREFIX); 118 | home 119 | } 120 | 121 | pub fn deno_canary_path() -> PathBuf { 122 | let dvm_dir = dvm_root().join(DVM_CANARY_PATH_PREFIX); 123 | dvm_dir.join(DENO_EXE) 124 | } 125 | 126 | /// CGQAQ: Put hardlink to executable to this file, 127 | /// and prepend this folder to env when dvm activated. 128 | pub fn deno_bin_path() -> PathBuf { 129 | let dvm_bin_dir = dvm_root().join("bin"); 130 | dvm_bin_dir.join(DENO_EXE) 131 | } 132 | 133 | pub fn deno_version_path(version: &Version) -> PathBuf { 134 | let dvm_dir = dvm_root().join(format!("{}/{}", DVM_CACHE_PATH_PREFIX, version)); 135 | dvm_dir.join(DENO_EXE) 136 | } 137 | 138 | #[inline] 139 | pub fn is_semver(version: &str) -> bool { 140 | Version::parse(version).is_ok() 141 | } 142 | 143 | #[inline] 144 | pub fn is_http_like_url(url: &str) -> bool { 145 | url.starts_with("http://") || url.starts_with("https://") 146 | } 147 | 148 | #[cfg(test)] 149 | mod tests { 150 | use super::*; 151 | use semver::VersionReq; 152 | 153 | #[test] 154 | fn test_best_version() { 155 | let versions = vec![ 156 | "0.8.5", 157 | "0.8.0", 158 | "0.9.0", 159 | "1.0.0", 160 | "1.0.0-alpha", 161 | "1.0.0-beta", 162 | "0.5.0", 163 | "2.0.0", 164 | ]; 165 | assert_eq!( 166 | best_version(versions.iter().map(AsRef::as_ref), VersionReq::parse("*").unwrap()), 167 | Some(Version::parse("2.0.0").unwrap()) 168 | ); 169 | assert_eq!( 170 | best_version(versions.iter().map(AsRef::as_ref), VersionReq::parse("^1").unwrap()), 171 | Some(Version::parse("1.0.0").unwrap()) 172 | ); 173 | assert_eq!( 174 | best_version(versions.iter().map(AsRef::as_ref), VersionReq::parse("~0.8").unwrap()), 175 | Some(Version::parse("0.8.5").unwrap()) 176 | ); 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /src/commands/use_version.rs: -------------------------------------------------------------------------------- 1 | use crate::commands::install; 2 | use crate::configrc::{rc_get_with_fix, rc_update}; 3 | use crate::consts::{ 4 | DVM_CONFIGRC_KEY_DENO_VERSION, DVM_CONFIGRC_KEY_REGISTRY_BINARY, DVM_VERSION_CANARY, DVM_VERSION_LATEST, 5 | DVM_VERSION_SYSTEM, REGISTRY_OFFICIAL, 6 | }; 7 | use crate::deno_bin_path; 8 | use crate::meta::DvmMeta; 9 | use crate::utils::{best_version, deno_canary_path, deno_version_path, prompt_request, run_with_spinner, update_stub}; 10 | use crate::utils::{is_exact_version, load_dvmrc}; 11 | use crate::version::remote_versions; 12 | use crate::version::{get_latest_version, VersionArg}; 13 | use anyhow::Result; 14 | use semver::Version; 15 | use std::fs; 16 | use std::path::Path; 17 | use std::process::Command; 18 | 19 | /// using a tag or a specific version 20 | pub fn exec(meta: &mut DvmMeta, version: Option, write_local: bool) -> Result<()> { 21 | let rc_binary_url = 22 | rc_get_with_fix(DVM_CONFIGRC_KEY_REGISTRY_BINARY).unwrap_or_else(|_| REGISTRY_OFFICIAL.to_string()); 23 | 24 | let version_req: VersionArg; 25 | if let Some(ref version) = version { 26 | if version == &DVM_VERSION_CANARY.to_string() { 27 | let canary_path = deno_canary_path(); 28 | if !canary_path.exists() { 29 | if prompt_request("deno canary is not installed. do you want to install it?") { 30 | install::exec(meta, true, Some(DVM_VERSION_CANARY.to_string())).unwrap(); 31 | use_canary_bin_path(write_local).unwrap(); 32 | } else { 33 | std::process::exit(1); 34 | } 35 | } 36 | 37 | use_canary_bin_path(write_local).unwrap(); 38 | return Ok(()); 39 | } else if version == &DVM_VERSION_SYSTEM.to_string() { 40 | std::fs::remove_file(deno_bin_path()).unwrap(); 41 | println!("Deno that was previously installed on your system will be activated now."); 42 | return Ok(()); 43 | } 44 | 45 | if is_exact_version(version) { 46 | version_req = VersionArg::Exact(Version::parse(version).unwrap()); 47 | } else if meta.has_alias(version) { 48 | version_req = meta.resolve_version_req(version); 49 | } else { 50 | // dvm will reject for using semver range directly now. 51 | eprintln!( 52 | "`{}` is not a valid semver version or tag and will not be used\ntype `dvm help` for more info", 53 | version 54 | ); 55 | std::process::exit(1); 56 | } 57 | } else { 58 | println!("No version input detect, try to use version in .dvmrc file"); 59 | version_req = load_dvmrc(); 60 | println!("Using semver range: {}", version_req); 61 | } 62 | 63 | let used_version = if version_req.to_string() == "*" { 64 | println!("Checking for latest version"); 65 | let version = get_latest_version(&rc_binary_url).expect("Get latest version failed"); 66 | println!("The latest version is v{}", version); 67 | version 68 | } else { 69 | match version_req { 70 | VersionArg::Exact(ref v) => v.clone(), 71 | VersionArg::Range(ref r) => { 72 | println!("Fetching version list"); 73 | let versions = remote_versions().expect("Fetching version list failed."); 74 | best_version(versions.iter().map(AsRef::as_ref), r.clone()).unwrap() 75 | } 76 | } 77 | }; 78 | 79 | let new_exe_path = deno_version_path(&used_version); 80 | 81 | if !new_exe_path.exists() { 82 | if prompt_request(format!("deno v{} is not installed. do you want to install it?", used_version).as_str()) { 83 | install::exec(meta, true, Some(used_version.to_string())).unwrap(); 84 | let temp = version_req.to_string(); 85 | let version = version.as_ref().unwrap_or(&temp); 86 | if !is_exact_version(version) { 87 | meta.set_version_mapping(version.clone(), used_version.to_string()); 88 | } 89 | } else { 90 | std::process::exit(1); 91 | } 92 | } 93 | 94 | use_this_bin_path( 95 | &new_exe_path, 96 | &used_version, 97 | version.unwrap_or_else(|| DVM_VERSION_LATEST.to_string()), 98 | write_local, 99 | )?; 100 | update_stub(used_version.to_string().as_str()); 101 | Ok(()) 102 | } 103 | 104 | pub fn use_canary_bin_path(local: bool) -> Result<()> { 105 | run_with_spinner("Processing".to_string(), "Now using deno canary".to_string(), |_| { 106 | let canary_dir = deno_canary_path(); 107 | 108 | if !canary_dir.exists() { 109 | eprintln!("Canary dir not found, will not be used"); 110 | std::process::exit(1); 111 | } 112 | 113 | let bin_path = deno_bin_path(); 114 | if !bin_path.parent().unwrap().exists() { 115 | fs::create_dir_all(bin_path.parent().unwrap()).unwrap(); 116 | } 117 | if bin_path.exists() { 118 | fs::remove_file(&bin_path)?; 119 | } 120 | fs::hard_link(&canary_dir, &bin_path)?; 121 | 122 | rc_update(local, DVM_CONFIGRC_KEY_DENO_VERSION, DVM_VERSION_CANARY)?; 123 | 124 | Ok(()) 125 | }) 126 | } 127 | 128 | pub fn use_this_bin_path(exe_path: &Path, version: &Version, raw_version: String, local: bool) -> Result<()> { 129 | run_with_spinner("Processing".to_string(), format!("Now using deno {}", &version), |_| { 130 | check_exe(exe_path, version)?; 131 | 132 | let bin_path = deno_bin_path(); 133 | if !bin_path.parent().unwrap().exists() { 134 | fs::create_dir_all(bin_path.parent().unwrap()).unwrap(); 135 | } 136 | if bin_path.exists() { 137 | fs::remove_file(&bin_path)?; 138 | } 139 | fs::hard_link(exe_path, &bin_path)?; 140 | 141 | rc_update(local, DVM_CONFIGRC_KEY_DENO_VERSION, raw_version.as_str())?; 142 | Ok(()) 143 | }) 144 | } 145 | 146 | fn check_exe(exe_path: &Path, expected_version: &Version) -> Result<()> { 147 | let output = Command::new(exe_path) 148 | .arg("-V") 149 | .stderr(std::process::Stdio::inherit()) 150 | .output()?; 151 | let stdout = String::from_utf8(output.stdout)?; 152 | assert!(output.status.success()); 153 | assert_eq!(stdout.trim(), format!("deno {}", expected_version)); 154 | Ok(()) 155 | } 156 | -------------------------------------------------------------------------------- /src/configrc.rs: -------------------------------------------------------------------------------- 1 | use crate::consts::{ 2 | DVM_CONFIGRC_FILENAME, DVM_CONFIGRC_KEY_DENO_VERSION, DVM_CONFIGRC_KEY_REGISTRY_BINARY, 3 | DVM_CONFIGRC_KEY_REGISTRY_VERSION, 4 | }; 5 | use crate::consts::{REGISTRY_LIST_OFFICIAL, REGISTRY_OFFICIAL}; 6 | use std::fs; 7 | use std::io; 8 | 9 | /// check global rc file exists 10 | pub fn rc_exists() -> bool { 11 | let dir = dirs::home_dir() 12 | .map(|it| it.join(DVM_CONFIGRC_FILENAME)) 13 | .unwrap_or_default(); 14 | fs::metadata(dir).is_ok() 15 | } 16 | 17 | /// init user-wide rc file 18 | pub fn rc_init() -> io::Result<()> { 19 | rc_update(false, DVM_CONFIGRC_KEY_REGISTRY_BINARY, REGISTRY_OFFICIAL)?; 20 | rc_update(false, DVM_CONFIGRC_KEY_REGISTRY_VERSION, REGISTRY_LIST_OFFICIAL) 21 | } 22 | 23 | /// fix missing rc properties 24 | pub fn rc_fix() -> io::Result<()> { 25 | if !rc_exists() { 26 | rc_init()?; 27 | } else { 28 | if !rc_has(DVM_CONFIGRC_KEY_REGISTRY_BINARY) { 29 | rc_update(false, DVM_CONFIGRC_KEY_REGISTRY_BINARY, REGISTRY_OFFICIAL)?; 30 | } 31 | if !rc_has(DVM_CONFIGRC_KEY_REGISTRY_VERSION) { 32 | rc_update(false, DVM_CONFIGRC_KEY_REGISTRY_VERSION, REGISTRY_LIST_OFFICIAL)?; 33 | } 34 | if !rc_has(DVM_CONFIGRC_KEY_DENO_VERSION) { 35 | rc_update(false, DVM_CONFIGRC_KEY_DENO_VERSION, "latest")?; 36 | } 37 | } 38 | 39 | Ok(()) 40 | } 41 | 42 | /// check if key exists in rc file 43 | pub fn rc_has(key: &str) -> bool { 44 | let Ok(content) = rc_content_cascade() else { 45 | return false; 46 | }; 47 | 48 | content 49 | .lines() 50 | .filter(|it| it.contains('=')) 51 | .map(|it| { 52 | let mut it = it.split('='); 53 | (it.next().unwrap().trim(), it.next().unwrap().trim()) 54 | }) 55 | .any(|(k, _)| k == key) 56 | } 57 | 58 | /// get value by key from configrc 59 | /// first try to get from current folder 60 | /// if not found, try to get from home folder 61 | /// if not found, return Err 62 | pub fn rc_get(key: &str) -> io::Result { 63 | if !rc_exists() { 64 | rc_init()?; 65 | } 66 | 67 | let content = rc_content_cascade()?; 68 | let config = rc_parse(content.as_str()); 69 | 70 | config 71 | .iter() 72 | .find_map(|(k, v)| if k == &key { Some(v.to_string()) } else { None }) 73 | .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "key not found")) 74 | } 75 | 76 | /// get value by key from configuration with a possible fix 77 | /// first try to get from current folder 78 | /// if not found, try to get from home folder 79 | /// if not found, try to the fix the missing properties. 80 | /// and then try to get this key's value again without the fix 81 | pub fn rc_get_with_fix(key: &str) -> io::Result { 82 | // always return the error which is from `rc_get` fn 83 | rc_get(key).or_else(|err| rc_fix().and_then(|_| rc_get(key)).map_err(|_| err)) 84 | } 85 | 86 | /// update the config file key with the new value 87 | /// create the file if it doesn't exist 88 | /// create key value pair if it doesn't exist 89 | pub fn rc_update(is_local: bool, key: &str, value: &str) -> io::Result<()> { 90 | let (config_path, content) = rc_content(is_local); 91 | 92 | let _content; 93 | let mut config = if let Ok(c) = content { 94 | _content = c; 95 | rc_parse(_content.as_str()) 96 | } else { 97 | Vec::new() 98 | }; 99 | 100 | let idx = config.iter().position(|(k, _)| k == &key); 101 | if let Some(idx) = idx { 102 | config[idx].1 = value; 103 | } else { 104 | config.push((key, value)); 105 | } 106 | 107 | let config = config 108 | .iter() 109 | .map(|(k, v)| format!("{}={}", k, v)) 110 | .collect::>() 111 | .join("\n"); 112 | fs::write(config_path, config) 113 | } 114 | 115 | /// remove key value pair from config file 116 | #[allow(dead_code)] 117 | pub fn rc_remove(is_local: bool, key: &str) -> io::Result<()> { 118 | let (config_path, content) = rc_content(is_local); 119 | let Ok(content) = content else { 120 | // no need to remove 121 | return Ok(()); 122 | }; 123 | let config = rc_parse(content.as_str()); 124 | let config = config.iter().filter(|(k, _)| k != &key).collect::>(); 125 | 126 | let config = config 127 | .iter() 128 | .map(|(k, v)| format!("{}={}", k, v)) 129 | .collect::>() 130 | .join("\n"); 131 | fs::write(config_path, config) 132 | } 133 | 134 | fn rc_parse(content: &str) -> Vec<(&str, &str)> { 135 | let config = content 136 | .lines() 137 | // throw away non key value pair 138 | .filter(|it| it.contains('=')) 139 | .map(|line| { 140 | let mut parts = line.splitn(2, '='); 141 | let k = parts.next().unwrap(); 142 | let v = parts.next().unwrap_or(""); 143 | (k, v) 144 | }) 145 | .collect::>(); 146 | config 147 | } 148 | 149 | fn rc_content(is_local: bool) -> (std::path::PathBuf, io::Result) { 150 | let config_path = if is_local { 151 | std::path::PathBuf::from(DVM_CONFIGRC_FILENAME) 152 | } else { 153 | dirs::home_dir() 154 | .ok_or_else(|| io::Error::from(io::ErrorKind::NotFound)) 155 | .unwrap() 156 | .join(DVM_CONFIGRC_FILENAME) 157 | }; 158 | 159 | (config_path.clone(), fs::read_to_string(config_path)) 160 | } 161 | 162 | fn rc_content_cascade() -> io::Result { 163 | rc_content(false).1.or_else(|_| rc_content(true).1) 164 | } 165 | 166 | /// remove all key value pair that ain't supported by dvm from config file 167 | pub fn rc_clean(is_local: bool) -> io::Result<()> { 168 | if !rc_exists() { 169 | rc_init()?; 170 | } 171 | 172 | let (config_path, content) = rc_content(is_local); 173 | let content = if let Ok(content) = content { 174 | content 175 | } else { 176 | // if file not found, just return Ok, 'cause it's not needed to be cleaned 177 | return Ok(()); 178 | }; 179 | 180 | let config = rc_parse(content.as_str()); 181 | let config = config 182 | .iter() 183 | .filter(|(k, _)| { 184 | k == &DVM_CONFIGRC_KEY_DENO_VERSION 185 | || k == &DVM_CONFIGRC_KEY_REGISTRY_BINARY 186 | || k == &DVM_CONFIGRC_KEY_REGISTRY_VERSION 187 | }) 188 | .collect::>(); 189 | 190 | let config = config 191 | .iter() 192 | .map(|(k, v)| format!("{}={}", k, v)) 193 | .collect::>() 194 | .join("\n"); 195 | fs::write(config_path, config) 196 | } 197 | 198 | /// clear and delete the rc file 199 | /// if is_local is true, delete the local rc file 200 | /// if is_local is false, delete the global(user-wide) rc file 201 | #[allow(dead_code)] 202 | pub fn rc_unlink(is_local: bool) -> io::Result<()> { 203 | if is_local { 204 | fs::remove_file(DVM_CONFIGRC_FILENAME) 205 | } else { 206 | let home_dir = dirs::home_dir().ok_or_else(|| io::Error::from(io::ErrorKind::NotFound))?; 207 | let rc_file = home_dir.join(DVM_CONFIGRC_FILENAME); 208 | fs::remove_file(rc_file) 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /src/version.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 justjavac. All rights reserved. MIT license. 2 | use crate::configrc::rc_get_with_fix; 3 | use crate::consts::{ 4 | DVM_CACHE_PATH_PREFIX, DVM_CACHE_REMOTE_PATH, DVM_CONFIGRC_KEY_REGISTRY_VERSION, REGISTRY_LATEST_CANARY_PATH, 5 | REGISTRY_LATEST_RELEASE_PATH, 6 | }; 7 | use crate::utils::{dvm_root, is_exact_version, is_semver, run_with_spinner}; 8 | use anyhow::Result; 9 | use colored::Colorize; 10 | use json_minimal::Json; 11 | use semver::{Version, VersionReq}; 12 | use serde::{Deserialize, Serialize}; 13 | use std::fmt::Formatter; 14 | use std::fs::read_dir; 15 | use std::io::Write; 16 | use std::path::{Path, PathBuf}; 17 | use std::process::{Command, Stdio}; 18 | use std::str::FromStr; 19 | use std::string::String; 20 | 21 | pub const DVM: &str = env!("CARGO_PKG_VERSION"); 22 | 23 | #[derive(Debug, Serialize, Deserialize)] 24 | pub struct Cached { 25 | versions: Vec, 26 | time: String, 27 | } 28 | 29 | #[derive(Debug, Eq, PartialEq, Clone)] 30 | pub enum VersionArg { 31 | Exact(Version), 32 | Range(VersionReq), 33 | } 34 | 35 | impl std::fmt::Display for VersionArg { 36 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 37 | match self { 38 | VersionArg::Exact(version) => f.write_str(version.to_string().as_str()), 39 | VersionArg::Range(version) => f.write_str(version.to_string().as_str()), 40 | } 41 | } 42 | } 43 | 44 | impl FromStr for VersionArg { 45 | type Err = (); 46 | 47 | fn from_str(s: &str) -> std::result::Result { 48 | if is_exact_version(s) { 49 | Version::parse(s).map(VersionArg::Exact).map_err(|_| ()) 50 | } else { 51 | VersionReq::parse(s) 52 | .map(VersionArg::Range) 53 | .or_else(|_| VersionReq::parse("*").map(VersionArg::Range).map_err(|_| ())) 54 | } 55 | } 56 | } 57 | 58 | pub fn current_version() -> Option { 59 | match Command::new("deno").arg("-V").stderr(Stdio::inherit()).output() { 60 | Ok(output) => { 61 | assert!(output.status.success()); 62 | match String::from_utf8(output.stdout) { 63 | Ok(stdout) => Some(stdout.trim()[5..].to_string()), 64 | Err(_) => None, 65 | } 66 | } 67 | Err(_) => None, 68 | } 69 | } 70 | 71 | pub fn local_versions() -> Vec { 72 | let mut v: Vec = Vec::new(); 73 | 74 | if let Ok(entries) = read_dir(dvm_root().join(Path::new(DVM_CACHE_PATH_PREFIX))) { 75 | for entry in entries.flatten() { 76 | if let Ok(file_type) = entry.file_type() { 77 | if file_type.is_dir() { 78 | let file_name = entry.file_name().into_string().unwrap(); 79 | if is_semver(&file_name) { 80 | v.push(file_name); 81 | } 82 | } 83 | } 84 | } 85 | } 86 | 87 | v 88 | } 89 | 90 | #[inline] 91 | pub fn cached_remote_versions_location() -> PathBuf { 92 | dvm_root().join(Path::new(DVM_CACHE_REMOTE_PATH)) 93 | } 94 | 95 | pub fn cache_remote_versions() -> Result<()> { 96 | run_with_spinner( 97 | "fetching remote versions...".to_string(), 98 | "updated remote versions".to_string(), 99 | |_| { 100 | let cached_remote_versions_location = cached_remote_versions_location(); 101 | 102 | let remote_versions_url = rc_get_with_fix(DVM_CONFIGRC_KEY_REGISTRY_VERSION)?; 103 | let remote_versions = tinyget::get(remote_versions_url).send()?.as_str()?.to_owned(); 104 | std::fs::write(cached_remote_versions_location, remote_versions).map_err(|e| anyhow::anyhow!(e)) 105 | }, 106 | ) 107 | } 108 | 109 | /// use cached remote versions if exists, otherwise ask user to fetch remote versions 110 | pub fn remote_versions() -> Result> { 111 | if !is_versions_cache_exists() { 112 | println!("It seems that you have not updated the remote version cache, please run `dvm update` first."); 113 | print!("Do you want to update the remote version cache now? [Y/n]"); 114 | std::io::stdout().lock().flush().unwrap(); 115 | let mut input = String::new(); 116 | std::io::stdin().read_line(&mut input)?; 117 | if input.trim().to_lowercase() == "y" || input.trim().is_empty() { 118 | cache_remote_versions()?; 119 | } else { 120 | println!("Please run `dvm update` to update the remote version cache."); 121 | std::process::exit(1); 122 | } 123 | } 124 | 125 | let cached_remote_versions_location = cached_remote_versions_location(); 126 | let cached_content = std::fs::read_to_string(cached_remote_versions_location)?; 127 | 128 | let json = match Json::parse(cached_content.as_bytes()) { 129 | Ok(json) => json, 130 | Err(e) => { 131 | eprintln!("Failed to parse remote versions cache. location: {}", e.0); 132 | eprintln!("Error: {}", e.1.red()); 133 | eprintln!("The remote version cache is corrupted, please run `dvm update` to update the remote version cache."); 134 | std::process::exit(1); 135 | } 136 | }; 137 | 138 | let mut result: Vec = Vec::new(); 139 | 140 | let Some(cli_versions) = json.get("cli") else { 141 | eprintln!("The remote version cache is corrupted(missing cli property), please run `dvm update` to update the remote version cache."); 142 | std::process::exit(1); 143 | }; 144 | 145 | if let Json::OBJECT { name: _, value } = cli_versions { 146 | if let Json::ARRAY(list) = value.unbox() { 147 | for item in list { 148 | if let Json::STRING(val) = item.unbox() { 149 | result.push(val.replace('v', "").to_string()); 150 | } 151 | } 152 | } 153 | } 154 | Ok(result) 155 | } 156 | 157 | pub fn is_versions_cache_exists() -> bool { 158 | let remote_versions_location = cached_remote_versions_location(); 159 | remote_versions_location.exists() 160 | } 161 | 162 | pub fn get_latest_version(registry: &str) -> Result { 163 | let response = tinyget::get(format!("{}{}", registry, REGISTRY_LATEST_RELEASE_PATH)).send()?; 164 | 165 | let body = response.as_str()?; 166 | let v = body.trim().replace('v', ""); 167 | Ok(Version::parse(&v).unwrap()) 168 | } 169 | 170 | pub fn get_latest_canary(registry: &str) -> Result { 171 | let response = tinyget::get(format!("{}{}", registry, REGISTRY_LATEST_CANARY_PATH)).send()?; 172 | 173 | let body = response.as_str()?; 174 | let v = body.trim().replace('v', ""); 175 | Ok(v) 176 | } 177 | 178 | pub fn version_req_parse(version: &str) -> VersionReq { 179 | VersionReq::parse(version).unwrap_or_else(|_| panic!("version is invalid: {}", version)) 180 | } 181 | 182 | pub fn find_max_matching_version<'a, I>(version_req_str: &str, iterable: I) -> Result> 183 | where 184 | I: IntoIterator, 185 | { 186 | let version_req = version_req_parse(version_req_str); 187 | Ok( 188 | iterable 189 | .into_iter() 190 | .filter_map(|s| Version::parse(s).ok()) 191 | .filter(|s| version_req.matches(s)) 192 | .max(), 193 | ) 194 | } 195 | -------------------------------------------------------------------------------- /src/cli.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | use clap::builder::PossibleValue; 4 | use clap::{Parser, ValueEnum}; 5 | use clap_complete::Shell; 6 | use clap_derive::{Parser, Subcommand}; 7 | 8 | use crate::commands; 9 | use crate::consts::{ 10 | AFTER_HELP, COMPLETIONS_HELP, REGISTRY_CN, REGISTRY_LIST_CN, REGISTRY_LIST_OFFICIAL, REGISTRY_NAME_CN, 11 | REGISTRY_NAME_OFFICIAL, REGISTRY_OFFICIAL, 12 | }; 13 | use crate::meta::DvmMeta; 14 | 15 | pub fn cli_parse(meta: &mut DvmMeta) -> Result { 16 | let args: Vec = env::args().collect(); 17 | if args.len() > 1 && args[1] == "exec" { 18 | if args.len() > 2 { 19 | let version: Option; 20 | let exec_args: Vec; 21 | if args[2] == "--version" || args[2] == "-V" { 22 | if args.len() > 3 { 23 | version = Some(args[3].clone()); 24 | exec_args = args[4..].to_vec(); 25 | } else { 26 | eprintln!("A version should be followed after {}", args[2]); 27 | std::process::exit(1) 28 | } 29 | } else if args[2].starts_with("--version=") || args[2].starts_with("-V=") { 30 | version = Some( 31 | args[2] 32 | .trim_start_matches("-V=") 33 | .trim_start_matches("--version=") 34 | .to_string(), 35 | ); 36 | exec_args = args[3..].to_vec(); 37 | } else { 38 | version = None; 39 | exec_args = args[2..].to_vec(); 40 | } 41 | commands::exec::exec(meta, version, exec_args).unwrap(); 42 | } else { 43 | commands::exec::exec(meta, None, vec![]).unwrap(); 44 | } 45 | return Err(()); 46 | } 47 | 48 | Ok(Cli::parse()) 49 | } 50 | 51 | #[derive(Parser)] 52 | #[clap(version, about)] 53 | #[clap(after_help = AFTER_HELP)] 54 | #[clap(propagate_version = true)] 55 | pub struct Cli { 56 | #[clap(subcommand)] 57 | pub command: Commands, 58 | } 59 | 60 | #[derive(Subcommand)] 61 | pub enum Commands { 62 | #[clap(about = "Generate shell completions")] 63 | #[clap(long_about = COMPLETIONS_HELP)] 64 | Completions { 65 | #[arg(value_enum)] 66 | shell: Shell, 67 | }, 68 | 69 | #[clap(about = "Show dvm info.")] 70 | Info, 71 | 72 | #[clap(about = "Install deno executable to the given version.")] 73 | #[clap(visible_aliases = & ["i", "add"])] 74 | #[clap(disable_version_flag = true)] 75 | Install { 76 | #[clap(long, help = "Only install to local, but not use")] 77 | no_use: bool, 78 | #[clap(help = "The version to install")] 79 | version: Option, 80 | }, 81 | 82 | #[clap(about = "List all installed versions")] 83 | #[clap(visible_aliases = & ["ls", "ll", "la"])] 84 | List, 85 | 86 | #[clap(about = "List all released versions")] 87 | #[clap(visible_aliases = & ["lr", "ls-remote"])] 88 | ListRemote, 89 | 90 | #[clap(about = "Uninstall a given version")] 91 | #[clap(visible_aliases = & ["un", "unlink", "rm", "remove"])] 92 | #[clap(disable_version_flag = true)] 93 | Uninstall { 94 | #[clap(help = "The version to uninstall")] 95 | version: Option, 96 | }, 97 | 98 | #[clap(about = "Use a given version or a semver range or a alias to the range.")] 99 | #[clap(disable_version_flag = true)] 100 | Use { 101 | #[clap(help = "The version, semver range or alias to use")] 102 | version: Option, 103 | 104 | #[clap( 105 | short = 'L', 106 | long = "write-local", 107 | help = "Writing the version to the .dvmrc file of the current directory if present" 108 | )] 109 | write_local: bool, 110 | }, 111 | 112 | #[clap(about = "Set or unset an alias")] 113 | Alias { 114 | #[clap(subcommand)] 115 | command: AliasCommands, 116 | }, 117 | 118 | #[clap(about = "Activate Dvm")] 119 | Activate, 120 | #[clap(about = "Deactivate Dvm")] 121 | Deactivate, 122 | 123 | #[clap(about = "Fixing dvm specific environment variables and other issues")] 124 | Doctor, 125 | 126 | #[clap(about = "Upgrade aliases to the latest version, use `self` to upgrade dvm itself")] 127 | Upgrade { 128 | #[clap(help = "The alias to upgrade, use `self` to upgrade `dvm` itself, upgrade all aliases if not present")] 129 | alias: Option, 130 | }, 131 | 132 | #[clap(about = "Execute deno command with a specific deno version")] 133 | #[clap(disable_version_flag = true)] 134 | Exec { 135 | #[clap(help = "The command given to deno")] 136 | command: Option, 137 | 138 | #[clap(help = "The version to use", long, short)] 139 | version: Option, 140 | }, 141 | 142 | #[clap(about = "Clean dvm cache")] 143 | Clean, 144 | 145 | #[clap(about = "Change registry that dvm fetch from")] 146 | Registry { 147 | #[clap(subcommand)] 148 | command: RegistryCommands, 149 | }, 150 | 151 | #[clap(about = "Update remove version list local cache to the latest")] 152 | Update, 153 | } 154 | 155 | #[derive(Subcommand)] 156 | pub enum AliasCommands { 157 | #[clap(about = "Set an alias")] 158 | Set { 159 | #[clap(help = "Alias name to set")] 160 | name: String, 161 | #[clap(help = "Alias content")] 162 | content: String, 163 | }, 164 | 165 | #[clap(about = "Unset an alias")] 166 | Unset { 167 | #[clap(help = "Alias name to unset")] 168 | name: String, 169 | }, 170 | 171 | #[clap(about = "List all aliases")] 172 | List, 173 | } 174 | 175 | #[derive(Subcommand)] 176 | pub enum RegistryCommands { 177 | #[clap(about = "List predefined registries")] 178 | List, 179 | 180 | #[clap(about = "Show current binary registry and version registry")] 181 | Show, 182 | 183 | #[clap(about = "Set registry to one of predefined registries")] 184 | Set { 185 | predefined: RegistryPredefined, 186 | 187 | #[clap( 188 | long = "write-local", 189 | short = 'L', 190 | help = "Write to current directory .dvmrc file instead of global(user-wide) config" 191 | )] 192 | write_local: bool, 193 | }, 194 | 195 | #[clap(about = "Binary registry operations")] 196 | Binary { 197 | #[clap(subcommand)] 198 | sub: BinaryRegistryCommands, 199 | }, 200 | 201 | #[clap(about = "Version registry operations")] 202 | Version { 203 | #[clap(subcommand)] 204 | sub: VersionRegistryCommands, 205 | }, 206 | } 207 | 208 | #[derive(Subcommand)] 209 | pub enum BinaryRegistryCommands { 210 | #[clap(about = "Show current binary registry")] 211 | Show, 212 | #[clap(about = "Set binary registry to one of predefined registries")] 213 | Set { 214 | custom: String, 215 | #[clap( 216 | long = "write-local", 217 | short = 'L', 218 | help = "Write to current directory .dvmrc file instead of global(user-wide) config" 219 | )] 220 | write_local: bool, 221 | }, 222 | } 223 | 224 | #[derive(Subcommand)] 225 | pub enum VersionRegistryCommands { 226 | #[clap(about = "Show current version registry")] 227 | Show, 228 | #[clap(about = "Set version registry to one of predefined registries")] 229 | Set { 230 | custom: String, 231 | #[clap( 232 | long = "write-local", 233 | short = 'L', 234 | help = "Write to current directory .dvmrc file instead of global(user-wide) config" 235 | )] 236 | write_local: bool, 237 | }, 238 | } 239 | 240 | #[derive(Clone)] 241 | pub enum RegistryPredefined { 242 | Official, 243 | CN, 244 | } 245 | 246 | impl ValueEnum for RegistryPredefined { 247 | fn value_variants<'a>() -> &'a [Self] { 248 | &[RegistryPredefined::Official, RegistryPredefined::CN] 249 | } 250 | 251 | fn from_str(input: &str, ignore_case: bool) -> Result { 252 | if (ignore_case && REGISTRY_NAME_OFFICIAL == input.to_ascii_lowercase()) || REGISTRY_NAME_OFFICIAL == input { 253 | Ok(RegistryPredefined::Official) 254 | } else if (ignore_case && REGISTRY_NAME_CN == input.to_ascii_lowercase()) || REGISTRY_NAME_CN == input { 255 | Ok(RegistryPredefined::CN) 256 | } else { 257 | Err(format!("{} is not a valid registry", input)) 258 | } 259 | } 260 | 261 | fn to_possible_value(&self) -> Option { 262 | Some(PossibleValue::new(match self { 263 | RegistryPredefined::Official => REGISTRY_NAME_OFFICIAL, 264 | RegistryPredefined::CN => REGISTRY_NAME_CN, 265 | })) 266 | } 267 | } 268 | 269 | impl RegistryPredefined { 270 | pub fn get_version_url(&self) -> String { 271 | match self { 272 | RegistryPredefined::Official => REGISTRY_LIST_OFFICIAL, 273 | RegistryPredefined::CN => REGISTRY_LIST_CN, 274 | } 275 | .to_string() 276 | } 277 | 278 | pub fn get_binary_url(&self) -> String { 279 | match self { 280 | RegistryPredefined::Official => REGISTRY_OFFICIAL, 281 | RegistryPredefined::CN => REGISTRY_CN, 282 | } 283 | .to_string() 284 | } 285 | } 286 | -------------------------------------------------------------------------------- /src/commands/install.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. 2 | // Copyright 2020-2022 justjavac. All rights reserved. MIT license. 3 | use super::use_version; 4 | use crate::configrc::rc_get_with_fix; 5 | use crate::consts::{ 6 | DVM_CACHE_PATH_PREFIX, DVM_CANARY_PATH_PREFIX, DVM_CONFIGRC_KEY_REGISTRY_BINARY, DVM_VERSION_CANARY, 7 | DVM_VERSION_LATEST, REGISTRY_OFFICIAL, 8 | }; 9 | use crate::meta::DvmMeta; 10 | use crate::utils::{deno_canary_path, deno_version_path, dvm_root}; 11 | use crate::version::get_latest_canary; 12 | use anyhow::Result; 13 | use cfg_if::cfg_if; 14 | use semver::Version; 15 | use std::fs; 16 | use std::path::{Path, PathBuf}; 17 | use std::process::Command; 18 | use std::string::String; 19 | 20 | cfg_if! { 21 | if #[cfg(windows)] { 22 | const ARCHIVE_NAME: &str = "deno-x86_64-pc-windows-msvc.zip"; 23 | } else if #[cfg(all(target_os = "macos", target_arch = "aarch64"))] { 24 | const ARCHIVE_NAME: &str = "deno-aarch64-apple-darwin.zip"; 25 | } else if #[cfg(all(target_os = "macos", target_arch = "x86_64"))] { 26 | const ARCHIVE_NAME: &str = "deno-x86_64-apple-darwin.zip"; 27 | } else if #[cfg(all(target_os = "linux", target_arch = "x86_64"))] { 28 | const ARCHIVE_NAME: &str = "deno-x86_64-unknown-linux-gnu.zip"; 29 | } else if #[cfg(all(target_os = "linux", target_arch = "aarch64"))] { 30 | const ARCHIVE_NAME: &str = "deno-aarch64-unknown-linux-gnu.zip"; 31 | } 32 | } 33 | 34 | pub fn exec(_: &DvmMeta, no_use: bool, version: Option) -> Result<()> { 35 | let binary_registry_url = 36 | rc_get_with_fix(DVM_CONFIGRC_KEY_REGISTRY_BINARY).unwrap_or_else(|_| REGISTRY_OFFICIAL.to_string()); 37 | 38 | if let Some(version) = version.clone() { 39 | if version == *DVM_VERSION_CANARY { 40 | let canary_path = deno_canary_path(); 41 | std::fs::create_dir_all(canary_path.parent().unwrap())?; 42 | let hash = get_latest_canary(&binary_registry_url).expect("Failed to get latest canary"); 43 | let data = download_canary(&binary_registry_url, &hash)?; 44 | unpack_canary(data)?; 45 | 46 | if !no_use { 47 | use_version::use_canary_bin_path(false).unwrap(); 48 | } 49 | 50 | return Ok(()); 51 | } 52 | } 53 | 54 | let install_version = match version { 55 | Some(ref passed_version) => { 56 | Version::parse(passed_version).map_err(|_| anyhow::format_err!("Invalid semver {}", passed_version))? 57 | } 58 | None => get_latest_version(&binary_registry_url)?, 59 | }; 60 | 61 | let exe_path = deno_version_path(&install_version); 62 | 63 | if exe_path.exists() { 64 | println!("Version v{} is already installed", install_version); 65 | } else { 66 | let archive_data = download_package( 67 | &compose_url_to_exec(&binary_registry_url, &install_version), 68 | &install_version, 69 | )?; 70 | unpack(archive_data, &install_version)?; 71 | } 72 | 73 | if !no_use { 74 | use_version::use_this_bin_path( 75 | &exe_path, 76 | &install_version, 77 | version.unwrap_or_else(|| DVM_VERSION_LATEST.to_string()), 78 | false, 79 | )?; 80 | } 81 | 82 | Ok(()) 83 | } 84 | 85 | fn get_latest_version(registry: &str) -> Result { 86 | println!("Checking for latest version"); 87 | 88 | let response = tinyget::get(format!("{}release-latest.txt", registry)).send()?; 89 | 90 | let body = response.as_str()?; 91 | let v = body.trim().replace('v', ""); 92 | println!("The latest version is v{}", &v); 93 | Ok(Version::parse(&v).unwrap()) 94 | } 95 | 96 | fn download_package(url: &str, version: &Version) -> Result> { 97 | println!("downloading {}", &url); 98 | 99 | let response = match tinyget::get(url).send() { 100 | Ok(response) => response, 101 | Err(error) => { 102 | println!("Network error {}", &error); 103 | std::process::exit(1) 104 | } 105 | }; 106 | 107 | if response.status_code == 404 { 108 | println!("Version has not been found, aborting"); 109 | std::process::exit(1) 110 | } 111 | 112 | if response.status_code >= 400 && response.status_code <= 599 { 113 | println!("Download '{}' failed: {}", &url, response.status_code); 114 | std::process::exit(1) 115 | } 116 | 117 | println!("Version has been found"); 118 | println!("Deno v{} has been downloaded", &version); 119 | 120 | Ok(response.into_bytes()) 121 | } 122 | 123 | fn compose_url_to_exec(registry: &str, version: &Version) -> String { 124 | format!("{}release/v{}/{}", registry, version, ARCHIVE_NAME) 125 | } 126 | 127 | fn unpack(archive_data: Vec, version: &Version) -> Result { 128 | let version_dir = dvm_root().join(format!("{}/{}", DVM_CACHE_PATH_PREFIX, version)); 129 | fs::create_dir_all(&version_dir)?; 130 | let exe_path = deno_version_path(version); 131 | 132 | unpack_impl(archive_data, version_dir, exe_path) 133 | } 134 | 135 | fn unpack_canary(archive_data: Vec) -> Result { 136 | let canary_dir = dvm_root().join(DVM_CANARY_PATH_PREFIX); 137 | fs::create_dir_all(&canary_dir)?; 138 | let exe_path = deno_canary_path(); 139 | 140 | if exe_path.exists() { 141 | fs::remove_file(exe_path.clone())?; 142 | } 143 | 144 | unpack_impl(archive_data, canary_dir, exe_path) 145 | } 146 | 147 | fn unpack_impl(archive_data: Vec, version_dir: PathBuf, path: PathBuf) -> Result { 148 | let archive_ext = Path::new(ARCHIVE_NAME) 149 | .extension() 150 | .and_then(|ext| ext.to_str()) 151 | .unwrap(); 152 | let unpack_status = match archive_ext { 153 | "zip" if cfg!(windows) => { 154 | let archive_path = version_dir.join("deno.zip"); 155 | fs::write(&archive_path, &archive_data)?; 156 | Command::new("powershell.exe") 157 | .arg("-NoLogo") 158 | .arg("-NoProfile") 159 | .arg("-NonInteractive") 160 | .arg("-Command") 161 | .arg( 162 | "& { 163 | param($Path, $DestinationPath) 164 | trap { $host.ui.WriteErrorLine($_.Exception); exit 1 } 165 | Add-Type -AssemblyName System.IO.Compression.FileSystem 166 | [System.IO.Compression.ZipFile]::ExtractToDirectory( 167 | $Path, 168 | $DestinationPath 169 | ); 170 | }", 171 | ) 172 | .arg("-Path") 173 | .arg(format!("'{}'", &archive_path.to_str().unwrap())) 174 | .arg("-DestinationPath") 175 | .arg(format!("'{}'", &version_dir.to_str().unwrap())) 176 | .spawn()? 177 | .wait()? 178 | } 179 | "zip" => { 180 | let archive_path = version_dir.join("deno.zip"); 181 | fs::write(&archive_path, &archive_data)?; 182 | Command::new("unzip") 183 | .current_dir(&version_dir) 184 | .arg(archive_path) 185 | .spawn()? 186 | .wait()? 187 | } 188 | ext => panic!("Unsupported archive type: '{}'", ext), 189 | }; 190 | assert!(unpack_status.success()); 191 | assert!(path.exists()); 192 | Ok(version_dir) 193 | } 194 | 195 | fn download_canary(registry: &str, hash: &str) -> Result> { 196 | // TODO: remove this when deno canary support m1 chip, 197 | let archive_name = if ARCHIVE_NAME == "deno-aarch64-apple-darwin.zip" { 198 | "deno-x86_64-apple-darwin.zip" 199 | } else { 200 | ARCHIVE_NAME 201 | }; 202 | 203 | let url = format!("{}canary/{}/{}", registry, hash, archive_name); 204 | 205 | let resp = tinyget::get(url).send()?; 206 | Ok(resp.into_bytes()) 207 | } 208 | 209 | #[test] 210 | fn test_compose_url_to_exec() { 211 | use crate::consts::REGISTRY_OFFICIAL; 212 | use asserts_rs::asserts_eq_one_of; 213 | 214 | let v = Version::parse("1.7.0").unwrap(); 215 | let url = compose_url_to_exec(REGISTRY_OFFICIAL, &v); 216 | 217 | cfg_if! { 218 | if #[cfg(windows)] { 219 | asserts_eq_one_of!( 220 | url.as_str(), 221 | "https://dl.deno.land/release/v1.7.0/deno-x86_64-pc-windows-msvc.zip", 222 | "https://dl.deno.js.cn/release/v1.7.0/deno-x86_64-pc-windows-msvc.zip" 223 | ); 224 | } else if #[cfg(all(target_os = "macos", target_arch = "x86_64"))] { 225 | asserts_eq_one_of!( 226 | url.as_str(), 227 | "https://dl.deno.land/release/v1.7.0/deno-x86_64-apple-darwin.zip", 228 | "https://dl.deno.js.cn/release/v1.7.0/deno-x86_64-apple-darwin.zip" 229 | ); 230 | } else if #[cfg(all(target_os = "macos", target_arch = "aarch64"))] { 231 | asserts_eq_one_of!( 232 | url.as_str(), 233 | "https://dl.deno.land/release/v1.7.0/deno-aarch64-apple-darwin.zip", 234 | "https://dl.deno.js.cn/release/v1.7.0/deno-aarch64-apple-darwin.zip" 235 | ); 236 | } else if #[cfg(all(target_os = "linux", target_arch = "x86_64"))] { 237 | asserts_eq_one_of!( 238 | url.as_str(), 239 | "https://dl.deno.land/release/v1.7.0/deno-x86_64-unknown-linux-gnu.zip", 240 | "https://dl.deno.js.cn/release/v1.7.0/deno-x86_64-unknown-linux-gnu.zip" 241 | ); 242 | } else if #[cfg(all(target_os = "linux", target_arch = "aarch64"))] { 243 | asserts_eq_one_of!( 244 | url.as_str(), 245 | "https://dl.deno.land/release/v1.7.0/deno-aarch64-unknown-linux-gnu.zip", 246 | "https://dl.deno.js.cn/release/v1.7.0/deno-aarch64-unknown-linux-gnu.zip" 247 | ); 248 | } 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /src/meta.rs: -------------------------------------------------------------------------------- 1 | use crate::consts::DVM_CACHE_INVALID_TIMEOUT; 2 | use crate::utils::{deno_version_path, dvm_root, dvm_versions, now}; 3 | use crate::version::VersionArg; 4 | use colored::Colorize; 5 | use semver::{Version, VersionReq}; 6 | use serde::{Deserialize, Serialize}; 7 | use std::fs::{create_dir_all, read_to_string, write}; 8 | use std::path::{Path, PathBuf}; 9 | use std::str::FromStr; 10 | 11 | pub const DEFAULT_ALIAS: phf::Map<&'static str, &'static str> = phf::phf_map! { 12 | "latest" => "*" 13 | }; 14 | 15 | pub trait ToVersionReq { 16 | #[allow(dead_code)] 17 | fn to_version_req(&self) -> VersionReq; 18 | fn try_to_version_req(&self) -> anyhow::Result; 19 | } 20 | 21 | #[derive(Clone, Eq, PartialEq, Deserialize, Serialize)] 22 | pub struct VersionMapping { 23 | pub required: String, 24 | pub current: String, 25 | } 26 | 27 | impl VersionMapping { 28 | pub fn is_valid_mapping(&self) -> bool { 29 | let current = Version::parse(&self.current); 30 | if current.is_err() { 31 | return false; 32 | } 33 | let current = current.unwrap(); 34 | 35 | let req = self.try_to_version_req(); 36 | if req.is_err() { 37 | return false; 38 | } 39 | let req = req.unwrap(); 40 | 41 | req.matches(¤t) 42 | } 43 | } 44 | 45 | impl ToVersionReq for VersionMapping { 46 | fn to_version_req(&self) -> VersionReq { 47 | VersionReq::from_str(&self.required).expect("VersionMapping::required is not a valid VersionReq") 48 | } 49 | 50 | fn try_to_version_req(&self) -> anyhow::Result { 51 | VersionReq::from_str(&self.required).map_err(|err| anyhow::anyhow!(err)) 52 | } 53 | } 54 | 55 | #[derive(Clone, Eq, PartialEq, Deserialize, Serialize, Debug)] 56 | pub struct Alias { 57 | pub name: String, 58 | pub required: String, 59 | } 60 | 61 | impl ToVersionReq for Alias { 62 | fn to_version_req(&self) -> VersionReq { 63 | VersionReq::from_str(&self.required).expect("Alias::required is not a valid VersionReq") 64 | } 65 | 66 | fn try_to_version_req(&self) -> anyhow::Result { 67 | VersionReq::from_str(&self.required).map_err(|err| anyhow::anyhow!(err)) 68 | } 69 | } 70 | 71 | #[derive(Clone, Default, Eq, PartialEq, Deserialize, Serialize)] 72 | pub struct DvmMeta { 73 | pub versions: Vec, 74 | pub alias: Vec, 75 | } 76 | 77 | impl DvmMeta { 78 | pub fn path() -> PathBuf { 79 | let mut meta = dvm_root(); 80 | meta.push(Path::new("dvm-metadata.json")); 81 | meta 82 | } 83 | 84 | pub fn new() -> Self { 85 | let path = DvmMeta::path(); 86 | if path.exists() { 87 | let content = read_to_string(path); 88 | if let Ok(content) = content { 89 | let config = serde_json::from_str::(content.as_str()); 90 | if let Ok(mut config) = config { 91 | let mut i = 0; 92 | while i < config.versions.len() { 93 | if !deno_version_path(&Version::parse(&config.versions[i].current).unwrap()).exists() { 94 | config.versions.remove(i); 95 | } else { 96 | i += 1; 97 | } 98 | } 99 | return config; 100 | } 101 | } 102 | } 103 | 104 | let mut config = DvmMeta::default(); 105 | config.save_and_reload(); 106 | config 107 | } 108 | 109 | pub fn clean_files(&self) { 110 | let cache_folder = dvm_versions(); 111 | if let Ok(dir) = cache_folder.read_dir() { 112 | for entry in dir.flatten() { 113 | let path = entry.path(); 114 | if path.is_dir() { 115 | let name = path.file_name().unwrap().to_str().unwrap(); 116 | 117 | // it's been pointed by dvm versions 118 | if self.versions.iter().any(|it| it.current == name) { 119 | continue; 120 | } 121 | 122 | // it's not been outdated 123 | let stub = path.join(".dvmstub"); 124 | if stub.exists() && stub.is_file() { 125 | let content = std::fs::read_to_string(stub).expect("read stub file failed"); 126 | let content: u128 = content.parse().expect("parse stub file failed"); 127 | if content > now() - DVM_CACHE_INVALID_TIMEOUT { 128 | continue; 129 | } 130 | } 131 | 132 | println!("Cleaning version {}", name.bright_black()); 133 | std::fs::remove_dir_all(path).unwrap(); 134 | } 135 | } 136 | } 137 | } 138 | 139 | /// 140 | /// set a version mapping 141 | /// `required` is either a semver range or a alias to a semver rage 142 | /// `current` is the current directory that the deno located in 143 | pub fn set_version_mapping(&mut self, required: String, current: String) { 144 | let result = self.versions.iter().position(|it| it.required == required); 145 | if let Some(index) = result { 146 | self.versions[index] = VersionMapping { required, current }; 147 | } else { 148 | self.versions.push(VersionMapping { required, current }); 149 | } 150 | self.save_and_reload(); 151 | } 152 | 153 | /// 154 | /// get fold name of a given mapping, 155 | /// None if there haven't a deno version met the required semver range or alias that 156 | /// are installed already 157 | pub fn get_version_mapping(&self, required: &str) -> Option { 158 | self 159 | .versions 160 | .iter() 161 | .position(|it| it.required == required) 162 | .map(|index| self.versions[index].current.clone()) 163 | } 164 | 165 | /// 166 | /// delete a version mapping 167 | /// this will also delete actual files. 168 | pub fn delete_version_mapping(&mut self, required: String) { 169 | let result = self.versions.iter().position(|it| it.required == required); 170 | if let Some(index) = result { 171 | self.versions.remove(index); 172 | } 173 | 174 | self.save_and_reload(); 175 | } 176 | 177 | /// 178 | /// list aliases 179 | /// including predefined aliases 180 | pub fn list_alias(&self) -> Vec { 181 | let mut alias = self.alias.clone(); 182 | for (k, v) in DEFAULT_ALIAS.into_iter() { 183 | alias.insert( 184 | 0, 185 | Alias { 186 | name: k.to_string(), 187 | required: v.to_string(), 188 | }, 189 | ); 190 | } 191 | 192 | alias 193 | } 194 | 195 | /// set a alias 196 | /// name is alias name 197 | /// required is a semver range 198 | pub fn set_alias(&mut self, name: String, required: String) { 199 | if DEFAULT_ALIAS.contains_key(name.as_str()) { 200 | return; 201 | } 202 | let result = self.alias.iter().position(|it| it.name == name); 203 | if let Some(index) = result { 204 | self.alias[index] = Alias { name, required }; 205 | } else { 206 | self.alias.push(Alias { name, required }); 207 | } 208 | 209 | self.save_and_reload(); 210 | } 211 | 212 | pub fn has_alias(&self, name: &str) -> bool { 213 | self.get_alias(name).is_some() 214 | } 215 | 216 | /// get the semver range of alias 217 | pub fn get_alias(&self, name: &str) -> Option { 218 | if DEFAULT_ALIAS.contains_key(name) { 219 | VersionArg::from_str(DEFAULT_ALIAS[name]).ok() 220 | } else { 221 | self 222 | .alias 223 | .iter() 224 | .position(|it| it.name == name) 225 | .map(|index| VersionArg::from_str(&self.alias[index].required).unwrap()) 226 | } 227 | } 228 | 229 | /// delete a alias 230 | pub fn delete_alias(&mut self, name: String) { 231 | let result = self.alias.iter().position(|it| it.name == name); 232 | if let Some(index) = result { 233 | self.alias.remove(index); 234 | } 235 | 236 | self.save_and_reload(); 237 | } 238 | 239 | pub fn resolve_version_req(&self, required: &str) -> VersionArg { 240 | if self.has_alias(required) { 241 | self.get_alias(required).unwrap() 242 | } else { 243 | VersionArg::from_str(required).unwrap() 244 | } 245 | } 246 | 247 | /// reload from disk 248 | pub fn reload(&mut self) { 249 | let new = DvmMeta::new(); 250 | self.versions = new.versions; 251 | self.alias = new.alias; 252 | } 253 | 254 | /// write to disk 255 | pub fn save(&self) { 256 | let file_path = DvmMeta::path(); 257 | let dir_path = file_path.parent().unwrap(); 258 | if !dir_path.exists() { 259 | create_dir_all(dir_path).unwrap(); 260 | } 261 | write(file_path, serde_json::to_string_pretty(self).unwrap()).unwrap(); 262 | } 263 | 264 | pub fn save_and_reload(&mut self) { 265 | self.save(); 266 | self.reload(); 267 | } 268 | } 269 | 270 | #[cfg(test)] 271 | mod tests { 272 | use super::*; 273 | use serde_json::json; 274 | 275 | #[test] 276 | fn test_default_config() { 277 | let result = serde_json::to_string(&DvmMeta::default()); 278 | assert!(result.is_ok()); 279 | assert_eq!(result.unwrap(), "{\"versions\":[],\"alias\":[]}"); 280 | } 281 | 282 | #[test] 283 | fn test_versions_config() { 284 | let mut conf = DvmMeta::default(); 285 | conf.versions.push(VersionMapping { 286 | required: "~1.0.0".to_string(), 287 | current: "1.0.1".to_string(), 288 | }); 289 | let result = serde_json::to_string(&conf); 290 | assert!(result.is_ok()); 291 | assert_eq!( 292 | result.unwrap(), 293 | "{\"versions\":[{\"required\":\"~1.0.0\",\"current\":\"1.0.1\"}],\"alias\":[]}" 294 | ) 295 | } 296 | 297 | #[test] 298 | fn test_alias_config() { 299 | let mut conf = DvmMeta::default(); 300 | conf.alias.push(Alias { 301 | name: "stable".to_string(), 302 | required: "1.0.0".to_string(), 303 | }); 304 | conf.alias.push(Alias { 305 | name: "two-point-o".to_string(), 306 | required: "2.0.0".to_string(), 307 | }); 308 | let result = serde_json::to_string(&conf); 309 | assert!(result.is_ok()); 310 | assert_eq!( 311 | result.unwrap(), 312 | "{\"versions\":[],\"alias\":[{\"name\":\"stable\",\"required\":\"1.0.0\"},{\"name\":\"two-point-o\",\"required\":\"2.0.0\"}]}" 313 | ) 314 | } 315 | 316 | #[test] 317 | fn test_parse_valid() { 318 | let raw = json!( 319 | { 320 | "versions": [ 321 | { "required": "~1.0.0", "current": "1.0.1" }, 322 | { "required": "^1.0.0", "current": "1.2.0" }, 323 | ], 324 | "alias": [ 325 | { "name": "latest", "required": "*" }, 326 | { "name": "stable", "required": "^1.0.0"}, 327 | ] 328 | } 329 | ); 330 | 331 | let parsed = DvmMeta::deserialize(raw); 332 | assert!(parsed.is_ok()); 333 | let parsed = parsed.unwrap(); 334 | assert_eq!(parsed.alias.len(), 2); 335 | assert_eq!(parsed.versions.len(), 2); 336 | assert_eq!(parsed.alias[0].name, "latest"); 337 | assert_eq!(parsed.alias[0].required, "*"); 338 | assert_eq!(parsed.alias[0].to_version_req(), VersionReq::parse("*").unwrap()); 339 | assert!(parsed.alias[0].try_to_version_req().is_ok()); 340 | assert_eq!(parsed.alias[1].name, "stable"); 341 | assert_eq!(parsed.alias[1].required, "^1.0.0"); 342 | assert!(parsed.alias[1].try_to_version_req().is_ok()); 343 | assert_eq!(parsed.versions[0].required, "~1.0.0"); 344 | assert_eq!(parsed.versions[0].current, "1.0.1"); 345 | assert!(parsed.versions[0].try_to_version_req().is_ok()); 346 | assert!(parsed.versions[0].is_valid_mapping()); 347 | assert_eq!(parsed.versions[1].required, "^1.0.0"); 348 | assert_eq!(parsed.versions[1].current, "1.2.0"); 349 | assert!(parsed.versions[1].try_to_version_req().is_ok()); 350 | assert!(parsed.versions[1].is_valid_mapping()); 351 | } 352 | } 353 | -------------------------------------------------------------------------------- /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 = "anstream" 7 | version = "0.6.18" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" 10 | dependencies = [ 11 | "anstyle", 12 | "anstyle-parse", 13 | "anstyle-query", 14 | "anstyle-wincon", 15 | "colorchoice", 16 | "is_terminal_polyfill", 17 | "utf8parse", 18 | ] 19 | 20 | [[package]] 21 | name = "anstyle" 22 | version = "1.0.10" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 25 | 26 | [[package]] 27 | name = "anstyle-parse" 28 | version = "0.2.6" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 31 | dependencies = [ 32 | "utf8parse", 33 | ] 34 | 35 | [[package]] 36 | name = "anstyle-query" 37 | version = "1.1.2" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" 40 | dependencies = [ 41 | "windows-sys 0.59.0", 42 | ] 43 | 44 | [[package]] 45 | name = "anstyle-wincon" 46 | version = "3.0.8" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "6680de5231bd6ee4c6191b8a1325daa282b415391ec9d3a37bd34f2060dc73fa" 49 | dependencies = [ 50 | "anstyle", 51 | "once_cell_polyfill", 52 | "windows-sys 0.59.0", 53 | ] 54 | 55 | [[package]] 56 | name = "anyhow" 57 | version = "1.0.98" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" 60 | 61 | [[package]] 62 | name = "asserts-rs" 63 | version = "0.3.0" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "42319a061ddba4d1b9853bbba4a94f085b930035faebc0537523f40821d20217" 66 | 67 | [[package]] 68 | name = "bitflags" 69 | version = "2.9.1" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" 72 | 73 | [[package]] 74 | name = "bumpalo" 75 | version = "3.17.0" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" 78 | 79 | [[package]] 80 | name = "cc" 81 | version = "1.2.24" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "16595d3be041c03b09d08d0858631facccee9221e579704070e6e9e4915d3bc7" 84 | dependencies = [ 85 | "shlex", 86 | ] 87 | 88 | [[package]] 89 | name = "cfg-if" 90 | version = "1.0.0" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 93 | 94 | [[package]] 95 | name = "clap" 96 | version = "4.5.38" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "ed93b9805f8ba930df42c2590f05453d5ec36cbb85d018868a5b24d31f6ac000" 99 | dependencies = [ 100 | "clap_builder", 101 | ] 102 | 103 | [[package]] 104 | name = "clap_builder" 105 | version = "4.5.38" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "379026ff283facf611b0ea629334361c4211d1b12ee01024eec1591133b04120" 108 | dependencies = [ 109 | "anstream", 110 | "anstyle", 111 | "clap_lex", 112 | "strsim", 113 | ] 114 | 115 | [[package]] 116 | name = "clap_complete" 117 | version = "4.5.50" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "c91d3baa3bcd889d60e6ef28874126a0b384fd225ab83aa6d8a801c519194ce1" 120 | dependencies = [ 121 | "clap", 122 | ] 123 | 124 | [[package]] 125 | name = "clap_derive" 126 | version = "4.5.32" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" 129 | dependencies = [ 130 | "heck", 131 | "proc-macro2", 132 | "quote", 133 | "syn", 134 | ] 135 | 136 | [[package]] 137 | name = "clap_lex" 138 | version = "0.7.4" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" 141 | 142 | [[package]] 143 | name = "colorchoice" 144 | version = "1.0.3" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 147 | 148 | [[package]] 149 | name = "colored" 150 | version = "2.2.0" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" 153 | dependencies = [ 154 | "lazy_static", 155 | "windows-sys 0.59.0", 156 | ] 157 | 158 | [[package]] 159 | name = "console" 160 | version = "0.15.11" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" 163 | dependencies = [ 164 | "encode_unicode", 165 | "libc", 166 | "once_cell", 167 | "unicode-width", 168 | "windows-sys 0.59.0", 169 | ] 170 | 171 | [[package]] 172 | name = "core-foundation" 173 | version = "0.9.4" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 176 | dependencies = [ 177 | "core-foundation-sys", 178 | "libc", 179 | ] 180 | 181 | [[package]] 182 | name = "core-foundation-sys" 183 | version = "0.8.7" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 186 | 187 | [[package]] 188 | name = "ctor" 189 | version = "0.2.9" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" 192 | dependencies = [ 193 | "quote", 194 | "syn", 195 | ] 196 | 197 | [[package]] 198 | name = "dirs" 199 | version = "4.0.0" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" 202 | dependencies = [ 203 | "dirs-sys 0.3.7", 204 | ] 205 | 206 | [[package]] 207 | name = "dirs" 208 | version = "5.0.1" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" 211 | dependencies = [ 212 | "dirs-sys 0.4.1", 213 | ] 214 | 215 | [[package]] 216 | name = "dirs-sys" 217 | version = "0.3.7" 218 | source = "registry+https://github.com/rust-lang/crates.io-index" 219 | checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" 220 | dependencies = [ 221 | "libc", 222 | "redox_users", 223 | "winapi", 224 | ] 225 | 226 | [[package]] 227 | name = "dirs-sys" 228 | version = "0.4.1" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" 231 | dependencies = [ 232 | "libc", 233 | "option-ext", 234 | "redox_users", 235 | "windows-sys 0.48.0", 236 | ] 237 | 238 | [[package]] 239 | name = "dvm" 240 | version = "1.9.3" 241 | dependencies = [ 242 | "anyhow", 243 | "asserts-rs", 244 | "cfg-if", 245 | "clap", 246 | "clap_complete", 247 | "clap_derive", 248 | "colored", 249 | "ctor", 250 | "dirs 5.0.1", 251 | "indicatif", 252 | "json_minimal", 253 | "native-tls", 254 | "output_vt100", 255 | "phf", 256 | "semver", 257 | "serde", 258 | "serde_json", 259 | "set_env", 260 | "tempfile", 261 | "tinyget", 262 | "which", 263 | "winapi", 264 | ] 265 | 266 | [[package]] 267 | name = "either" 268 | version = "1.15.0" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" 271 | 272 | [[package]] 273 | name = "encode_unicode" 274 | version = "1.0.0" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" 277 | 278 | [[package]] 279 | name = "errno" 280 | version = "0.3.12" 281 | source = "registry+https://github.com/rust-lang/crates.io-index" 282 | checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" 283 | dependencies = [ 284 | "libc", 285 | "windows-sys 0.59.0", 286 | ] 287 | 288 | [[package]] 289 | name = "fastrand" 290 | version = "2.3.0" 291 | source = "registry+https://github.com/rust-lang/crates.io-index" 292 | checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" 293 | 294 | [[package]] 295 | name = "foreign-types" 296 | version = "0.3.2" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 299 | dependencies = [ 300 | "foreign-types-shared", 301 | ] 302 | 303 | [[package]] 304 | name = "foreign-types-shared" 305 | version = "0.1.1" 306 | source = "registry+https://github.com/rust-lang/crates.io-index" 307 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 308 | 309 | [[package]] 310 | name = "getrandom" 311 | version = "0.2.16" 312 | source = "registry+https://github.com/rust-lang/crates.io-index" 313 | checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" 314 | dependencies = [ 315 | "cfg-if", 316 | "libc", 317 | "wasi 0.11.0+wasi-snapshot-preview1", 318 | ] 319 | 320 | [[package]] 321 | name = "getrandom" 322 | version = "0.3.3" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" 325 | dependencies = [ 326 | "cfg-if", 327 | "libc", 328 | "r-efi", 329 | "wasi 0.14.2+wasi-0.2.4", 330 | ] 331 | 332 | [[package]] 333 | name = "heck" 334 | version = "0.5.0" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 337 | 338 | [[package]] 339 | name = "home" 340 | version = "0.5.11" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" 343 | dependencies = [ 344 | "windows-sys 0.59.0", 345 | ] 346 | 347 | [[package]] 348 | name = "indicatif" 349 | version = "0.17.11" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235" 352 | dependencies = [ 353 | "console", 354 | "number_prefix", 355 | "portable-atomic", 356 | "unicode-width", 357 | "web-time", 358 | ] 359 | 360 | [[package]] 361 | name = "is_terminal_polyfill" 362 | version = "1.70.1" 363 | source = "registry+https://github.com/rust-lang/crates.io-index" 364 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 365 | 366 | [[package]] 367 | name = "itoa" 368 | version = "1.0.15" 369 | source = "registry+https://github.com/rust-lang/crates.io-index" 370 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 371 | 372 | [[package]] 373 | name = "js-sys" 374 | version = "0.3.77" 375 | source = "registry+https://github.com/rust-lang/crates.io-index" 376 | checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" 377 | dependencies = [ 378 | "once_cell", 379 | "wasm-bindgen", 380 | ] 381 | 382 | [[package]] 383 | name = "json_minimal" 384 | version = "0.1.3" 385 | source = "registry+https://github.com/rust-lang/crates.io-index" 386 | checksum = "5f23a154c7bbe06d65d084a909a7ba16981b514768c5b79ccf423dda1e4b4d8e" 387 | 388 | [[package]] 389 | name = "lazy_static" 390 | version = "1.5.0" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 393 | 394 | [[package]] 395 | name = "libc" 396 | version = "0.2.172" 397 | source = "registry+https://github.com/rust-lang/crates.io-index" 398 | checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" 399 | 400 | [[package]] 401 | name = "libredox" 402 | version = "0.1.3" 403 | source = "registry+https://github.com/rust-lang/crates.io-index" 404 | checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" 405 | dependencies = [ 406 | "bitflags", 407 | "libc", 408 | ] 409 | 410 | [[package]] 411 | name = "linux-raw-sys" 412 | version = "0.4.15" 413 | source = "registry+https://github.com/rust-lang/crates.io-index" 414 | checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" 415 | 416 | [[package]] 417 | name = "linux-raw-sys" 418 | version = "0.9.4" 419 | source = "registry+https://github.com/rust-lang/crates.io-index" 420 | checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" 421 | 422 | [[package]] 423 | name = "log" 424 | version = "0.4.27" 425 | source = "registry+https://github.com/rust-lang/crates.io-index" 426 | checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" 427 | 428 | [[package]] 429 | name = "memchr" 430 | version = "2.7.4" 431 | source = "registry+https://github.com/rust-lang/crates.io-index" 432 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 433 | 434 | [[package]] 435 | name = "native-tls" 436 | version = "0.2.14" 437 | source = "registry+https://github.com/rust-lang/crates.io-index" 438 | checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" 439 | dependencies = [ 440 | "libc", 441 | "log", 442 | "openssl", 443 | "openssl-probe", 444 | "openssl-sys", 445 | "schannel", 446 | "security-framework", 447 | "security-framework-sys", 448 | "tempfile", 449 | ] 450 | 451 | [[package]] 452 | name = "number_prefix" 453 | version = "0.4.0" 454 | source = "registry+https://github.com/rust-lang/crates.io-index" 455 | checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" 456 | 457 | [[package]] 458 | name = "once_cell" 459 | version = "1.21.3" 460 | source = "registry+https://github.com/rust-lang/crates.io-index" 461 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 462 | 463 | [[package]] 464 | name = "once_cell_polyfill" 465 | version = "1.70.1" 466 | source = "registry+https://github.com/rust-lang/crates.io-index" 467 | checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" 468 | 469 | [[package]] 470 | name = "openssl" 471 | version = "0.10.72" 472 | source = "registry+https://github.com/rust-lang/crates.io-index" 473 | checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da" 474 | dependencies = [ 475 | "bitflags", 476 | "cfg-if", 477 | "foreign-types", 478 | "libc", 479 | "once_cell", 480 | "openssl-macros", 481 | "openssl-sys", 482 | ] 483 | 484 | [[package]] 485 | name = "openssl-macros" 486 | version = "0.1.1" 487 | source = "registry+https://github.com/rust-lang/crates.io-index" 488 | checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" 489 | dependencies = [ 490 | "proc-macro2", 491 | "quote", 492 | "syn", 493 | ] 494 | 495 | [[package]] 496 | name = "openssl-probe" 497 | version = "0.1.6" 498 | source = "registry+https://github.com/rust-lang/crates.io-index" 499 | checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" 500 | 501 | [[package]] 502 | name = "openssl-src" 503 | version = "300.5.0+3.5.0" 504 | source = "registry+https://github.com/rust-lang/crates.io-index" 505 | checksum = "e8ce546f549326b0e6052b649198487d91320875da901e7bd11a06d1ee3f9c2f" 506 | dependencies = [ 507 | "cc", 508 | ] 509 | 510 | [[package]] 511 | name = "openssl-sys" 512 | version = "0.9.108" 513 | source = "registry+https://github.com/rust-lang/crates.io-index" 514 | checksum = "e145e1651e858e820e4860f7b9c5e169bc1d8ce1c86043be79fa7b7634821847" 515 | dependencies = [ 516 | "cc", 517 | "libc", 518 | "openssl-src", 519 | "pkg-config", 520 | "vcpkg", 521 | ] 522 | 523 | [[package]] 524 | name = "option-ext" 525 | version = "0.2.0" 526 | source = "registry+https://github.com/rust-lang/crates.io-index" 527 | checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" 528 | 529 | [[package]] 530 | name = "output_vt100" 531 | version = "0.1.3" 532 | source = "registry+https://github.com/rust-lang/crates.io-index" 533 | checksum = "628223faebab4e3e40667ee0b2336d34a5b960ff60ea743ddfdbcf7770bcfb66" 534 | dependencies = [ 535 | "winapi", 536 | ] 537 | 538 | [[package]] 539 | name = "phf" 540 | version = "0.11.3" 541 | source = "registry+https://github.com/rust-lang/crates.io-index" 542 | checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" 543 | dependencies = [ 544 | "phf_macros", 545 | "phf_shared", 546 | ] 547 | 548 | [[package]] 549 | name = "phf_generator" 550 | version = "0.11.3" 551 | source = "registry+https://github.com/rust-lang/crates.io-index" 552 | checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" 553 | dependencies = [ 554 | "phf_shared", 555 | "rand", 556 | ] 557 | 558 | [[package]] 559 | name = "phf_macros" 560 | version = "0.11.3" 561 | source = "registry+https://github.com/rust-lang/crates.io-index" 562 | checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" 563 | dependencies = [ 564 | "phf_generator", 565 | "phf_shared", 566 | "proc-macro2", 567 | "quote", 568 | "syn", 569 | ] 570 | 571 | [[package]] 572 | name = "phf_shared" 573 | version = "0.11.3" 574 | source = "registry+https://github.com/rust-lang/crates.io-index" 575 | checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" 576 | dependencies = [ 577 | "siphasher", 578 | ] 579 | 580 | [[package]] 581 | name = "pkg-config" 582 | version = "0.3.32" 583 | source = "registry+https://github.com/rust-lang/crates.io-index" 584 | checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" 585 | 586 | [[package]] 587 | name = "portable-atomic" 588 | version = "1.11.0" 589 | source = "registry+https://github.com/rust-lang/crates.io-index" 590 | checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" 591 | 592 | [[package]] 593 | name = "proc-macro2" 594 | version = "1.0.95" 595 | source = "registry+https://github.com/rust-lang/crates.io-index" 596 | checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" 597 | dependencies = [ 598 | "unicode-ident", 599 | ] 600 | 601 | [[package]] 602 | name = "quote" 603 | version = "1.0.40" 604 | source = "registry+https://github.com/rust-lang/crates.io-index" 605 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 606 | dependencies = [ 607 | "proc-macro2", 608 | ] 609 | 610 | [[package]] 611 | name = "r-efi" 612 | version = "5.2.0" 613 | source = "registry+https://github.com/rust-lang/crates.io-index" 614 | checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" 615 | 616 | [[package]] 617 | name = "rand" 618 | version = "0.8.5" 619 | source = "registry+https://github.com/rust-lang/crates.io-index" 620 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 621 | dependencies = [ 622 | "rand_core", 623 | ] 624 | 625 | [[package]] 626 | name = "rand_core" 627 | version = "0.6.4" 628 | source = "registry+https://github.com/rust-lang/crates.io-index" 629 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 630 | 631 | [[package]] 632 | name = "redox_users" 633 | version = "0.4.6" 634 | source = "registry+https://github.com/rust-lang/crates.io-index" 635 | checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" 636 | dependencies = [ 637 | "getrandom 0.2.16", 638 | "libredox", 639 | "thiserror", 640 | ] 641 | 642 | [[package]] 643 | name = "rustix" 644 | version = "0.38.44" 645 | source = "registry+https://github.com/rust-lang/crates.io-index" 646 | checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" 647 | dependencies = [ 648 | "bitflags", 649 | "errno", 650 | "libc", 651 | "linux-raw-sys 0.4.15", 652 | "windows-sys 0.59.0", 653 | ] 654 | 655 | [[package]] 656 | name = "rustix" 657 | version = "1.0.7" 658 | source = "registry+https://github.com/rust-lang/crates.io-index" 659 | checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" 660 | dependencies = [ 661 | "bitflags", 662 | "errno", 663 | "libc", 664 | "linux-raw-sys 0.9.4", 665 | "windows-sys 0.59.0", 666 | ] 667 | 668 | [[package]] 669 | name = "ryu" 670 | version = "1.0.20" 671 | source = "registry+https://github.com/rust-lang/crates.io-index" 672 | checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 673 | 674 | [[package]] 675 | name = "schannel" 676 | version = "0.1.27" 677 | source = "registry+https://github.com/rust-lang/crates.io-index" 678 | checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" 679 | dependencies = [ 680 | "windows-sys 0.59.0", 681 | ] 682 | 683 | [[package]] 684 | name = "security-framework" 685 | version = "2.11.1" 686 | source = "registry+https://github.com/rust-lang/crates.io-index" 687 | checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" 688 | dependencies = [ 689 | "bitflags", 690 | "core-foundation", 691 | "core-foundation-sys", 692 | "libc", 693 | "security-framework-sys", 694 | ] 695 | 696 | [[package]] 697 | name = "security-framework-sys" 698 | version = "2.14.0" 699 | source = "registry+https://github.com/rust-lang/crates.io-index" 700 | checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" 701 | dependencies = [ 702 | "core-foundation-sys", 703 | "libc", 704 | ] 705 | 706 | [[package]] 707 | name = "semver" 708 | version = "1.0.26" 709 | source = "registry+https://github.com/rust-lang/crates.io-index" 710 | checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" 711 | 712 | [[package]] 713 | name = "serde" 714 | version = "1.0.219" 715 | source = "registry+https://github.com/rust-lang/crates.io-index" 716 | checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" 717 | dependencies = [ 718 | "serde_derive", 719 | ] 720 | 721 | [[package]] 722 | name = "serde_derive" 723 | version = "1.0.219" 724 | source = "registry+https://github.com/rust-lang/crates.io-index" 725 | checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" 726 | dependencies = [ 727 | "proc-macro2", 728 | "quote", 729 | "syn", 730 | ] 731 | 732 | [[package]] 733 | name = "serde_json" 734 | version = "1.0.140" 735 | source = "registry+https://github.com/rust-lang/crates.io-index" 736 | checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" 737 | dependencies = [ 738 | "itoa", 739 | "memchr", 740 | "ryu", 741 | "serde", 742 | ] 743 | 744 | [[package]] 745 | name = "set_env" 746 | version = "1.3.4" 747 | source = "registry+https://github.com/rust-lang/crates.io-index" 748 | checksum = "51864fb62fd09b8a7d931a11832bfbbda5297425cfc9ce04b54bc86c945c58eb" 749 | dependencies = [ 750 | "dirs 4.0.0", 751 | ] 752 | 753 | [[package]] 754 | name = "shlex" 755 | version = "1.3.0" 756 | source = "registry+https://github.com/rust-lang/crates.io-index" 757 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 758 | 759 | [[package]] 760 | name = "siphasher" 761 | version = "1.0.1" 762 | source = "registry+https://github.com/rust-lang/crates.io-index" 763 | checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" 764 | 765 | [[package]] 766 | name = "strsim" 767 | version = "0.11.1" 768 | source = "registry+https://github.com/rust-lang/crates.io-index" 769 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 770 | 771 | [[package]] 772 | name = "syn" 773 | version = "2.0.101" 774 | source = "registry+https://github.com/rust-lang/crates.io-index" 775 | checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" 776 | dependencies = [ 777 | "proc-macro2", 778 | "quote", 779 | "unicode-ident", 780 | ] 781 | 782 | [[package]] 783 | name = "tempfile" 784 | version = "3.20.0" 785 | source = "registry+https://github.com/rust-lang/crates.io-index" 786 | checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" 787 | dependencies = [ 788 | "fastrand", 789 | "getrandom 0.3.3", 790 | "once_cell", 791 | "rustix 1.0.7", 792 | "windows-sys 0.59.0", 793 | ] 794 | 795 | [[package]] 796 | name = "thiserror" 797 | version = "1.0.69" 798 | source = "registry+https://github.com/rust-lang/crates.io-index" 799 | checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 800 | dependencies = [ 801 | "thiserror-impl", 802 | ] 803 | 804 | [[package]] 805 | name = "thiserror-impl" 806 | version = "1.0.69" 807 | source = "registry+https://github.com/rust-lang/crates.io-index" 808 | checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 809 | dependencies = [ 810 | "proc-macro2", 811 | "quote", 812 | "syn", 813 | ] 814 | 815 | [[package]] 816 | name = "tinyget" 817 | version = "1.1.2" 818 | source = "registry+https://github.com/rust-lang/crates.io-index" 819 | checksum = "5205be93938f3ae916c773ffa3361eaf3b0ee4d40f3752da46d8b1d5311e7285" 820 | dependencies = [ 821 | "native-tls", 822 | "urlencoding", 823 | ] 824 | 825 | [[package]] 826 | name = "unicode-ident" 827 | version = "1.0.18" 828 | source = "registry+https://github.com/rust-lang/crates.io-index" 829 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 830 | 831 | [[package]] 832 | name = "unicode-width" 833 | version = "0.2.0" 834 | source = "registry+https://github.com/rust-lang/crates.io-index" 835 | checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" 836 | 837 | [[package]] 838 | name = "urlencoding" 839 | version = "2.1.3" 840 | source = "registry+https://github.com/rust-lang/crates.io-index" 841 | checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" 842 | 843 | [[package]] 844 | name = "utf8parse" 845 | version = "0.2.2" 846 | source = "registry+https://github.com/rust-lang/crates.io-index" 847 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 848 | 849 | [[package]] 850 | name = "vcpkg" 851 | version = "0.2.15" 852 | source = "registry+https://github.com/rust-lang/crates.io-index" 853 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 854 | 855 | [[package]] 856 | name = "wasi" 857 | version = "0.11.0+wasi-snapshot-preview1" 858 | source = "registry+https://github.com/rust-lang/crates.io-index" 859 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 860 | 861 | [[package]] 862 | name = "wasi" 863 | version = "0.14.2+wasi-0.2.4" 864 | source = "registry+https://github.com/rust-lang/crates.io-index" 865 | checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" 866 | dependencies = [ 867 | "wit-bindgen-rt", 868 | ] 869 | 870 | [[package]] 871 | name = "wasm-bindgen" 872 | version = "0.2.100" 873 | source = "registry+https://github.com/rust-lang/crates.io-index" 874 | checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" 875 | dependencies = [ 876 | "cfg-if", 877 | "once_cell", 878 | "wasm-bindgen-macro", 879 | ] 880 | 881 | [[package]] 882 | name = "wasm-bindgen-backend" 883 | version = "0.2.100" 884 | source = "registry+https://github.com/rust-lang/crates.io-index" 885 | checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" 886 | dependencies = [ 887 | "bumpalo", 888 | "log", 889 | "proc-macro2", 890 | "quote", 891 | "syn", 892 | "wasm-bindgen-shared", 893 | ] 894 | 895 | [[package]] 896 | name = "wasm-bindgen-macro" 897 | version = "0.2.100" 898 | source = "registry+https://github.com/rust-lang/crates.io-index" 899 | checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" 900 | dependencies = [ 901 | "quote", 902 | "wasm-bindgen-macro-support", 903 | ] 904 | 905 | [[package]] 906 | name = "wasm-bindgen-macro-support" 907 | version = "0.2.100" 908 | source = "registry+https://github.com/rust-lang/crates.io-index" 909 | checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" 910 | dependencies = [ 911 | "proc-macro2", 912 | "quote", 913 | "syn", 914 | "wasm-bindgen-backend", 915 | "wasm-bindgen-shared", 916 | ] 917 | 918 | [[package]] 919 | name = "wasm-bindgen-shared" 920 | version = "0.2.100" 921 | source = "registry+https://github.com/rust-lang/crates.io-index" 922 | checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" 923 | dependencies = [ 924 | "unicode-ident", 925 | ] 926 | 927 | [[package]] 928 | name = "web-time" 929 | version = "1.1.0" 930 | source = "registry+https://github.com/rust-lang/crates.io-index" 931 | checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" 932 | dependencies = [ 933 | "js-sys", 934 | "wasm-bindgen", 935 | ] 936 | 937 | [[package]] 938 | name = "which" 939 | version = "5.0.0" 940 | source = "registry+https://github.com/rust-lang/crates.io-index" 941 | checksum = "9bf3ea8596f3a0dd5980b46430f2058dfe2c36a27ccfbb1845d6fbfcd9ba6e14" 942 | dependencies = [ 943 | "either", 944 | "home", 945 | "once_cell", 946 | "rustix 0.38.44", 947 | "windows-sys 0.48.0", 948 | ] 949 | 950 | [[package]] 951 | name = "winapi" 952 | version = "0.3.9" 953 | source = "registry+https://github.com/rust-lang/crates.io-index" 954 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 955 | dependencies = [ 956 | "winapi-i686-pc-windows-gnu", 957 | "winapi-x86_64-pc-windows-gnu", 958 | ] 959 | 960 | [[package]] 961 | name = "winapi-i686-pc-windows-gnu" 962 | version = "0.4.0" 963 | source = "registry+https://github.com/rust-lang/crates.io-index" 964 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 965 | 966 | [[package]] 967 | name = "winapi-x86_64-pc-windows-gnu" 968 | version = "0.4.0" 969 | source = "registry+https://github.com/rust-lang/crates.io-index" 970 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 971 | 972 | [[package]] 973 | name = "windows-sys" 974 | version = "0.48.0" 975 | source = "registry+https://github.com/rust-lang/crates.io-index" 976 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 977 | dependencies = [ 978 | "windows-targets 0.48.5", 979 | ] 980 | 981 | [[package]] 982 | name = "windows-sys" 983 | version = "0.59.0" 984 | source = "registry+https://github.com/rust-lang/crates.io-index" 985 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 986 | dependencies = [ 987 | "windows-targets 0.52.6", 988 | ] 989 | 990 | [[package]] 991 | name = "windows-targets" 992 | version = "0.48.5" 993 | source = "registry+https://github.com/rust-lang/crates.io-index" 994 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 995 | dependencies = [ 996 | "windows_aarch64_gnullvm 0.48.5", 997 | "windows_aarch64_msvc 0.48.5", 998 | "windows_i686_gnu 0.48.5", 999 | "windows_i686_msvc 0.48.5", 1000 | "windows_x86_64_gnu 0.48.5", 1001 | "windows_x86_64_gnullvm 0.48.5", 1002 | "windows_x86_64_msvc 0.48.5", 1003 | ] 1004 | 1005 | [[package]] 1006 | name = "windows-targets" 1007 | version = "0.52.6" 1008 | source = "registry+https://github.com/rust-lang/crates.io-index" 1009 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 1010 | dependencies = [ 1011 | "windows_aarch64_gnullvm 0.52.6", 1012 | "windows_aarch64_msvc 0.52.6", 1013 | "windows_i686_gnu 0.52.6", 1014 | "windows_i686_gnullvm", 1015 | "windows_i686_msvc 0.52.6", 1016 | "windows_x86_64_gnu 0.52.6", 1017 | "windows_x86_64_gnullvm 0.52.6", 1018 | "windows_x86_64_msvc 0.52.6", 1019 | ] 1020 | 1021 | [[package]] 1022 | name = "windows_aarch64_gnullvm" 1023 | version = "0.48.5" 1024 | source = "registry+https://github.com/rust-lang/crates.io-index" 1025 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 1026 | 1027 | [[package]] 1028 | name = "windows_aarch64_gnullvm" 1029 | version = "0.52.6" 1030 | source = "registry+https://github.com/rust-lang/crates.io-index" 1031 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 1032 | 1033 | [[package]] 1034 | name = "windows_aarch64_msvc" 1035 | version = "0.48.5" 1036 | source = "registry+https://github.com/rust-lang/crates.io-index" 1037 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 1038 | 1039 | [[package]] 1040 | name = "windows_aarch64_msvc" 1041 | version = "0.52.6" 1042 | source = "registry+https://github.com/rust-lang/crates.io-index" 1043 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 1044 | 1045 | [[package]] 1046 | name = "windows_i686_gnu" 1047 | version = "0.48.5" 1048 | source = "registry+https://github.com/rust-lang/crates.io-index" 1049 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 1050 | 1051 | [[package]] 1052 | name = "windows_i686_gnu" 1053 | version = "0.52.6" 1054 | source = "registry+https://github.com/rust-lang/crates.io-index" 1055 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 1056 | 1057 | [[package]] 1058 | name = "windows_i686_gnullvm" 1059 | version = "0.52.6" 1060 | source = "registry+https://github.com/rust-lang/crates.io-index" 1061 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 1062 | 1063 | [[package]] 1064 | name = "windows_i686_msvc" 1065 | version = "0.48.5" 1066 | source = "registry+https://github.com/rust-lang/crates.io-index" 1067 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 1068 | 1069 | [[package]] 1070 | name = "windows_i686_msvc" 1071 | version = "0.52.6" 1072 | source = "registry+https://github.com/rust-lang/crates.io-index" 1073 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 1074 | 1075 | [[package]] 1076 | name = "windows_x86_64_gnu" 1077 | version = "0.48.5" 1078 | source = "registry+https://github.com/rust-lang/crates.io-index" 1079 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 1080 | 1081 | [[package]] 1082 | name = "windows_x86_64_gnu" 1083 | version = "0.52.6" 1084 | source = "registry+https://github.com/rust-lang/crates.io-index" 1085 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 1086 | 1087 | [[package]] 1088 | name = "windows_x86_64_gnullvm" 1089 | version = "0.48.5" 1090 | source = "registry+https://github.com/rust-lang/crates.io-index" 1091 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 1092 | 1093 | [[package]] 1094 | name = "windows_x86_64_gnullvm" 1095 | version = "0.52.6" 1096 | source = "registry+https://github.com/rust-lang/crates.io-index" 1097 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 1098 | 1099 | [[package]] 1100 | name = "windows_x86_64_msvc" 1101 | version = "0.48.5" 1102 | source = "registry+https://github.com/rust-lang/crates.io-index" 1103 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 1104 | 1105 | [[package]] 1106 | name = "windows_x86_64_msvc" 1107 | version = "0.52.6" 1108 | source = "registry+https://github.com/rust-lang/crates.io-index" 1109 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 1110 | 1111 | [[package]] 1112 | name = "wit-bindgen-rt" 1113 | version = "0.39.0" 1114 | source = "registry+https://github.com/rust-lang/crates.io-index" 1115 | checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" 1116 | dependencies = [ 1117 | "bitflags", 1118 | ] 1119 | --------------------------------------------------------------------------------