Ver código fonte

feat(tauri.js) add dependency manager command (#829)

Lucas Fernandes Nogueira 5 anos atrás
pai
commit
77282c1e51

+ 6 - 0
.changes/tauri.js-dependency-manager.md

@@ -0,0 +1,6 @@
+---
+"tauri.js": minor
+---
+
+Adds a dependency manager command to the Node.js CLI (`tauri deps`). The manager is able to install and update Rust and the Tauri ecosystem dependencies (npm package, crates, cargo subcommands).
+Usage: `tauri deps install` and `tauri deps update`.

+ 22 - 0
cli/tauri.js/bin/tauri-deps.js

@@ -0,0 +1,22 @@
+async function run() {
+  const {
+    installDependencies,
+    updateDependencies
+  } = require('../dist/api/dependency-manager')
+
+  const choice = process.argv[2]
+  if (choice === 'install') {
+    await installDependencies()
+  } else if (choice === 'update') {
+    await updateDependencies()
+  } else {
+    console.log(`
+    Description
+      Tauri dependency management script
+    Usage
+      $ tauri deps [install|update]
+  `)
+  }
+}
+
+run()

+ 4 - 2
cli/tauri.js/bin/tauri.js

@@ -1,6 +1,6 @@
 #!/usr/bin/env node
 
-const cmds = ['init', 'dev', 'build', 'help', 'icon', 'info']
+const cmds = ['init', 'dev', 'build', 'help', 'icon', 'info', 'deps']
 
 const cmd = process.argv[2]
 /**
@@ -45,6 +45,8 @@ const tauri = function (command) {
     console.log(`Invalid command ${command}. Use one of ${cmds.join(',')}.`)
   }
 }
-module.exports = { tauri }
+module.exports = {
+  tauri
+}
 
 tauri(cmd)

+ 3 - 0
cli/tauri.js/package.json

@@ -70,6 +70,7 @@
     "ms": "2.1.2",
     "png2icons": "2.0.1",
     "read-chunk": "3.2.0",
+    "semver": "^7.3.2",
     "sharp": "0.25.4",
     "webpack-merge": "5.0.9",
     "webpack-shell-plugin": "0.5.0"
@@ -89,9 +90,11 @@
     "@types/http-proxy": "1.17.4",
     "@types/imagemin": "7.0.0",
     "@types/imagemin-optipng": "5.2.0",
+    "@types/inquirer": "^6.5.0",
     "@types/jsdom": "16.2.3",
     "@types/lodash": "4.14.157",
     "@types/ms": "0.7.31",
+    "@types/semver": "^7.3.1",
     "@types/sharp": "0.25.0",
     "@types/webpack-merge": "4.1.5",
     "@typescript-eslint/eslint-plugin": "3.6.0",

BIN
cli/tauri.js/scripts/rustup-init.exe


+ 553 - 0
cli/tauri.js/scripts/rustup-init.sh

@@ -0,0 +1,553 @@
+#!/bin/sh
+# shellcheck shell=dash
+
+# This is just a little script that can be downloaded from the internet to
+# install rustup. It just does platform detection, downloads the installer
+# and runs it.
+
+# It runs on Unix shells like {a,ba,da,k,z}sh. It uses the common `local`
+# extension. Note: Most shells limit `local` to 1 var per line, contra bash.
+
+set -u
+
+# If RUSTUP_UPDATE_ROOT is unset or empty, default it.
+RUSTUP_UPDATE_ROOT="${RUSTUP_UPDATE_ROOT:-https://static.rust-lang.org/rustup}"
+
+#XXX: If you change anything here, please make the same changes in setup_mode.rs
+usage() {
+    cat 1>&2 <<EOF
+rustup-init 1.22.1 (76644d669 2020-07-08)
+The installer for rustup
+
+USAGE:
+    rustup-init [FLAGS] [OPTIONS]
+
+FLAGS:
+    -v, --verbose           Enable verbose output
+    -q, --quiet             Disable progress output
+    -y                      Disable confirmation prompt.
+        --no-modify-path    Don't configure the PATH environment variable
+    -h, --help              Prints help information
+    -V, --version           Prints version information
+
+OPTIONS:
+        --default-host <default-host>              Choose a default host triple
+        --default-toolchain <default-toolchain>    Choose a default toolchain to install
+        --default-toolchain none                   Do not install any toolchains
+        --profile [minimal|default|complete]       Choose a profile
+    -c, --component <components>...                Component name to also install
+    -t, --target <targets>...                      Target name to also install
+EOF
+}
+
+main() {
+    downloader --check
+    need_cmd uname
+    need_cmd mktemp
+    need_cmd chmod
+    need_cmd mkdir
+    need_cmd rm
+    need_cmd rmdir
+
+    get_architecture || return 1
+    local _arch="$RETVAL"
+    assert_nz "$_arch" "arch"
+
+    local _ext=""
+    case "$_arch" in
+        *windows*)
+            _ext=".exe"
+            ;;
+    esac
+
+    local _url="${RUSTUP_UPDATE_ROOT}/dist/${_arch}/rustup-init${_ext}"
+
+    local _dir
+    _dir="$(mktemp -d 2>/dev/null || ensure mktemp -d -t rustup)"
+    local _file="${_dir}/rustup-init${_ext}"
+
+    local _ansi_escapes_are_valid=false
+    if [ -t 2 ]; then
+        if [ "${TERM+set}" = 'set' ]; then
+            case "$TERM" in
+                xterm*|rxvt*|urxvt*|linux*|vt*)
+                    _ansi_escapes_are_valid=true
+                ;;
+            esac
+        fi
+    fi
+
+    # check if we have to use /dev/tty to prompt the user
+    local need_tty=yes
+    for arg in "$@"; do
+        case "$arg" in
+            -h|--help)
+                usage
+                exit 0
+                ;;
+            -y)
+                # user wants to skip the prompt -- we don't need /dev/tty
+                need_tty=no
+                ;;
+            *)
+                ;;
+        esac
+    done
+
+    if $_ansi_escapes_are_valid; then
+        printf "\33[1minfo:\33[0m downloading installer\n" 1>&2
+    else
+        printf '%s\n' 'info: downloading installer' 1>&2
+    fi
+
+    ensure mkdir -p "$_dir"
+    ensure downloader "$_url" "$_file" "$_arch"
+    ensure chmod u+x "$_file"
+    if [ ! -x "$_file" ]; then
+        printf '%s\n' "Cannot execute $_file (likely because of mounting /tmp as noexec)." 1>&2
+        printf '%s\n' "Please copy the file to a location where you can execute binaries and run ./rustup-init${_ext}." 1>&2
+        exit 1
+    fi
+
+    if [ "$need_tty" = "yes" ]; then
+        # The installer is going to want to ask for confirmation by
+        # reading stdin.  This script was piped into `sh` though and
+        # doesn't have stdin to pass to its children. Instead we're going
+        # to explicitly connect /dev/tty to the installer's stdin.
+        if [ ! -t 1 ]; then
+            err "Unable to run interactively. Run with -y to accept defaults, --help for additional options"
+        fi
+
+        ignore "$_file" "$@" < /dev/tty
+    else
+        ignore "$_file" "$@"
+    fi
+
+    local _retval=$?
+
+    ignore rm "$_file"
+    ignore rmdir "$_dir"
+
+    return "$_retval"
+}
+
+get_bitness() {
+    need_cmd head
+    # Architecture detection without dependencies beyond coreutils.
+    # ELF files start out "\x7fELF", and the following byte is
+    #   0x01 for 32-bit and
+    #   0x02 for 64-bit.
+    # The printf builtin on some shells like dash only supports octal
+    # escape sequences, so we use those.
+    local _current_exe_head
+    _current_exe_head=$(head -c 5 /proc/self/exe )
+    if [ "$_current_exe_head" = "$(printf '\177ELF\001')" ]; then
+        echo 32
+    elif [ "$_current_exe_head" = "$(printf '\177ELF\002')" ]; then
+        echo 64
+    else
+        err "unknown platform bitness"
+    fi
+}
+
+get_endianness() {
+    local cputype=$1
+    local suffix_eb=$2
+    local suffix_el=$3
+
+    # detect endianness without od/hexdump, like get_bitness() does.
+    need_cmd head
+    need_cmd tail
+
+    local _current_exe_endianness
+    _current_exe_endianness="$(head -c 6 /proc/self/exe | tail -c 1)"
+    if [ "$_current_exe_endianness" = "$(printf '\001')" ]; then
+        echo "${cputype}${suffix_el}"
+    elif [ "$_current_exe_endianness" = "$(printf '\002')" ]; then
+        echo "${cputype}${suffix_eb}"
+    else
+        err "unknown platform endianness"
+    fi
+}
+
+get_architecture() {
+    local _ostype _cputype _bitness _arch _clibtype
+    _ostype="$(uname -s)"
+    _cputype="$(uname -m)"
+    _clibtype="gnu"
+
+    if [ "$_ostype" = Linux ]; then
+        if [ "$(uname -o)" = Android ]; then
+            _ostype=Android
+        fi
+        if ldd --version 2>&1 | grep -q 'musl'; then
+            _clibtype="musl"
+        fi
+    fi
+
+    if [ "$_ostype" = Darwin ] && [ "$_cputype" = i386 ]; then
+        # Darwin `uname -m` lies
+        if sysctl hw.optional.x86_64 | grep -q ': 1'; then
+            _cputype=x86_64
+        fi
+    fi
+
+    case "$_ostype" in
+
+        Android)
+            _ostype=linux-android
+            ;;
+
+        Linux)
+            _ostype=unknown-linux-$_clibtype
+            _bitness=$(get_bitness)
+            ;;
+
+        FreeBSD)
+            _ostype=unknown-freebsd
+            ;;
+
+        NetBSD)
+            _ostype=unknown-netbsd
+            ;;
+
+        DragonFly)
+            _ostype=unknown-dragonfly
+            ;;
+
+        Darwin)
+            _ostype=apple-darwin
+            ;;
+
+        MINGW* | MSYS* | CYGWIN*)
+            _ostype=pc-windows-gnu
+            ;;
+
+        *)
+            err "unrecognized OS type: $_ostype"
+            ;;
+
+    esac
+
+    case "$_cputype" in
+
+        i386 | i486 | i686 | i786 | x86)
+            _cputype=i686
+            ;;
+
+        xscale | arm)
+            _cputype=arm
+            if [ "$_ostype" = "linux-android" ]; then
+                _ostype=linux-androideabi
+            fi
+            ;;
+
+        armv6l)
+            _cputype=arm
+            if [ "$_ostype" = "linux-android" ]; then
+                _ostype=linux-androideabi
+            else
+                _ostype="${_ostype}eabihf"
+            fi
+            ;;
+
+        armv7l | armv8l)
+            _cputype=armv7
+            if [ "$_ostype" = "linux-android" ]; then
+                _ostype=linux-androideabi
+            else
+                _ostype="${_ostype}eabihf"
+            fi
+            ;;
+
+        aarch64)
+            _cputype=aarch64
+            ;;
+
+        x86_64 | x86-64 | x64 | amd64)
+            _cputype=x86_64
+            ;;
+
+        mips)
+            _cputype=$(get_endianness mips '' el)
+            ;;
+
+        mips64)
+            if [ "$_bitness" -eq 64 ]; then
+                # only n64 ABI is supported for now
+                _ostype="${_ostype}abi64"
+                _cputype=$(get_endianness mips64 '' el)
+            fi
+            ;;
+
+        ppc)
+            _cputype=powerpc
+            ;;
+
+        ppc64)
+            _cputype=powerpc64
+            ;;
+
+        ppc64le)
+            _cputype=powerpc64le
+            ;;
+
+        s390x)
+            _cputype=s390x
+            ;;
+        riscv64)
+            _cputype=riscv64gc
+            ;;
+        *)
+            err "unknown CPU type: $_cputype"
+
+    esac
+
+    # Detect 64-bit linux with 32-bit userland
+    if [ "${_ostype}" = unknown-linux-gnu ] && [ "${_bitness}" -eq 32 ]; then
+        case $_cputype in
+            x86_64)
+                _cputype=i686
+                ;;
+            mips64)
+                _cputype=$(get_endianness mips '' el)
+                ;;
+            powerpc64)
+                _cputype=powerpc
+                ;;
+            aarch64)
+                _cputype=armv7
+                if [ "$_ostype" = "linux-android" ]; then
+                    _ostype=linux-androideabi
+                else
+                    _ostype="${_ostype}eabihf"
+                fi
+                ;;
+            riscv64gc)
+                err "riscv64 with 32-bit userland unsupported"
+                ;;
+        esac
+    fi
+
+    # Detect armv7 but without the CPU features Rust needs in that build,
+    # and fall back to arm.
+    # See https://github.com/rust-lang/rustup.rs/issues/587.
+    if [ "$_ostype" = "unknown-linux-gnueabihf" ] && [ "$_cputype" = armv7 ]; then
+        if ensure grep '^Features' /proc/cpuinfo | grep -q -v neon; then
+            # At least one processor does not have NEON.
+            _cputype=arm
+        fi
+    fi
+
+    _arch="${_cputype}-${_ostype}"
+
+    RETVAL="$_arch"
+}
+
+say() {
+    printf 'rustup: %s\n' "$1"
+}
+
+err() {
+    say "$1" >&2
+    exit 1
+}
+
+need_cmd() {
+    if ! check_cmd "$1"; then
+        err "need '$1' (command not found)"
+    fi
+}
+
+check_cmd() {
+    command -v "$1" > /dev/null 2>&1
+}
+
+assert_nz() {
+    if [ -z "$1" ]; then err "assert_nz $2"; fi
+}
+
+# Run a command that should never fail. If the command fails execution
+# will immediately terminate with an error showing the failing
+# command.
+ensure() {
+    if ! "$@"; then err "command failed: $*"; fi
+}
+
+# This is just for indicating that commands' results are being
+# intentionally ignored. Usually, because it's being executed
+# as part of error handling.
+ignore() {
+    "$@"
+}
+
+# This wraps curl or wget. Try curl first, if not installed,
+# use wget instead.
+downloader() {
+    local _dld
+    local _ciphersuites
+    if check_cmd curl; then
+        _dld=curl
+    elif check_cmd wget; then
+        _dld=wget
+    else
+        _dld='curl or wget' # to be used in error message of need_cmd
+    fi
+
+    if [ "$1" = --check ]; then
+        need_cmd "$_dld"
+    elif [ "$_dld" = curl ]; then
+        get_ciphersuites_for_curl
+        _ciphersuites="$RETVAL"
+        if [ -n "$_ciphersuites" ]; then
+            curl --proto '=https' --tlsv1.2 --ciphers "$_ciphersuites" --silent --show-error --fail --location "$1" --output "$2"
+        else
+            echo "Warning: Not enforcing strong cipher suites for TLS, this is potentially less secure"
+            if ! check_help_for "$3" curl --proto --tlsv1.2; then
+                echo "Warning: Not enforcing TLS v1.2, this is potentially less secure"
+                curl --silent --show-error --fail --location "$1" --output "$2"
+            else
+                curl --proto '=https' --tlsv1.2 --silent --show-error --fail --location "$1" --output "$2"
+            fi
+        fi
+    elif [ "$_dld" = wget ]; then
+        get_ciphersuites_for_wget
+        _ciphersuites="$RETVAL"
+        if [ -n "$_ciphersuites" ]; then
+            wget --https-only --secure-protocol=TLSv1_2 --ciphers "$_ciphersuites" "$1" -O "$2"
+        else
+            echo "Warning: Not enforcing strong cipher suites for TLS, this is potentially less secure"
+            if ! check_help_for "$3" wget --https-only --secure-protocol; then
+                echo "Warning: Not enforcing TLS v1.2, this is potentially less secure"
+                wget "$1" -O "$2"
+            else
+                wget --https-only --secure-protocol=TLSv1_2 "$1" -O "$2"
+            fi
+        fi
+    else
+        err "Unknown downloader"   # should not reach here
+    fi
+}
+
+check_help_for() {
+    local _arch
+    local _cmd
+    local _arg
+    _arch="$1"
+    shift
+    _cmd="$1"
+    shift
+
+    case "$_arch" in
+
+        # If we're running on OS-X, older than 10.13, then we always
+        # fail to find these options to force fallback
+        *darwin*)
+        if check_cmd sw_vers; then
+            if [ "$(sw_vers -productVersion | cut -d. -f2)" -lt 13 ]; then
+                # Older than 10.13
+                echo "Warning: Detected OS X platform older than 10.13"
+                return 1
+            fi
+        fi
+        ;;
+
+    esac
+
+    for _arg in "$@"; do
+        if ! "$_cmd" --help | grep -q -- "$_arg"; then
+            return 1
+        fi
+    done
+
+    true # not strictly needed
+}
+
+# Return cipher suite string specified by user, otherwise return strong TLS 1.2-1.3 cipher suites
+# if support by local tools is detected. Detection currently supports these curl backends: 
+# GnuTLS and OpenSSL (possibly also LibreSSL and BoringSSL). Return value can be empty.
+get_ciphersuites_for_curl() {
+    if [ -n "${RUSTUP_TLS_CIPHERSUITES-}" ]; then
+        # user specified custom cipher suites, assume they know what they're doing
+        RETVAL="$RUSTUP_TLS_CIPHERSUITES"
+        return
+    fi
+
+    local _openssl_syntax="no"
+    local _gnutls_syntax="no"
+    local _backend_supported="yes"
+    if curl -V | grep -q ' OpenSSL/'; then
+        _openssl_syntax="yes"
+    elif curl -V | grep -iq ' LibreSSL/'; then
+        _openssl_syntax="yes"
+    elif curl -V | grep -iq ' BoringSSL/'; then
+        _openssl_syntax="yes"
+    elif curl -V | grep -iq ' GnuTLS/'; then
+        _gnutls_syntax="yes"
+    else
+        _backend_supported="no"
+    fi
+
+    local _args_supported="no"
+    if [ "$_backend_supported" = "yes" ]; then
+        # "unspecified" is for arch, allows for possibility old OS using macports, homebrew, etc.
+        if check_help_for "notspecified" "curl" "--tlsv1.2" "--ciphers" "--proto"; then
+            _args_supported="yes"
+        fi
+    fi
+
+    local _cs=""
+    if [ "$_args_supported" = "yes" ]; then
+        if [ "$_openssl_syntax" = "yes" ]; then
+            _cs=$(get_strong_ciphersuites_for "openssl")
+        elif [ "$_gnutls_syntax" = "yes" ]; then
+            _cs=$(get_strong_ciphersuites_for "gnutls")
+        fi
+    fi
+
+    RETVAL="$_cs"
+}
+
+# Return cipher suite string specified by user, otherwise return strong TLS 1.2-1.3 cipher suites
+# if support by local tools is detected. Detection currently supports these wget backends: 
+# GnuTLS and OpenSSL (possibly also LibreSSL and BoringSSL). Return value can be empty.
+get_ciphersuites_for_wget() {
+    if [ -n "${RUSTUP_TLS_CIPHERSUITES-}" ]; then
+        # user specified custom cipher suites, assume they know what they're doing
+        RETVAL="$RUSTUP_TLS_CIPHERSUITES"
+        return
+    fi
+
+    local _cs=""
+    if wget -V | grep -q '\-DHAVE_LIBSSL'; then
+        # "unspecified" is for arch, allows for possibility old OS using macports, homebrew, etc.
+        if check_help_for "notspecified" "wget" "TLSv1_2" "--ciphers" "--https-only" "--secure-protocol"; then
+            _cs=$(get_strong_ciphersuites_for "openssl")
+        fi
+    elif wget -V | grep -q '\-DHAVE_LIBGNUTLS'; then
+        # "unspecified" is for arch, allows for possibility old OS using macports, homebrew, etc.
+        if check_help_for "notspecified" "wget" "TLSv1_2" "--ciphers" "--https-only" "--secure-protocol"; then
+            _cs=$(get_strong_ciphersuites_for "gnutls")
+        fi
+    fi
+
+    RETVAL="$_cs"
+}
+
+# Return strong TLS 1.2-1.3 cipher suites in OpenSSL or GnuTLS syntax. TLS 1.2 
+# excludes non-ECDHE and non-AEAD cipher suites. DHE is excluded due to bad 
+# DH params often found on servers (see RFC 7919). Sequence matches or is
+# similar to Firefox 68 ESR with weak cipher suites disabled via about:config.  
+# $1 must be openssl or gnutls.
+get_strong_ciphersuites_for() {
+    if [ "$1" = "openssl" ]; then
+        # OpenSSL is forgiving of unknown values, no problems with TLS 1.3 values on versions that don't support it yet.
+        echo "TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384"
+    elif [ "$1" = "gnutls" ]; then
+        # GnuTLS isn't forgiving of unknown values, so this may require a GnuTLS version that supports TLS 1.3 even if wget doesn't.
+        # Begin with SECURE128 (and higher) then remove/add to build cipher suites. Produces same 9 cipher suites as OpenSSL but in slightly different order.
+        echo "SECURE128:-VERS-SSL3.0:-VERS-TLS1.0:-VERS-TLS1.1:-VERS-DTLS-ALL:-CIPHER-ALL:-MAC-ALL:-KX-ALL:+AEAD:+ECDHE-ECDSA:+ECDHE-RSA:+AES-128-GCM:+CHACHA20-POLY1305:+AES-256-GCM"
+    fi 
+}
+
+main "$@" || exit 1

+ 61 - 0
cli/tauri.js/src/api/dependency-manager/cargo-commands.ts

@@ -0,0 +1,61 @@
+import { ManagementType, Result } from './types'
+import { getCrateLatestVersion, semverLt } from './util'
+import getScriptVersion from '../../helpers/get-script-version'
+import logger from '../../helpers/logger'
+import { sync as spawnSync } from 'cross-spawn'
+import inquirer from 'inquirer'
+
+const log = logger('dependency:cargo-commands')
+
+const dependencies = ['tauri-bundler']
+
+async function manageDependencies(managementType: ManagementType): Promise<Result> {
+  const installedDeps = []
+  const updatedDeps = []
+
+  for (const dependency of dependencies) {
+    const currentVersion = getScriptVersion('cargo', [dependency])
+    if (currentVersion === null) {
+      log(`Installing ${dependency}...`)
+      spawnSync('cargo', ['install', dependency])
+      installedDeps.push(dependency)
+    } else if (managementType === ManagementType.Update) {
+      const latestVersion = await getCrateLatestVersion(dependency)
+      if (semverLt(currentVersion, latestVersion)) {
+        const inquired = await inquirer.prompt([{
+          type: 'confirm',
+          name: 'answer',
+          message: `[CARGO COMMANDS] "${dependency}" latest version is ${latestVersion}. Do you want to update?`,
+          default: false
+        }])
+        if (inquired.answer) {
+          spawnSync('cargo', ['install', dependency, '--force'])
+          updatedDeps.push(dependency)
+        }
+      } else {
+        log(`"${dependency}" is up to date`)
+      }
+    } else {
+      log(`"${dependency}" is already installed`)
+    }
+  }
+
+  const result: Result = new Map<ManagementType, string[]>()
+  result.set(ManagementType.Install, installedDeps)
+  result.set(ManagementType.Update, updatedDeps)
+
+  return result
+}
+
+async function install(): Promise<Result> {
+  return await manageDependencies(ManagementType.Install)
+}
+
+async function update(): Promise<Result> {
+  return await manageDependencies(ManagementType.Update)
+}
+
+export {
+  install,
+  update
+}

+ 108 - 0
cli/tauri.js/src/api/dependency-manager/cargo-crates.ts

@@ -0,0 +1,108 @@
+import { spawnSync } from './../../helpers/spawn'
+import { CargoManifest, CargoManifestDependency, CargoLock } from './../../types/cargo'
+import { ManagementType, Result } from './types'
+import { getCrateLatestVersion, semverLt } from './util'
+import logger from '../../helpers/logger'
+import { resolve as appResolve, tauriDir } from '../../helpers/app-paths'
+import { readFileSync, writeFileSync, existsSync } from 'fs'
+import toml from '@tauri-apps/toml'
+import inquirer from 'inquirer'
+
+const log = logger('dependency:crates')
+
+const dependencies = ['tauri']
+
+function readToml<T>(tomlPath: string): T | null {
+  if (existsSync(tomlPath)) {
+    const manifest = readFileSync(tomlPath).toString()
+    return toml.parse(manifest) as any as T
+  }
+  return null
+}
+
+function dependencyDefinition(version: string): CargoManifestDependency {
+  return { version: version.substring(0, version.lastIndexOf('.')) }
+}
+
+async function manageDependencies(managementType: ManagementType): Promise<Result> {
+  const installedDeps = []
+  const updatedDeps = []
+  const result: Result = new Map<ManagementType, string[]>()
+
+  const manifest = readToml<CargoManifest>(appResolve.tauri('Cargo.toml'))
+
+  if (manifest === null) {
+    log('Cargo.toml not found. Skipping crates check...')
+    return result
+  }
+
+  const lockPath = appResolve.tauri('Cargo.lock')
+  if (!existsSync(lockPath)) {
+    spawnSync('cargo', ['generate-lockfile'], tauriDir)
+  }
+  const lock = readToml<CargoLock>(lockPath)
+
+  for (const dependency of dependencies) {
+    const lockPackages = lock ? lock.package.filter(pkg => pkg.name === dependency) : []
+    // eslint-disable-next-line security/detect-object-injection
+    const manifestDep = manifest.dependencies[dependency]
+    const currentVersion = lockPackages.length === 1
+      ? lockPackages[0].version
+      : (typeof manifestDep === 'string' ? manifestDep : manifestDep?.version)
+    if (currentVersion === undefined) {
+      log(`Installing ${dependency}...`)
+      const latestVersion = await getCrateLatestVersion(dependency)
+      // eslint-disable-next-line security/detect-object-injection
+      manifest.dependencies[dependency] = dependencyDefinition(latestVersion)
+      installedDeps.push(dependency)
+    } else if (managementType === ManagementType.Update) {
+      const latestVersion = await getCrateLatestVersion(dependency)
+      if (semverLt(currentVersion, latestVersion)) {
+        const inquired = await inquirer.prompt([{
+          type: 'confirm',
+          name: 'answer',
+          message: `[CRATES] "${dependency}" latest version is ${latestVersion}. Do you want to update?`,
+          default: false
+        }])
+        if (inquired.answer) {
+          log(`Updating ${dependency}...`)
+          // eslint-disable-next-line security/detect-object-injection
+          manifest.dependencies[dependency] = dependencyDefinition(latestVersion)
+          updatedDeps.push(dependency)
+        }
+      } else {
+        log(`"${dependency}" is up to date`)
+      }
+    } else {
+      log(`"${dependency}" is already installed`)
+    }
+  }
+
+  if (installedDeps.length || updatedDeps.length) {
+    writeFileSync(appResolve.tauri('Cargo.toml'), toml.stringify(manifest as any))
+  }
+  if (updatedDeps.length) {
+    if (!existsSync(appResolve.tauri('Cargo.lock'))) {
+      spawnSync('cargo', ['generate-lockfile'], tauriDir)
+    }
+    spawnSync('cargo', ['update', ...updatedDeps.reduce<string[]>((initialValue, dep) => [...initialValue, '-p', dep], [])], tauriDir)
+  }
+
+  result.set(ManagementType.Install, installedDeps)
+  result.set(ManagementType.Update, updatedDeps)
+
+  return result
+}
+
+async function install(): Promise<Result> {
+  return await manageDependencies(ManagementType.Install)
+}
+
+async function update(): Promise<Result> {
+  return await manageDependencies(ManagementType.Update)
+}
+
+export {
+  install,
+  update
+}

+ 24 - 0
cli/tauri.js/src/api/dependency-manager/index.ts

@@ -0,0 +1,24 @@
+import logger from '../../helpers/logger'
+import * as rust from './rust'
+import * as cargoCommands from './cargo-commands'
+import * as cargoCrates from './cargo-crates'
+import * as npmPackages from './npm-packages'
+
+const log = logger('dependency:manager')
+
+module.exports = {
+  async installDependencies() {
+    log('Installing missing dependencies...')
+    rust.install()
+    await cargoCommands.install()
+    await cargoCrates.install()
+    await npmPackages.install()
+  },
+  async updateDependencies() {
+    log('Updating dependencies...')
+    rust.update()
+    await cargoCommands.update()
+    await cargoCrates.update()
+    await npmPackages.update()
+  }
+}

+ 60 - 0
cli/tauri.js/src/api/dependency-manager/npm-packages.ts

@@ -0,0 +1,60 @@
+import { ManagementType, Result } from './types'
+import { getNpmLatestVersion, getNpmPackageVersion, installNpmPackage, updateNpmPackage, semverLt } from './util'
+import logger from '../../helpers/logger'
+import inquirer from 'inquirer'
+
+const log = logger('dependency:npm-packages')
+
+const dependencies = ['tauri']
+
+async function manageDependencies(managementType: ManagementType): Promise<Result> {
+  const installedDeps = []
+  const updatedDeps = []
+
+  for (const dependency of dependencies) {
+    const currentVersion = await getNpmPackageVersion(dependency)
+    if (currentVersion === null) {
+      log(`Installing ${dependency}...`)
+      installNpmPackage(dependency)
+      installedDeps.push(dependency)
+    } else if (managementType === ManagementType.Update) {
+      const latestVersion = getNpmLatestVersion(dependency)
+      if (semverLt(currentVersion, latestVersion)) {
+        const inquired = await inquirer.prompt([{
+          type: 'confirm',
+          name: 'answer',
+          message: `[NPM]: "${dependency}" latest version is ${latestVersion}. Do you want to update?`,
+          default: false
+        }])
+        if (inquired.answer) {
+          log(`Updating ${dependency}...`)
+          updateNpmPackage(dependency)
+          updatedDeps.push(dependency)
+        }
+      } else {
+        log(`"${dependency}" is up to date`)
+      }
+    } else {
+      log(`"${dependency}" is already installed`)
+    }
+  }
+
+  const result: Result = new Map<ManagementType, string[]>()
+  result.set(ManagementType.Install, installedDeps)
+  result.set(ManagementType.Update, updatedDeps)
+
+  return result
+}
+
+async function install(): Promise<Result> {
+  return await manageDependencies(ManagementType.Install)
+}
+
+async function update(): Promise<Result> {
+  return await manageDependencies(ManagementType.Update)
+}
+
+export {
+  install,
+  update
+}

+ 57 - 0
cli/tauri.js/src/api/dependency-manager/rust.ts

@@ -0,0 +1,57 @@
+import { ManagementType } from './types'
+import { spawnSync } from '../../helpers/spawn'
+import getScriptVersion from '../../helpers/get-script-version'
+import logger from '../../helpers/logger'
+import { createWriteStream, unlinkSync } from 'fs'
+import { resolve } from 'path'
+import { platform } from 'os'
+import https from 'https'
+
+const log = logger('dependency:rust')
+
+async function download(url: string, dest: string): Promise<void> {
+  const file = createWriteStream(dest)
+  return await new Promise((resolve, reject) => {
+    https.get(url, response => {
+      response.pipe(file)
+      file.on('finish', function() {
+        file.close()
+        resolve()
+      })
+    }).on('error', function(err) {
+      unlinkSync(dest)
+      reject(err.message)
+    })
+  })
+};
+
+function installRustup(): void {
+  if (platform() === 'win32') {
+    return spawnSync('powershell', [resolve(__dirname, '../../scripts/rustup-init.exe')], process.cwd())
+  }
+  return spawnSync('/bin/sh', [resolve(__dirname, '../../scripts/rustup-init.sh')], process.cwd())
+}
+
+function manageDependencies(managementType: ManagementType): void {
+  if (getScriptVersion('rustup') === null) {
+    log('Installing rustup...')
+    installRustup()
+  }
+
+  if (managementType === ManagementType.Update) {
+    spawnSync('rustup', ['update'], process.cwd())
+  }
+}
+
+function install(): void {
+  return manageDependencies(ManagementType.Install)
+}
+
+function update(): void {
+  return manageDependencies(ManagementType.Update)
+}
+
+export {
+  install,
+  update
+}

+ 6 - 0
cli/tauri.js/src/api/dependency-manager/types.ts

@@ -0,0 +1,6 @@
+export enum ManagementType {
+  Install,
+  Update
+}
+
+export type Result = Map<ManagementType, string[]>

+ 83 - 0
cli/tauri.js/src/api/dependency-manager/util.ts

@@ -0,0 +1,83 @@
+import https from 'https'
+import { IncomingMessage } from 'http'
+import { spawnSync } from '../../helpers/spawn'
+import { sync as crossSpawnSync } from 'cross-spawn'
+import { appDir, resolve as appResolve } from '../../helpers/app-paths'
+import { existsSync } from 'fs'
+import semver from 'semver'
+
+const BASE_URL = 'https://docs.rs/crate/'
+
+async function getCrateLatestVersion(crateName: string): Promise<string> {
+  return await new Promise((resolve, reject) => {
+    const url = `${BASE_URL}${crateName}`
+    https.get(url, (res: IncomingMessage) => {
+      if (res.statusCode !== 302 || !res.headers.location) {
+        reject(res)
+      } else {
+        const version = res.headers.location.replace(url + '/', '')
+        resolve(version)
+      }
+    })
+  })
+}
+
+function getNpmLatestVersion(packageName: string): string {
+  const child = crossSpawnSync('npm', ['show', packageName, 'version'], { cwd: appDir })
+  return String(child.output[1]).replace('\n', '')
+}
+
+async function getNpmPackageVersion(packageName: string): Promise<string | null> {
+  return await new Promise((resolve) => {
+    const child = crossSpawnSync('npm', ['list', packageName, 'version', '--depth', '0'], { cwd: appDir })
+    const output = String(child.output[1])
+    // eslint-disable-next-line security/detect-non-literal-regexp
+    const matches = new RegExp(packageName + '@(\\S+)', 'g').exec(output)
+    if (matches?.[1]) {
+      resolve(matches[1])
+    } else {
+      resolve(null)
+    }
+  })
+}
+
+function installNpmPackage(packageName: string): void {
+  const usesYarn = existsSync(appResolve.app('yarn.lock'))
+  if (usesYarn) {
+    spawnSync('yarn', ['add', packageName], appDir)
+  } else {
+    spawnSync('npm', ['install', packageName], appDir)
+  }
+}
+
+function updateNpmPackage(packageName: string): void {
+  const usesYarn = existsSync(appResolve.app('yarn.lock'))
+  if (usesYarn) {
+    spawnSync('yarn', ['upgrade', packageName, '--latest'], appDir)
+  } else {
+    spawnSync('npm', ['install', `${packageName}@latest`], appDir)
+  }
+}
+
+function padVersion(version: string): string {
+  let count = (version.match(/\./g) ?? []).length
+  while (count < 2) {
+    count++
+    version += '.0'
+  }
+  return version
+}
+
+function semverLt(first: string, second: string): boolean {
+  return semver.lt(padVersion(first), padVersion(second))
+}
+
+export {
+  getCrateLatestVersion,
+  getNpmLatestVersion,
+  getNpmPackageVersion,
+  installNpmPackage,
+  updateNpmPackage,
+  padVersion,
+  semverLt
+}

+ 9 - 12
cli/tauri.js/src/api/info.ts

@@ -10,6 +10,7 @@ import { TauriConfig } from './../types/config'
 import { CargoLock, CargoManifest } from '../types/cargo'
 import nonWebpackRequire from '../helpers/non-webpack-require'
 import { version } from '../../package.json'
+import getScriptVersion from '../helpers/get-script-version'
 
 interface DirInfo {
   path: string
@@ -45,17 +46,13 @@ function getVersion(
   args: string[] = [],
   formatter?: (output: string) => string
 ): string {
-  try {
-    const child = spawn(command, [...args, '--version'])
-    if (child.status === 0) {
-      const output = String(child.output[1])
-      return chalk
-        .green(formatter === undefined ? output : formatter(output))
-        .replace('\n', '')
-    }
-    return chalk.red('Not installed')
-  } catch (err) {
+  const version = getScriptVersion(command, args)
+  if (version === null) {
     return chalk.red('Not installed')
+  } else {
+    return chalk
+      .green(formatter === undefined ? version : formatter(version))
+      .replace('\n', '')
   }
 }
 
@@ -68,7 +65,7 @@ interface Info {
 function printInfo(info: Info): void {
   console.log(
     `${info.section ? '\n' : ''}${info.key}${
-      info.value === undefined ? '' : ' - ' + info.value
+    info.value === undefined ? '' : ' - ' + info.value
     }`
   )
 }
@@ -173,7 +170,7 @@ function printAppInfo(tauriDir: string): void {
         ? chalk.green(config.build.devPath)
         : chalk.red('unset')
     })
-  } catch (_) {}
+  } catch (_) { }
 }
 
 module.exports = () => {

+ 17 - 0
cli/tauri.js/src/helpers/get-script-version.ts

@@ -0,0 +1,17 @@
+import { sync as spawn } from 'cross-spawn'
+
+export default function getVersion(
+  command: string,
+  args: string[] = []
+): string | null {
+  try {
+    const child = spawn(command, [...args, '--version'])
+    if (child.status === 0) {
+      const output = String(child.output[1])
+      return output
+    }
+    return null
+  } catch (err) {
+    return null
+  }
+}

+ 1 - 0
cli/tauri.js/webpack.config.js

@@ -9,6 +9,7 @@ module.exports = {
     'api/init': './src/api/init.ts',
     'api/tauricon': './src/api/tauricon.ts',
     'api/info': './src/api/info.ts',
+    'api/dependency-manager': './src/api/dependency-manager/index.ts',
     'helpers/tauri-config': './src/helpers/tauri-config.ts',
     'helpers/spawn': './src/helpers/spawn.ts'
   },

+ 110 - 60
cli/tauri.js/yarn.lock

@@ -794,14 +794,6 @@
     "@babel/helper-plugin-utils" "^7.10.4"
     "@babel/plugin-transform-typescript" "^7.10.4"
 
-"@babel/runtime-corejs3@^7.8.3":
-  version "7.10.4"
-  resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.10.4.tgz#f29fc1990307c4c57b10dbd6ce667b27159d9e0d"
-  integrity sha512-BFlgP2SoLO9HJX9WBwN67gHWMBhDX/eDz64Jajd6mR/UAUzqrNMm99d4qHnVaKscAElZoFiPv+JpR/Siud5lXw==
-  dependencies:
-    core-js-pure "^3.0.0"
-    regenerator-runtime "^0.13.4"
-
 "@babel/runtime@^7.8.4":
   version "7.10.4"
   resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.4.tgz#a6724f1a6b8d2f6ea5236dbfe58c7d7ea9c5eb99"
@@ -1305,6 +1297,14 @@
   dependencies:
     "@types/node" "*"
 
+"@types/inquirer@^6.5.0":
+  version "6.5.0"
+  resolved "https://registry.yarnpkg.com/@types/inquirer/-/inquirer-6.5.0.tgz#b83b0bf30b88b8be7246d40e51d32fe9d10e09be"
+  integrity sha512-rjaYQ9b9y/VFGOpqBEXRavc3jh0a+e6evAbI31tMda8VlPaSy0AZJfXsvmIe3wklc7W6C3zCSfleuMXR7NOyXw==
+  dependencies:
+    "@types/through" "*"
+    rxjs "^6.4.0"
+
 "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1":
   version "2.0.3"
   resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762"
@@ -1365,9 +1365,9 @@
   integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==
 
 "@types/node@*":
-  version "14.0.22"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.22.tgz#23ea4d88189cec7d58f9e6b66f786b215eb61bdc"
-  integrity sha512-emeGcJvdiZ4Z3ohbmw93E/64jRzUHAItSHt8nF7M4TGgQTiWqFVGB8KNpLGFmUHmHLvjvBgFwVlqNcq+VuGv9g==
+  version "14.0.23"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.23.tgz#676fa0883450ed9da0bb24156213636290892806"
+  integrity sha512-Z4U8yDAl5TFkmYsZdFPdjeMa57NOvnaf1tljHzhouaPEp7LCj2JKkejpI1ODviIAQuW4CcQmxkQ77rnLsOOoKw==
 
 "@types/normalize-package-data@^2.4.0":
   version "2.4.0"
@@ -1401,6 +1401,13 @@
   dependencies:
     "@types/node" "*"
 
+"@types/semver@^7.3.1":
+  version "7.3.1"
+  resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.1.tgz#7a9a5d595b6d873f338c867dcef64df289468cfa"
+  integrity sha512-ooD/FJ8EuwlDKOI6D9HWxgIgJjMg2cuziXm/42npDC8y4NjxplBUn9loewZiBNCt44450lHAU0OSb51/UqXeag==
+  dependencies:
+    "@types/node" "*"
+
 "@types/sharp@0.25.0":
   version "0.25.0"
   resolved "https://registry.yarnpkg.com/@types/sharp/-/sharp-0.25.0.tgz#e21501779b110e26f33cec7fb1969e49e0cdc3c0"
@@ -1423,6 +1430,13 @@
   resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.6.tgz#a9ca4b70a18b270ccb2bc0aaafefd1d486b7ea74"
   integrity sha512-W+bw9ds02rAQaMvaLYxAbJ6cvguW/iJXNT6lTssS1ps6QdrMKttqEAMEG/b5CR8TZl3/L7/lH0ZV5nNR1LXikA==
 
+"@types/through@*":
+  version "0.0.30"
+  resolved "https://registry.yarnpkg.com/@types/through/-/through-0.0.30.tgz#e0e42ce77e897bd6aead6f6ea62aeb135b8a3895"
+  integrity sha512-FvnCJljyxhPM3gkRgWmxmDZyAQSiBQQWLI0A0VFL0K7W1oRUrPJSqNO0NvTnLkBcotdlp3lKvaT0JrnyRDkzOg==
+  dependencies:
+    "@types/node" "*"
+
 "@types/tough-cookie@*":
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.0.tgz#fef1904e4668b6e5ecee60c52cc6a078ffa6697d"
@@ -1498,7 +1512,18 @@
     eslint-scope "^5.0.0"
     eslint-utils "^2.0.0"
 
-"@typescript-eslint/parser@3.6.0", "@typescript-eslint/parser@^3.0.1":
+"@typescript-eslint/experimental-utils@3.6.1":
+  version "3.6.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-3.6.1.tgz#b5a2738ebbceb3fa90c5b07d50bb1225403c4a54"
+  integrity sha512-oS+hihzQE5M84ewXrTlVx7eTgc52eu+sVmG7ayLfOhyZmJ8Unvf3osyFQNADHP26yoThFfbxcibbO0d2FjnYhg==
+  dependencies:
+    "@types/json-schema" "^7.0.3"
+    "@typescript-eslint/types" "3.6.1"
+    "@typescript-eslint/typescript-estree" "3.6.1"
+    eslint-scope "^5.0.0"
+    eslint-utils "^2.0.0"
+
+"@typescript-eslint/parser@3.6.0":
   version "3.6.0"
   resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-3.6.0.tgz#79b5232e1a2d06f1fc745942b690cd87aca7b60e"
   integrity sha512-taghDxuLhbDAD1U5Fk8vF+MnR0yiFE9Z3v2/bYScFb0N1I9SK8eKHkdJl1DAD48OGFDMFTeOTX0z7g0W6SYUXw==
@@ -1509,11 +1534,27 @@
     "@typescript-eslint/typescript-estree" "3.6.0"
     eslint-visitor-keys "^1.1.0"
 
+"@typescript-eslint/parser@^3.0.1":
+  version "3.6.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-3.6.1.tgz#216e8adf4ee9c629f77c985476a2ea07fb80e1dc"
+  integrity sha512-SLihQU8RMe77YJ/jGTqOt0lMq7k3hlPVfp7v/cxMnXA9T0bQYoMDfTsNgHXpwSJM1Iq2aAJ8WqekxUwGv5F67Q==
+  dependencies:
+    "@types/eslint-visitor-keys" "^1.0.0"
+    "@typescript-eslint/experimental-utils" "3.6.1"
+    "@typescript-eslint/types" "3.6.1"
+    "@typescript-eslint/typescript-estree" "3.6.1"
+    eslint-visitor-keys "^1.1.0"
+
 "@typescript-eslint/types@3.6.0":
   version "3.6.0"
   resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-3.6.0.tgz#4bd6eee55d2f9d35a4b36c4804be1880bf68f7bc"
   integrity sha512-JwVj74ohUSt0ZPG+LZ7hb95fW8DFOqBuR6gE7qzq55KDI3BepqsCtHfBIoa0+Xi1AI7fq5nCu2VQL8z4eYftqg==
 
+"@typescript-eslint/types@3.6.1":
+  version "3.6.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-3.6.1.tgz#87600fe79a1874235d3cc1cf5c7e1a12eea69eee"
+  integrity sha512-NPxd5yXG63gx57WDTW1rp0cF3XlNuuFFB5G+Kc48zZ+51ZnQn9yjDEsjTPQ+aWM+V+Z0I4kuTFKjKvgcT1F7xQ==
+
 "@typescript-eslint/typescript-estree@3.6.0":
   version "3.6.0"
   resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-3.6.0.tgz#9b4cab43f1192b64ff51530815b8919f166ce177"
@@ -1528,6 +1569,20 @@
     semver "^7.3.2"
     tsutils "^3.17.1"
 
+"@typescript-eslint/typescript-estree@3.6.1":
+  version "3.6.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-3.6.1.tgz#a5c91fcc5497cce7922ff86bc37d5e5891dcdefa"
+  integrity sha512-G4XRe/ZbCZkL1fy09DPN3U0mR6SayIv1zSeBNquRFRk7CnVLgkC2ZPj8llEMJg5Y8dJ3T76SvTGtceytniaztQ==
+  dependencies:
+    "@typescript-eslint/types" "3.6.1"
+    "@typescript-eslint/visitor-keys" "3.6.1"
+    debug "^4.1.1"
+    glob "^7.1.6"
+    is-glob "^4.0.1"
+    lodash "^4.17.15"
+    semver "^7.3.2"
+    tsutils "^3.17.1"
+
 "@typescript-eslint/visitor-keys@3.6.0":
   version "3.6.0"
   resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-3.6.0.tgz#44185eb0cc47651034faa95c5e2e8b314ecebb26"
@@ -1535,6 +1590,13 @@
   dependencies:
     eslint-visitor-keys "^1.1.0"
 
+"@typescript-eslint/visitor-keys@3.6.1":
+  version "3.6.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-3.6.1.tgz#5c57a7772f4dd623cfeacc219303e7d46f963b37"
+  integrity sha512-qC8Olwz5ZyMTZrh4Wl3K4U6tfms0R/mzU4/5W3XeUZptVraGVmbptJbn6h2Ey6Rb3hOs3zWoAUebZk8t47KGiQ==
+  dependencies:
+    eslint-visitor-keys "^1.1.0"
+
 "@webassemblyjs/ast@1.9.0":
   version "1.9.0"
   resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964"
@@ -2418,16 +2480,16 @@ cacache@^12.0.2:
     y18n "^4.0.0"
 
 cacache@^15.0.4:
-  version "15.0.4"
-  resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.0.4.tgz#b2c23cf4ac4f5ead004fb15a0efb0a20340741f1"
-  integrity sha512-YlnKQqTbD/6iyoJvEY3KJftjrdBYroCbxxYXzhOzsFLWlp6KX4BOlEf4mTx0cMUfVaTS3ENL2QtDWeRYoGLkkw==
+  version "15.0.5"
+  resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.0.5.tgz#69162833da29170d6732334643c60e005f5f17d0"
+  integrity sha512-lloiL22n7sOjEEXdL8NAjTgv9a1u43xICE9/203qonkZUCj5X1UEWIdf2/Y0d6QcCtMzbKQyhrcDbdvlZTs/+A==
   dependencies:
     "@npmcli/move-file" "^1.0.1"
     chownr "^2.0.0"
     fs-minipass "^2.0.0"
     glob "^7.1.4"
     infer-owner "^1.0.4"
-    lru-cache "^5.1.1"
+    lru-cache "^6.0.0"
     minipass "^3.1.1"
     minipass-collect "^1.0.2"
     minipass-flush "^1.0.5"
@@ -2510,9 +2572,9 @@ camelcase@^6.0.0:
   integrity sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w==
 
 caniuse-lite@^1.0.30001093:
-  version "1.0.30001097"
-  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001097.tgz#1129c40c9f5ee3282158da08fd915d301f4a9bd8"
-  integrity sha512-TeuSleKt/vWXaPkLVFqGDnbweYfq4IaZ6rUugFf3rWY6dlII8StUZ8Ddin0PkADfgYZ4wRqCdO2ORl4Rn5eZIA==
+  version "1.0.30001099"
+  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001099.tgz#540118fcc6842d1fde62f4ee5521d1ec6afdb40e"
+  integrity sha512-sdS9A+sQTk7wKoeuZBN/YMAHVztUfVnjDi4/UV3sDE8xoh7YR12hKW+pIdB3oqKGwr9XaFL2ovfzt9w8eUI5CA==
 
 capture-exit@^2.0.0:
   version "2.0.0"
@@ -2979,11 +3041,6 @@ core-js-compat@^3.6.2:
     browserslist "^4.8.5"
     semver "7.0.0"
 
-core-js-pure@^3.0.0:
-  version "3.6.5"
-  resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.6.5.tgz#c79e75f5e38dbc85a662d91eea52b8256d53b813"
-  integrity sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA==
-
 core-util-is@1.0.2, core-util-is@~1.0.0:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
@@ -3204,13 +3261,6 @@ decamelize@^1.1.2, decamelize@^1.2.0:
   resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
   integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
 
-decamelize@^3.2.0:
-  version "3.2.0"
-  resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-3.2.0.tgz#84b8e8f4f8c579f938e35e2cc7024907e0090851"
-  integrity sha512-4TgkVUsmmu7oCSyGBm5FvfMoACuoh9EOidm7V5/J2X2djAwwt57qb3F2KMP2ITqODTCSwb+YRV+0Zqrv18k/hw==
-  dependencies:
-    xregexp "^4.2.4"
-
 decimal.js@^10.2.0:
   version "10.2.0"
   resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.2.0.tgz#39466113a9e036111d02f82489b5fd6b0b5ed231"
@@ -3555,9 +3605,9 @@ ecc-jsbn@~0.1.1:
     safer-buffer "^2.1.0"
 
 electron-to-chromium@^1.3.488:
-  version "1.3.496"
-  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.496.tgz#3f43d32930481d82ad3663d79658e7c59a58af0b"
-  integrity sha512-TXY4mwoyowwi4Lsrq9vcTUYBThyc1b2hXaTZI13p8/FRhY2CTaq5lK+DVjhYkKiTLsKt569Xes+0J5JsVXFurQ==
+  version "1.3.497"
+  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.497.tgz#de00f2f2f44c258c4577fbfbd5124b94c18bfa44"
+  integrity sha512-sPdW5bUDZwiFtoonuZCUwRGzsZmKzcLM0bMVhp6SMCfUG+B3faENLx3cE+o+K0Jl+MPuNA9s9cScyFjOlixZpQ==
 
 elliptic@^6.0.0, elliptic@^6.5.2:
   version "6.5.3"
@@ -5091,7 +5141,7 @@ inquirer@7.3.0:
     cli-width "^3.0.0"
     external-editor "^3.0.3"
     figures "^3.0.0"
-    lodash "^4.17.15"
+    lodash "^4.17.16"
     mute-stream "0.0.8"
     run-async "^2.4.0"
     rxjs "^6.6.0"
@@ -6357,7 +6407,7 @@ lodash.sortby@^4.7.0:
   resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
   integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=
 
-lodash@4.17.19, lodash@>=4.17.19, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15:
+lodash@4.17.19, lodash@>=4.17.19, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.16:
   version "4.17.19"
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b"
   integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==
@@ -6447,6 +6497,13 @@ lru-cache@^5.1.1:
   dependencies:
     yallist "^3.0.2"
 
+lru-cache@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
+  integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
+  dependencies:
+    yallist "^4.0.0"
+
 magic-string@^0.25.2:
   version "0.25.7"
   resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051"
@@ -6818,9 +6875,9 @@ node-abi@^2.7.0:
     semver "^5.4.1"
 
 node-addon-api@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.0.0.tgz#812446a1001a54f71663bed188314bba07e09247"
-  integrity sha512-sSHCgWfJ+Lui/u+0msF3oyCgvdkhxDbkCS6Q8uiJquzOimkJBvX6hl5aSSA7DR1XbMpdM8r7phjcF63sF4rkKg==
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.0.1.tgz#990544a2607ec3f538443df4858f8c40089b7783"
+  integrity sha512-YUpjl57P55u2yUaKX5Bgy4t5s6SCNYMg+62XNg+k41aYbBL1NgWrZfcgljR5MxDxHDjzl0qHDNtH6SkW4DXNCA==
 
 node-int64@^0.4.0:
   version "0.4.0"
@@ -7193,9 +7250,9 @@ p-limit@^2.0.0, p-limit@^2.2.0:
     p-try "^2.0.0"
 
 p-limit@^3.0.1:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.0.1.tgz#584784ac0722d1aed09f19f90ed2999af6ce2839"
-  integrity sha512-mw/p92EyOzl2MhauKodw54Rx5ZK4624rNfgNaBguFZkHzyUG9WsDzFF5/yQVEJinbJDdP4jEfMN+uBquiGnaLg==
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.0.2.tgz#1664e010af3cadc681baafd3e2a437be7b0fb5fe"
+  integrity sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg==
   dependencies:
     p-try "^2.0.0"
 
@@ -7945,9 +8002,9 @@ regexpu-core@^4.7.0:
     unicode-match-property-value-ecmascript "^1.2.0"
 
 registry-auth-token@^4.0.0:
-  version "4.1.1"
-  resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-4.1.1.tgz#40a33be1e82539460f94328b0f7f0f84c16d9479"
-  integrity sha512-9bKS7nTl9+/A1s7tnPeGrUpRcVY+LUh7bfFgzpndALdPfXQBfQV77rQVtqgUV3ti4vc/Ik81Ex8UJDWDQ12zQA==
+  version "4.2.0"
+  resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-4.2.0.tgz#1d37dffda72bbecd0f581e4715540213a65eb7da"
+  integrity sha512-P+lWzPrsgfN+UEpDS3U8AQKg/UjZX6mQSJueZj3EK+vNESoqBSpBUD3gmu4sF9lOsjXWjF11dQKUqemf3veq1w==
   dependencies:
     rc "^1.2.8"
 
@@ -8219,7 +8276,7 @@ run-queue@^1.0.0, run-queue@^1.0.3:
   dependencies:
     aproba "^1.1.1"
 
-rxjs@^6.5.5, rxjs@^6.6.0:
+rxjs@^6.4.0, rxjs@^6.5.5, rxjs@^6.6.0:
   version "6.6.0"
   resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.0.tgz#af2901eedf02e3a83ffa7f886240ff9018bbec84"
   integrity sha512-3HMA8z/Oz61DUHe+SdOiQyzIf4tOx5oQHmMir7IZEu6TMqCLHT4LRcmNaUS0NwOz8VLvmmBduMsoaUvMaIiqzg==
@@ -8904,9 +8961,9 @@ strip-indent@^1.0.1:
     get-stdin "^4.0.1"
 
 strip-json-comments@^3.1.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.0.tgz#7638d31422129ecf4457440009fba03f9f9ac180"
-  integrity sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w==
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
+  integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
 
 strip-json-comments@~2.0.1:
   version "2.0.1"
@@ -9944,13 +10001,6 @@ xmlchars@^2.2.0:
   resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"
   integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==
 
-xregexp@^4.2.4:
-  version "4.3.0"
-  resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-4.3.0.tgz#7e92e73d9174a99a59743f67a4ce879a04b5ae50"
-  integrity sha512-7jXDIFXh5yJ/orPn4SXjuVrWWoi4Cr8jfV1eHv9CixKSbU+jY4mxfrBwAuDvupPNKpMUY+FeIqsVw/JLT9+B8g==
-  dependencies:
-    "@babel/runtime-corejs3" "^7.8.3"
-
 xtend@^4.0.0, xtend@~4.0.1:
   version "4.0.2"
   resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
@@ -10014,12 +10064,12 @@ yargs@^13.2.4, yargs@^13.3.2:
     yargs-parser "^13.1.2"
 
 yargs@^15.0.2, yargs@^15.3.1:
-  version "15.4.0"
-  resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.0.tgz#53949fb768309bac1843de9b17b80051e9805ec2"
-  integrity sha512-D3fRFnZwLWp8jVAAhPZBsmeIHY8tTsb8ItV9KaAaopmC6wde2u6Yw29JBIZHXw14kgkRnYmDgmQU4FVMDlIsWw==
+  version "15.4.1"
+  resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8"
+  integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==
   dependencies:
     cliui "^6.0.0"
-    decamelize "^3.2.0"
+    decamelize "^1.2.0"
     find-up "^4.1.0"
     get-caller-file "^2.0.1"
     require-directory "^2.1.1"