├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── custom.md │ └── feature_request.md ├── dependabot.yml └── workflows │ └── rust.yml ├── .gitignore ├── .gitmodules ├── CONTRIBUTING.md ├── CREDITS.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── TODO.md ├── assets ├── Animation.gif ├── volt-transparent-bg.png └── volt.png ├── crates ├── oro-diagnostics-derive │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── oro-diagnostics │ ├── Cargo.lock │ ├── Cargo.toml │ ├── src │ │ └── lib.rs │ └── tests │ │ ├── derive_enum.rs │ │ └── derive_struct.rs ├── oro-node-semver │ ├── Cargo.lock │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── lib.rs │ │ └── version_req.rs └── package-spec │ ├── Cargo.lock │ ├── Cargo.toml │ ├── src │ ├── error.rs │ ├── gitinfo.rs │ ├── lib.rs │ └── parsers │ │ ├── alias.rs │ │ ├── git.rs │ │ ├── mod.rs │ │ ├── npm.rs │ │ ├── package.rs │ │ ├── path.rs │ │ └── util.rs │ └── tests │ ├── dir.rs │ ├── git.rs │ └── npm.rs ├── dist └── volt.iss ├── hooks └── pre-commit ├── rust-toolchain └── src ├── cli ├── cli.rs ├── config.rs └── mod.rs ├── commands ├── add.rs ├── audit.rs ├── cache.rs ├── check.rs ├── clean.rs ├── clone.rs ├── create.rs ├── deploy.rs ├── discord.rs ├── fix.rs ├── info.rs ├── init.rs ├── install.rs ├── list.rs ├── login.rs ├── logout.rs ├── migrate.rs ├── mod.rs ├── node.rs ├── node │ ├── node_install.rs │ ├── node_list.rs │ ├── node_remove.rs │ └── node_use.rs ├── outdated.rs ├── owner.rs ├── ping.rs ├── publish.rs ├── remove.rs ├── run.rs ├── scripts.rs ├── search.rs ├── set.rs ├── stat.rs ├── tag.rs ├── team.rs ├── update.rs └── watch.rs ├── core ├── classes.rs ├── classes │ ├── create_templates.rs │ ├── init_data.rs │ ├── meta.rs │ └── package_manager.rs ├── io.rs ├── mod.rs ├── model.rs ├── model │ ├── http_manager.rs │ └── lock_file.rs ├── net.rs ├── prompt.rs ├── prompt │ ├── input.rs │ └── prompts.rs └── utils │ ├── api.rs │ ├── constants.rs │ ├── errors.rs │ ├── extensions.rs │ ├── helper.rs │ ├── mod.rs │ ├── package.rs │ ├── scripts.rs │ └── voltapi.rs └── main.rs /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "" 5 | labels: "" 6 | assignees: "" 7 | --- 8 | 9 | **Describe the bug** 10 | A clear and concise description of what the bug is. 11 | 12 | **To Reproduce** 13 | Steps to reproduce the behavior: 14 | 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | 28 | - OS: [e.g. iOS] 29 | - Browser [e.g. chrome, safari] 30 | - Version [e.g. 22] 31 | 32 | **Smartphone (please complete the following information):** 33 | 34 | - Device: [e.g. iPhone6] 35 | - OS: [e.g. iOS8.1] 36 | - Browser [e.g. stock browser, safari] 37 | - Version [e.g. 22] 38 | 39 | **Additional context** 40 | Add any other context about the problem here. 41 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Custom issue template 3 | about: Describe this issue template's purpose here. 4 | title: "" 5 | labels: "" 6 | assignees: "" 7 | --- 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "" 5 | labels: "" 6 | assignees: "" 7 | --- 8 | 9 | **Is your feature request related to a problem? Please describe.** 10 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 11 | 12 | **Describe the solution you'd like** 13 | A clear and concise description of what you want to happen. 14 | 15 | **Describe alternatives you've considered** 16 | A clear and concise description of any alternative solutions or features you've considered. 17 | 18 | **Additional context** 19 | Add any other context or screenshots about the feature request here. 20 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "daily" 12 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | on: [push] 2 | 3 | jobs: 4 | linux: 5 | name: Linux build pipeline 6 | runs-on: ubuntu-latest 7 | steps: 8 | - name: Rust Cache 9 | uses: Swatinem/rust-cache@v1.3.0 10 | 11 | - uses: actions/checkout@v2 12 | - name: Toolchain Install 13 | uses: actions-rs/toolchain@v1.0.6 14 | with: 15 | toolchain: stable 16 | override: true 17 | components: rustfmt, clippy 18 | 19 | - name: Cargo format 20 | uses: actions-rs/cargo@v1 21 | with: 22 | command: fmt 23 | args: -- --check 24 | 25 | - name: Cargo clippy 26 | uses: actions-rs/cargo@v1 27 | with: 28 | command: clippy 29 | 30 | windows: 31 | name: Windows build pipeline 32 | runs-on: windows-latest 33 | steps: 34 | - name: Rust Cache 35 | uses: Swatinem/rust-cache@v1.3.0 36 | 37 | - uses: actions/checkout@v2 38 | - name: Toolchain Install 39 | uses: actions-rs/toolchain@v1.0.6 40 | with: 41 | toolchain: stable 42 | override: true 43 | components: rustfmt, clippy 44 | 45 | - name: Cargo format 46 | uses: actions-rs/cargo@v1 47 | with: 48 | command: fmt 49 | args: -- --check 50 | 51 | - name: Cargo clippy 52 | uses: actions-rs/cargo@v1 53 | with: 54 | command: clippy 55 | 56 | 57 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # macos-specific metadata 2 | .DS_Store 3 | 4 | # test directories 5 | tests/ 6 | test/ 7 | yarn_test/ 8 | volt_test/ 9 | pnpm_test/ 10 | 11 | # accidental installation directories 12 | node_modules 13 | package.json 14 | 15 | # lockfiles 16 | yarn.lock 17 | pnpm-lock.yaml 18 | package-lock.json 19 | 20 | # others 21 | example/ 22 | /dev 23 | /target 24 | .vscode/ 25 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "registry"] 2 | path = registry 3 | url = https://github.com/voltpkg/registry.git -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Install `vcpkg` 2 | 3 | **Note**: `vcpkg` DOES NOT NEED TO BE IN `PATH` 4 | 5 | In `vcpkg` directory: 6 | `./vcpkg.exe integrate install` 7 | `./vcpkg.exe install --recurse curl[http2,openssl]:x64-windows-static-md` 8 | 9 | `Cargo.toml` 10 | ```toml 11 | isahc = { version = "1.5.0" , features = ["http2", "text-decoding"], default-features = false } 12 | ``` 13 | 14 | `build.rs` (not inside `src`) 15 | ```rs 16 | fn main() { 17 | println!("cargo:rustc-link-lib=nghttp2"); 18 | println!("cargo:rustc-link-lib=libssl"); 19 | println!("cargo:rustc-link-lib=libcrypto"); 20 | } 21 | ``` 22 | 23 | `Cargo.toml` 24 | ```rs 25 | build = "build.rs" 26 | ``` 27 | 28 | Go to `"C:\Users\xtrem\.cargo\registry\src\github.com-1ecc6299db9ec823\curl-sys-0.4.49+curl-7.79.1\build.rs"` 29 | 30 | Comment out the following `if` statement 31 | ```rs 32 | // if !Path::new("curl/.git").exists() { 33 | // let _ = Command::new("git") 34 | // .args(&["submodule", "update", "--init"]) 35 | // .status(); 36 | // } 37 | ``` 38 | 39 | `cargo clean` 40 | 41 | request code: 42 | ```rs 43 | let client: Request<&str> = 44 | Request::get(format!("https://registry.npmjs.org/{}", package_name)) 45 | .header( 46 | "accept", 47 | "application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*", 48 | ) 49 | .header("accept-encoding", "gzip,deflate") 50 | .header("connection", "keep-alive") 51 | .header("host", "registry.npmjs.org") 52 | .version_negotiation(VersionNegotiation::http2()) 53 | .ssl_options(SslOption::DANGER_ACCEPT_INVALID_CERTS) 54 | .body("") 55 | .map_err(VoltError::RequestBuilderError)?; 56 | ``` 57 | 58 | `cargo build --release` -------------------------------------------------------------------------------- /CREDITS.md: -------------------------------------------------------------------------------- 1 | # Credits 2 | 3 | #### Anyhow for handling errors 4 | 5 | #### Async-trait for using async/await in traits 6 | 7 | #### CHTTP (now isahc) as a HTTP client 8 | 9 | #### colored for rendering coloured text in the terminal 10 | 11 | #### console for creating interactive CLI 12 | 13 | #### dialoguer for creating prompts in the CLI 14 | 15 | #### dirs for doing folder IO 16 | 17 | #### flate2 for compression of node_modules dir 18 | 19 | #### lazy-static for declaring lazily evaluated statics 20 | 21 | #### serde for serialization/deserialization 22 | 23 | #### serde-json for serialization of ".json" files 24 | 25 | #### Sha-1 for checking the shasum of downloaded packages 26 | 27 | #### structopt for parsing CLI args with a struct 28 | 29 | #### Tar for reading/writing/creating a tar file 30 | 31 | #### This-error for creating custom error handlers 32 | 33 | #### Tokio for using top level async await and FileIO 34 | 35 |
36 | 37 | ## Windows 38 | 39 | #### winapi for using functions inside the windows API 40 | 41 | #### tempfile for handling temp files/directories 42 | 43 | #### Scopegaurd for convenient RAII scope guard handling 44 | 45 | #### RSLint Code linting and validation for `volt watch` 46 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "volt" 3 | version = "0.0.3" 4 | authors = [ 5 | "Tejas Ravishankar ", 6 | "Volt Contributors (https://github.com/voltpkg/volt/graphs/contributors)", 7 | ] 8 | license = "Apache-2.0" 9 | description = "A fast, memory-safe package manager for the web." 10 | edition = "2021" 11 | default-run = "volt" 12 | rust-version = "1.57" 13 | 14 | [dependencies] 15 | async-trait = "0.1.51" 16 | base64 = "0.13.0" 17 | bytes = "1.1.0" 18 | clap = { version = "3.1.8", features = [ 19 | "derive", 20 | "cargo", 21 | "std", 22 | "color", 23 | ], default-features = false } 24 | colored = "2.0.0" 25 | dialoguer = "0.10.0" 26 | dirs = "4.0.0" 27 | futures = "0.3.17" 28 | futures-util = "0.3.17" 29 | git-config = "0.1.7" 30 | indicatif = "0.17.0-rc.4" 31 | isahc = { version = "1.5.1", features = ["json"] } 32 | jwalk = "0.6.0" 33 | lazy_static = "1.4.0" 34 | miette = { version = "3.2.0", features = ["fancy"] } 35 | rand = "0.8.4" 36 | regex = "1.5.5" 37 | reqwest = { version = "0.11.10", features = [ 38 | "json", 39 | "rustls-tls", 40 | "blocking", 41 | ], default-features = false } 42 | node-semver = "2.0.0" 43 | cacache = "9.0.0" 44 | serde_json = "1.0.69" 45 | serde = { version = "1.0.130", features = ["derive"] } 46 | sha-1 = "0.10.0" 47 | sha2 = "0.10.2" 48 | ssri = "7.0.0" 49 | tar = "0.4.37" 50 | thiserror = "1.0.30" 51 | tokio = { version = "1.17.0", features = ["fs", "macros", "rt-multi-thread"] } 52 | minifier = "0.0.42" 53 | fs_extra = "1.2.0" 54 | webbrowser = "0.5.5" 55 | serde_yaml = "0.8.21" 56 | tempfile = "3.2.0" 57 | tracing = "0.1.29" 58 | tracing-subscriber = { version = "0.3.1", features = ["env-filter"] } 59 | comfy-table = "5.0.0" 60 | urlencoding = "2.1.0" 61 | speedy = "0.8.0" 62 | libdeflater = "0.7.3" 63 | package-spec = { path = "crates/package-spec" } 64 | hex = "0.4.3" 65 | rayon = "1.5.1" 66 | mimalloc = { version = "0.1.27", default-features = false } 67 | 68 | [target.'cfg(unix)'.dependencies] 69 | rust-lzma = "0.5.1" 70 | 71 | [target.'cfg(windows)'.dependencies] 72 | winapi = { version = "0.3.9", features = [ 73 | "errhandlingapi", 74 | "fileapi", 75 | "guiddef", 76 | "handleapi", 77 | "ioapiset", 78 | "processthreadsapi", 79 | "securitybaseapi", 80 | "winbase", 81 | "winioctl", 82 | "winnt", 83 | ] } 84 | junction = "0.2.0" 85 | scopeguard = "1.1.0" 86 | 87 | 88 | [profile.release-optimized] 89 | inherits = "release" 90 | opt-level = 3 91 | lto = "fat" 92 | codegen-units = 1 # Reduce number of codegen units to increase optimizations. 93 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |

Volt

6 |

Rapid, reliable and robust Javascript package management.

7 |
8 | 9 |

10 | 11 |

12 |
13 | 14 | 15 | 16 | WARNING: Volt is still in the development stage and is not ready for use! 17 | 18 | **Rapid**: Volt is incredibly fast and powerful. 19 | 20 | **Reliable**: Volt is built to be reliable and dependable. 21 | 22 | **Robust**: Volt works with low resource usage. 23 | 24 | **Important**: Volt is still in the alpha stage of development, and is not ready for use in production or development environments. 25 |
26 | 27 | # :zap: Installation 28 | 29 | We don't have an official release of Volt yet, however, if you would like to give it a try, feel free to follow the steps below to build from source. 30 |
31 | 32 | ## Build From Source 33 | 34 | Prerequisites: **Git**, **Rust Toolchain** 35 | 36 | ### Minimum Supported Rust Version (MSRV) 37 | 38 | Rust 1.58 39 | 40 | ### Steps 41 | 42 | 1. Clone the github repository using the Github CLI. 43 | 44 | ```powershell 45 | git clone https://github.com/voltpkg/volt 46 | ``` 47 | 48 | 2. Change to the `volt` directory. 49 | 50 | ```powershell 51 | cd volt 52 | ``` 53 | 54 | 3. Run a compiled and optimized build 55 | 56 | ``` 57 | cargo run --release -- --help 58 | # you should see a help menu from Volt 59 | ``` 60 | 61 |
62 | 63 | ## :test_tube: Testing 64 | 65 | First, make sure you [**Build From Source**](https://github.com/voltpkg/volt/#build-from-source). 66 | 67 | Run this command to run the tests for volt. 68 | 69 | ```powershell 70 | cargo test 71 | ``` 72 | 73 |
74 | 75 | ## :clap: Supporters 76 | 77 | [![Stargazers repo roster for @voltpkg/volt](https://reporoster.com/stars/voltpkg/volt)](https://github.com/voltpkg/volt/stargazers) 78 | 79 | [![Forkers repo roster for @voltpkg/volt](https://reporoster.com/forks/voltpkg/volt)](https://github.com/voltpkg/volt/network/members) 80 | 81 |
82 | 83 | ## :hammer: Build Status 84 | 85 | | Feature | Build Status | 86 | | -------- | ------------ | 87 | | Add | 🏗️ | 88 | | Audit | ❌ | 89 | | Cache | ❌ | 90 | | Check | ❌ | 91 | | Clone | 🏗️ | 92 | | Compress | 🏗️ | 93 | | Create | 🏗️ | 94 | | Deploy | 🏗️ | 95 | | Fix | ❌ | 96 | | Help | 🏗️ | 97 | | Info | ❌ | 98 | | Init | 🏗️ | 99 | | Install | 🏗️ | 100 | | List | 🏗️ | 101 | | Login | 🏗️ | 102 | | Logout | ❌ | 103 | | Migrate | 🏗️ | 104 | | Mod | ❌ | 105 | | Outdated | ❌ | 106 | | Owner | ❌ | 107 | | Ping | 🏗️ | 108 | | Publish | ❌ | 109 | | Remove | ❌ | 110 | | Run | 🏗️ | 111 | | Search | ❌ | 112 | | Set | ❌ | 113 | | Stat | ❌ | 114 | | Tag | ❌ | 115 | | Team | ❌ | 116 | | Update | ❌ | 117 | | Watch | 🏗️ | 118 | 119 |
120 | 121 | ## Built With 122 | 123 | [Rust](https://www.rust-lang.org/) 124 | 125 | [External Libraries](https://github.com/voltpkg/volt/blob/dev/CREDITS.md) 126 | 127 | ## Versioning 128 | 129 | We use [semver](https://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/voltpkg/volt/tags). 130 | 131 | ## License 132 | 133 | This project is licensed under Apache-2.0 - see the [LICENSE.md](LICENSE) file for details. 134 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # Move to simdjson 2 | 3 | # Add io_uring support 4 | 5 | # Use https://github.com/mafintosh/ims for inspiration for faster dep tree resolution 6 | -------------------------------------------------------------------------------- /assets/Animation.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimensionhq/volt/1589b603a40364651a6fdabf28f6fa66c4f0ff8e/assets/Animation.gif -------------------------------------------------------------------------------- /assets/volt-transparent-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimensionhq/volt/1589b603a40364651a6fdabf28f6fa66c4f0ff8e/assets/volt-transparent-bg.png -------------------------------------------------------------------------------- /assets/volt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimensionhq/volt/1589b603a40364651a6fdabf28f6fa66c4f0ff8e/assets/volt.png -------------------------------------------------------------------------------- /crates/oro-diagnostics-derive/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "oro-diagnostics-derive" 7 | version = "0.1.0" 8 | dependencies = [ 9 | "quote", 10 | "syn", 11 | ] 12 | 13 | [[package]] 14 | name = "proc-macro2" 15 | version = "1.0.33" 16 | source = "registry+https://github.com/rust-lang/crates.io-index" 17 | checksum = "fb37d2df5df740e582f28f8560cf425f52bb267d872fe58358eadb554909f07a" 18 | dependencies = [ 19 | "unicode-xid", 20 | ] 21 | 22 | [[package]] 23 | name = "quote" 24 | version = "1.0.10" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" 27 | dependencies = [ 28 | "proc-macro2", 29 | ] 30 | 31 | [[package]] 32 | name = "syn" 33 | version = "1.0.82" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59" 36 | dependencies = [ 37 | "proc-macro2", 38 | "quote", 39 | "unicode-xid", 40 | ] 41 | 42 | [[package]] 43 | name = "unicode-xid" 44 | version = "0.2.2" 45 | source = "registry+https://github.com/rust-lang/crates.io-index" 46 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 47 | -------------------------------------------------------------------------------- /crates/oro-diagnostics-derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "oro-diagnostics-derive" 3 | version = "0.1.0" 4 | authors = ["Kirill Vasiltsov "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [lib] 10 | proc-macro = true 11 | 12 | [dependencies] 13 | syn = "1.0" 14 | quote = "1.0" 15 | -------------------------------------------------------------------------------- /crates/oro-diagnostics/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "atty" 7 | version = "0.2.14" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 10 | dependencies = [ 11 | "hermit-abi", 12 | "libc", 13 | "winapi", 14 | ] 15 | 16 | [[package]] 17 | name = "colored" 18 | version = "2.0.0" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" 21 | dependencies = [ 22 | "atty", 23 | "lazy_static", 24 | "winapi", 25 | ] 26 | 27 | [[package]] 28 | name = "form_urlencoded" 29 | version = "1.0.1" 30 | source = "registry+https://github.com/rust-lang/crates.io-index" 31 | checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" 32 | dependencies = [ 33 | "matches", 34 | "percent-encoding", 35 | ] 36 | 37 | [[package]] 38 | name = "hermit-abi" 39 | version = "0.1.19" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 42 | dependencies = [ 43 | "libc", 44 | ] 45 | 46 | [[package]] 47 | name = "idna" 48 | version = "0.2.3" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" 51 | dependencies = [ 52 | "matches", 53 | "unicode-bidi", 54 | "unicode-normalization", 55 | ] 56 | 57 | [[package]] 58 | name = "lazy_static" 59 | version = "1.4.0" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 62 | 63 | [[package]] 64 | name = "libc" 65 | version = "0.2.109" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "f98a04dce437184842841303488f70d0188c5f51437d2a834dc097eafa909a01" 68 | 69 | [[package]] 70 | name = "matches" 71 | version = "0.1.9" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" 74 | 75 | [[package]] 76 | name = "oro-diagnostics" 77 | version = "0.1.0" 78 | dependencies = [ 79 | "colored", 80 | "oro-diagnostics-derive", 81 | "thiserror", 82 | "url", 83 | ] 84 | 85 | [[package]] 86 | name = "oro-diagnostics-derive" 87 | version = "0.1.0" 88 | dependencies = [ 89 | "quote", 90 | "syn", 91 | ] 92 | 93 | [[package]] 94 | name = "percent-encoding" 95 | version = "2.1.0" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" 98 | 99 | [[package]] 100 | name = "proc-macro2" 101 | version = "1.0.33" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "fb37d2df5df740e582f28f8560cf425f52bb267d872fe58358eadb554909f07a" 104 | dependencies = [ 105 | "unicode-xid", 106 | ] 107 | 108 | [[package]] 109 | name = "quote" 110 | version = "1.0.10" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" 113 | dependencies = [ 114 | "proc-macro2", 115 | ] 116 | 117 | [[package]] 118 | name = "syn" 119 | version = "1.0.82" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59" 122 | dependencies = [ 123 | "proc-macro2", 124 | "quote", 125 | "unicode-xid", 126 | ] 127 | 128 | [[package]] 129 | name = "thiserror" 130 | version = "1.0.30" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" 133 | dependencies = [ 134 | "thiserror-impl", 135 | ] 136 | 137 | [[package]] 138 | name = "thiserror-impl" 139 | version = "1.0.30" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" 142 | dependencies = [ 143 | "proc-macro2", 144 | "quote", 145 | "syn", 146 | ] 147 | 148 | [[package]] 149 | name = "tinyvec" 150 | version = "1.5.1" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" 153 | dependencies = [ 154 | "tinyvec_macros", 155 | ] 156 | 157 | [[package]] 158 | name = "tinyvec_macros" 159 | version = "0.1.0" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" 162 | 163 | [[package]] 164 | name = "unicode-bidi" 165 | version = "0.3.7" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" 168 | 169 | [[package]] 170 | name = "unicode-normalization" 171 | version = "0.1.19" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" 174 | dependencies = [ 175 | "tinyvec", 176 | ] 177 | 178 | [[package]] 179 | name = "unicode-xid" 180 | version = "0.2.2" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 183 | 184 | [[package]] 185 | name = "url" 186 | version = "2.2.2" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" 189 | dependencies = [ 190 | "form_urlencoded", 191 | "idna", 192 | "matches", 193 | "percent-encoding", 194 | ] 195 | 196 | [[package]] 197 | name = "winapi" 198 | version = "0.3.9" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 201 | dependencies = [ 202 | "winapi-i686-pc-windows-gnu", 203 | "winapi-x86_64-pc-windows-gnu", 204 | ] 205 | 206 | [[package]] 207 | name = "winapi-i686-pc-windows-gnu" 208 | version = "0.4.0" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 211 | 212 | [[package]] 213 | name = "winapi-x86_64-pc-windows-gnu" 214 | version = "0.4.0" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 217 | -------------------------------------------------------------------------------- /crates/oro-diagnostics/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "oro-diagnostics" 3 | version = "0.1.0" 4 | authors = ["Kat Marchán "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | url = "2.2.0" 9 | thiserror = "1.0.22" 10 | colored = "2.0.0" 11 | oro-diagnostics-derive = { path = "../oro-diagnostics-derive" } 12 | -------------------------------------------------------------------------------- /crates/oro-diagnostics/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::path::PathBuf; 3 | 4 | use colored::Colorize; 5 | use thiserror::Error; 6 | use url::Url; 7 | 8 | pub use oro_diagnostics_derive::Diagnostic; 9 | 10 | #[derive(Error)] 11 | #[error("{:?}", self)] 12 | pub struct DiagnosticError { 13 | pub error: Box, 14 | pub category: DiagnosticCategory, 15 | pub label: String, 16 | pub advice: Option, 17 | pub meta: Option, 18 | } 19 | 20 | impl fmt::Debug for DiagnosticError { 21 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 22 | if f.alternate() { 23 | return fmt::Debug::fmt(&self.error, f); 24 | } else { 25 | use DiagnosticCategory::*; 26 | write!(f, "{}", self.label.red())?; 27 | if let Net = &self.category { 28 | if let Some(Meta::Net { url }) = &self.meta { 29 | if let Some(ref url) = url { 30 | write!(f, " @ {}", format!("{}", url).cyan().underline())?; 31 | } 32 | } 33 | } 34 | write!(f, "\n\n")?; 35 | write!(f, "{}", self.error)?; 36 | if let Some(advice) = &self.advice { 37 | write!(f, "\n\n{}", "help".yellow())?; 38 | write!(f, ": {}", advice)?; 39 | } 40 | } 41 | Ok(()) 42 | } 43 | } 44 | 45 | pub type DiagnosticResult = Result; 46 | 47 | impl From for DiagnosticError 48 | where 49 | E: Diagnostic + Send + Sync, 50 | { 51 | fn from(error: E) -> Self { 52 | Self { 53 | category: error.category(), 54 | meta: error.meta(), 55 | label: error.label(), 56 | advice: error.advice(), 57 | error: Box::new(error), 58 | } 59 | } 60 | } 61 | 62 | pub enum Meta { 63 | Net { 64 | url: Option, 65 | }, 66 | Fs { 67 | path: PathBuf, 68 | }, 69 | Parse { 70 | input: String, 71 | row: usize, 72 | col: usize, 73 | path: Option, 74 | }, 75 | } 76 | 77 | pub trait Explain { 78 | fn meta(&self) -> Option { 79 | None 80 | } 81 | } 82 | 83 | pub trait Diagnostic: std::error::Error + Send + Sync + Explain + 'static { 84 | fn category(&self) -> DiagnosticCategory; 85 | fn label(&self) -> String; 86 | fn advice(&self) -> Option; 87 | } 88 | 89 | // This is needed so Box is correctly treated as an Error. 90 | impl std::error::Error for Box {} 91 | 92 | #[derive(Debug, Clone, Eq, PartialEq, Hash)] 93 | pub enum DiagnosticCategory { 94 | /// oro::misc 95 | Misc, 96 | /// oro::net 97 | Net, 98 | /// oro::fs 99 | Fs, 100 | /// oro::parse 101 | Parse, 102 | } 103 | 104 | pub trait AsDiagnostic { 105 | fn as_diagnostic(self, subpath: impl AsRef) -> std::result::Result; 106 | } 107 | 108 | impl AsDiagnostic for Result { 109 | fn as_diagnostic(self, label: impl AsRef) -> Result { 110 | self.map_err(|e| DiagnosticError { 111 | category: DiagnosticCategory::Misc, 112 | error: Box::new(e), 113 | label: label.as_ref().into(), 114 | advice: None, 115 | meta: None, 116 | }) 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /crates/oro-diagnostics/tests/derive_enum.rs: -------------------------------------------------------------------------------- 1 | use oro_diagnostics::Diagnostic; 2 | use oro_diagnostics::DiagnosticCategory; 3 | use oro_diagnostics::Explain; 4 | use thiserror::Error; 5 | 6 | #[derive(Debug, Error, Diagnostic)] 7 | #[error("Rainbow error.")] 8 | #[label("critical::rainbow")] 9 | #[advice("Rainbow.")] 10 | #[category(Misc)] 11 | pub struct Rainbow; 12 | 13 | impl Explain for Rainbow {} 14 | 15 | #[derive(Debug, Error, Diagnostic)] 16 | #[error("Critical error.")] 17 | pub enum Critical { 18 | #[category(Misc)] 19 | #[label("critical::blue")] 20 | #[advice("Blue.")] 21 | Blue(String), 22 | #[label("critical::red")] 23 | #[advice("Red.")] 24 | #[category(Parse)] 25 | Red, 26 | #[label("critical::orange")] 27 | #[advice("Orange.")] 28 | #[category(Fs)] 29 | Orange, 30 | Transparent(#[ask] Rainbow), 31 | } 32 | 33 | impl Explain for Critical {} 34 | 35 | #[test] 36 | fn it_works() { 37 | let blue = Critical::Blue("test".into()); 38 | assert_eq!("Blue.", blue.advice().unwrap()); 39 | assert_eq!("critical::blue", blue.label()); 40 | assert_eq!(DiagnosticCategory::Misc, blue.category()); 41 | 42 | let red = Critical::Red; 43 | assert_eq!("Red.", red.advice().unwrap()); 44 | assert_eq!("critical::red", red.label()); 45 | assert_eq!(DiagnosticCategory::Parse, red.category()); 46 | 47 | let orange = Critical::Orange; 48 | assert_eq!("Orange.", orange.advice().unwrap()); 49 | assert_eq!("critical::orange", orange.label()); 50 | assert_eq!(DiagnosticCategory::Fs, orange.category()); 51 | 52 | let rainbow = Rainbow {}; 53 | 54 | let transp = Critical::Transparent(rainbow); 55 | assert_eq!("Rainbow.", transp.advice().unwrap()); 56 | assert_eq!("critical::rainbow", transp.label()); 57 | assert_eq!(DiagnosticCategory::Misc, transp.category()); 58 | } 59 | -------------------------------------------------------------------------------- /crates/oro-diagnostics/tests/derive_struct.rs: -------------------------------------------------------------------------------- 1 | use oro_diagnostics::Diagnostic; 2 | use oro_diagnostics::DiagnosticCategory; 3 | use oro_diagnostics::Explain; 4 | use thiserror::Error; 5 | 6 | #[derive(Diagnostic, Debug, Eq, PartialEq, Error)] 7 | #[error("Colored struct.")] 8 | #[label("color::struct")] 9 | #[advice("Color.")] 10 | #[category(Misc)] 11 | pub struct Color { 12 | input: Option, 13 | field: i32, 14 | } 15 | 16 | impl Explain for Color {} 17 | 18 | #[test] 19 | fn it_works() { 20 | let clr = Color { 21 | field: 1, 22 | input: Some("lol".into()), 23 | }; 24 | assert_eq!("color::struct", clr.label()); 25 | assert_eq!("Color.", clr.advice().unwrap()); 26 | } 27 | -------------------------------------------------------------------------------- /crates/oro-node-semver/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "oro-node-semver" 3 | version = "0.1.0" 4 | authors = ["Felipe Sere "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | oro-diagnostics = { path = "../oro-diagnostics" } 11 | 12 | nom = "6.0.0" 13 | thiserror = "1.0.9" 14 | serde = "1.0.115" 15 | bytecount = "0.6.0" 16 | 17 | [dev-dependencies] 18 | serde_json = "1.0.57" 19 | serde_derive = "1.0.115" 20 | pretty_assertions = "0.6.1" 21 | -------------------------------------------------------------------------------- /crates/oro-node-semver/README.md: -------------------------------------------------------------------------------- 1 | # Oro Node Semver 2 | 3 | 4 | TODO: 5 | 6 | https://github.com/dart-lang/pub_semver/blob/master/lib/src/version_range.dart cotains a good impl of below operations needed for PubGrub 7 | 8 | 9 | * bool allowsAll(VersionConstraint other); 10 | /// Returns `true` if this constraint allows all the versions that [other] 11 | /// allows. 12 | e.g. foo.allowsAll(other) // foo ^1.5.0 is a subset of foo ^1.0.0 13 | 14 | 15 | * bool allowsAny(VersionConstraint other); 16 | /// Returns `true` if this constraint allows any of the versions that [other] 17 | /// allows. 18 | e.g. !foo.allowsAny(other) // foo ^2.0.0 is disjoint with foo ^1.0.0 19 | 20 | * VersionConstraint difference(VersionConstraint other); 21 | /// Returns a [VersionConstraint] that allows [Version]s allowed by this but 22 | /// not [other]. 23 | e.g. positive.constraint.difference(negative.constraint) // foo ^1.0.0 ∩ not foo ^1.5.0 → foo >=1.0.0 <1.5.0 24 | 25 | * VersionConstraint intersect(VersionConstraint other); 26 | /// Returns a [VersionConstraint] that only allows [Version]s allowed by both 27 | /// this and [other]. 28 | e.g. constraint.intersect(other.constraint); // foo ^1.0.0 ∩ foo >=1.5.0 <3.0.0 → foo ^1.5.0 29 | 30 | * VersionConstraint union(VersionConstraint other); 31 | /// Returns a [VersionConstraint] that allows [Version]s allowed by either 32 | /// this or [other]. 33 | e.g. constraint.union(other.constraint); // not foo ^1.0.0 ∩ not foo >=1.5.0 <3.0.0 → not foo >=1.0.0 <3.0.0 34 | -------------------------------------------------------------------------------- /crates/package-spec/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "package-spec" 3 | version = "0.1.0" 4 | authors = ["Kat Marchán "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | oro-diagnostics = { path = "../oro-diagnostics" } 9 | oro-node-semver = { path = "../oro-node-semver" } 10 | 11 | url = "2.1.0" 12 | nom = "7.1.0" 13 | thiserror = "1.0.9" 14 | percent-encoding = "2.1.0" 15 | bytecount = "0.6.0" 16 | -------------------------------------------------------------------------------- /crates/package-spec/src/error.rs: -------------------------------------------------------------------------------- 1 | use nom::error::{ContextError, ErrorKind, FromExternalError, ParseError}; 2 | use oro_diagnostics::{Diagnostic, DiagnosticCategory, Explain, Meta}; 3 | use oro_node_semver::SemverError; 4 | use thiserror::Error; 5 | use url::ParseError as UrlParseError; 6 | 7 | #[derive(Debug, Error, Diagnostic)] 8 | #[error("Error parsing package spec. {kind}")] 9 | #[category(Parse)] 10 | #[label("package_spec::no_parse")] 11 | #[advice("Please fix your spec. Go look up wherever they're documented idk.")] 12 | pub struct PackageSpecError { 13 | pub input: String, 14 | pub offset: usize, 15 | pub kind: SpecErrorKind, 16 | } 17 | 18 | impl PackageSpecError { 19 | pub fn location(&self) -> (usize, usize) { 20 | // Taken partially from nom. 21 | let prefix = &self.input.as_bytes()[..self.offset]; 22 | 23 | // Count the number of newlines in the first `offset` bytes of input 24 | let line_number = bytecount::count(prefix, b'\n'); 25 | 26 | // Find the line that includes the subslice: 27 | // Find the *last* newline before the substring starts 28 | let line_begin = prefix 29 | .iter() 30 | .rev() 31 | .position(|&b| b == b'\n') 32 | .map(|pos| self.offset - pos) 33 | .unwrap_or(0); 34 | 35 | // Find the full line after that newline 36 | let line = self.input[line_begin..] 37 | .lines() 38 | .next() 39 | .unwrap_or(&self.input[line_begin..]) 40 | .trim_end(); 41 | 42 | // The (1-indexed) column number is the offset of our substring into that line 43 | let column_number = self.input[self.offset..].as_ptr() as usize - line.as_ptr() as usize; 44 | 45 | (line_number, column_number) 46 | } 47 | } 48 | 49 | #[derive(Debug, Error)] 50 | pub enum SpecErrorKind { 51 | #[error("Found invalid characters: `{0}`")] 52 | InvalidCharacters(String), 53 | #[error("Drive letters on Windows can only be alphabetical. Got `{0}`.")] 54 | InvalidDriveLetter(char), 55 | #[error("Invalid git host `{0}`. Only github:, gitlab:, gist:, and bitbucket: are supported in shorthands.")] 56 | InvalidGitHost(String), 57 | #[error(transparent)] 58 | SemverParseError(SemverError), 59 | #[error(transparent)] 60 | UrlParseError(UrlParseError), 61 | #[error(transparent)] 62 | GitHostParseError(Box), 63 | #[error("Failed to parse {0} component of semver string.")] 64 | Context(&'static str), 65 | #[error("Incomplete input to semver parser.")] 66 | IncompleteInput, 67 | #[error("An unspecified error occurred.")] 68 | Other, 69 | } 70 | 71 | impl Explain for PackageSpecError { 72 | fn meta(&self) -> Option { 73 | let (row, col) = self.location(); 74 | Some(Meta::Parse { 75 | input: self.input.clone(), 76 | path: None, 77 | row, 78 | col, 79 | }) 80 | } 81 | } 82 | 83 | #[derive(Debug)] 84 | pub(crate) struct SpecParseError { 85 | pub(crate) input: I, 86 | pub(crate) context: Option<&'static str>, 87 | pub(crate) kind: Option, 88 | } 89 | 90 | impl ParseError for SpecParseError { 91 | fn from_error_kind(input: I, _kind: nom::error::ErrorKind) -> Self { 92 | Self { 93 | input, 94 | context: None, 95 | kind: None, 96 | } 97 | } 98 | 99 | fn append(_input: I, _kind: nom::error::ErrorKind, other: Self) -> Self { 100 | other 101 | } 102 | } 103 | 104 | impl ContextError for SpecParseError { 105 | fn add_context(_input: I, ctx: &'static str, mut other: Self) -> Self { 106 | other.context = Some(ctx); 107 | other 108 | } 109 | } 110 | 111 | // There's a few parsers that just... manually return SpecParseError in a 112 | // map_res, so this absurd thing is actually needed. Curious? Just comment it 113 | // out and look at all the red. 114 | impl<'a> FromExternalError<&'a str, SpecParseError<&'a str>> for SpecParseError<&'a str> { 115 | fn from_external_error(_input: &'a str, _kind: ErrorKind, e: SpecParseError<&'a str>) -> Self { 116 | e 117 | } 118 | } 119 | 120 | impl<'a> FromExternalError<&'a str, SemverError> for SpecParseError<&'a str> { 121 | fn from_external_error(input: &'a str, _kind: ErrorKind, e: SemverError) -> Self { 122 | SpecParseError { 123 | input, 124 | context: None, 125 | kind: Some(SpecErrorKind::SemverParseError(e)), 126 | } 127 | } 128 | } 129 | 130 | impl<'a> FromExternalError<&'a str, UrlParseError> for SpecParseError<&'a str> { 131 | fn from_external_error(input: &'a str, _kind: ErrorKind, e: UrlParseError) -> Self { 132 | SpecParseError { 133 | input, 134 | context: None, 135 | kind: Some(SpecErrorKind::UrlParseError(e)), 136 | } 137 | } 138 | } 139 | 140 | impl<'a> FromExternalError<&'a str, PackageSpecError> for SpecParseError<&'a str> { 141 | fn from_external_error(input: &'a str, _kind: ErrorKind, e: PackageSpecError) -> Self { 142 | SpecParseError { 143 | input, 144 | context: None, 145 | kind: Some(SpecErrorKind::GitHostParseError(Box::new(e))), 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /crates/package-spec/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::path::PathBuf; 3 | use std::str::FromStr; 4 | 5 | use nom::combinator::all_consuming; 6 | use nom::Err; 7 | use oro_node_semver::{Version, VersionReq as Range}; 8 | 9 | pub use crate::error::{PackageSpecError, SpecErrorKind}; 10 | pub use crate::gitinfo::{GitHost, GitInfo}; 11 | use crate::parsers::package; 12 | 13 | mod error; 14 | mod gitinfo; 15 | mod parsers; 16 | 17 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 18 | pub enum VersionSpec { 19 | Tag(String), 20 | Version(Version), 21 | Range(Range), 22 | } 23 | 24 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 25 | pub enum PackageSpec { 26 | Dir { 27 | path: PathBuf, 28 | }, 29 | Alias { 30 | name: String, 31 | spec: Box, 32 | }, 33 | Npm { 34 | scope: Option, 35 | name: String, 36 | requested: Option, 37 | }, 38 | Git(GitInfo), 39 | } 40 | 41 | impl PackageSpec { 42 | pub fn is_npm(&self) -> bool { 43 | use PackageSpec::*; 44 | match self { 45 | Alias { spec, .. } => spec.is_npm(), 46 | Dir { .. } | Git(..) => false, 47 | Npm { .. } => true, 48 | } 49 | } 50 | 51 | pub fn target(&self) -> &PackageSpec { 52 | use PackageSpec::*; 53 | match self { 54 | Alias { ref spec, .. } => spec, 55 | _ => self, 56 | } 57 | } 58 | } 59 | 60 | impl FromStr for PackageSpec { 61 | type Err = PackageSpecError; 62 | 63 | fn from_str(s: &str) -> Result { 64 | parse_package_spec(s) 65 | } 66 | } 67 | 68 | impl fmt::Display for PackageSpec { 69 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 70 | use PackageSpec::*; 71 | match self { 72 | Dir { path } => write!(f, "{}", path.display()), 73 | Git(info) => write!(f, "{}", info), 74 | Npm { 75 | ref scope, 76 | ref name, 77 | ref requested, 78 | } => { 79 | if let Some(scope) = scope { 80 | write!(f, "@{}/", scope)?; 81 | } 82 | write!(f, "{}", name)?; 83 | if let Some(req) = requested { 84 | write!(f, "{}", req)?; 85 | } 86 | Ok(()) 87 | } 88 | Alias { ref name, ref spec } => { 89 | write!(f, "{}@", name)?; 90 | if let Npm { .. } = **spec { 91 | write!(f, "npm:")?; 92 | } 93 | write!(f, "{}", spec) 94 | } 95 | } 96 | } 97 | } 98 | 99 | impl fmt::Display for VersionSpec { 100 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 101 | use VersionSpec::*; 102 | match self { 103 | Tag(tag) => write!(f, "{}", tag), 104 | Version(v) => write!(f, "{}", v), 105 | Range(range) => write!(f, "{}", range), 106 | } 107 | } 108 | } 109 | 110 | pub fn parse_package_spec(input: I) -> Result 111 | where 112 | I: AsRef, 113 | { 114 | let input = &input.as_ref()[..]; 115 | match all_consuming(package::package_spec)(input) { 116 | Ok((_, arg)) => Ok(arg), 117 | Err(err) => Err(match err { 118 | Err::Error(e) | Err::Failure(e) => PackageSpecError { 119 | input: input.into(), 120 | offset: e.input.as_ptr() as usize - input.as_ptr() as usize, 121 | kind: if let Some(kind) = e.kind { 122 | kind 123 | } else if let Some(ctx) = e.context { 124 | SpecErrorKind::Context(ctx) 125 | } else { 126 | SpecErrorKind::Other 127 | }, 128 | }, 129 | Err::Incomplete(_) => PackageSpecError { 130 | input: input.into(), 131 | offset: input.len() - 1, 132 | kind: SpecErrorKind::IncompleteInput, 133 | }, 134 | }), 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /crates/package-spec/src/parsers/alias.rs: -------------------------------------------------------------------------------- 1 | use nom::branch::alt; 2 | use nom::bytes::complete::{tag_no_case as tag, take_till1}; 3 | use nom::combinator::{map, map_res, opt}; 4 | use nom::error::context; 5 | use nom::sequence::{preceded, tuple}; 6 | use nom::IResult; 7 | 8 | use crate::error::SpecParseError; 9 | use crate::parsers::{git, npm, path, util}; 10 | use crate::PackageSpec; 11 | 12 | // alias_spec := [ [ '@' ], not('/')+ '/' ] not('@/')+ '@' prefixed-package-arg 13 | pub(crate) fn alias_spec<'a>( 14 | input: &'a str, 15 | ) -> IResult<&'a str, PackageSpec, SpecParseError<&'a str>> { 16 | context( 17 | "alias", 18 | map( 19 | tuple(( 20 | opt(scope), 21 | map_res(take_till1(|c| c == '@' || c == '/'), util::no_url_encode), 22 | tag("@"), 23 | prefixed_package_spec, 24 | )), 25 | |(scope, name, _, arg)| { 26 | let mut fullname = String::new(); 27 | if let Some(scope) = scope { 28 | fullname.push_str(&scope); 29 | fullname.push('/'); 30 | } 31 | fullname.push_str(name); 32 | PackageSpec::Alias { 33 | name: fullname, 34 | spec: Box::new(arg), 35 | } 36 | }, 37 | ), 38 | )(input) 39 | } 40 | 41 | /// prefixed_package-arg := ( "npm:" npm-pkg ) | ( [ "file:" ] path ) 42 | fn prefixed_package_spec<'a>( 43 | input: &'a str, 44 | ) -> IResult<&'a str, PackageSpec, SpecParseError<&'a str>> { 45 | context( 46 | "package spec", 47 | alt(( 48 | // Paths don't need to be prefixed, but they can be. 49 | preceded(opt(tag("file:")), path::path_spec), 50 | git::git_spec, 51 | preceded(tag("npm:"), npm::npm_spec), 52 | )), 53 | )(input) 54 | } 55 | 56 | fn scope<'a>(input: &'a str) -> IResult<&'a str, String, SpecParseError<&'a str>> { 57 | context( 58 | "scope", 59 | map( 60 | tuple(( 61 | opt(tag("@")), 62 | map_res(take_till1(|c| c == '/'), util::no_url_encode), 63 | tag("/"), 64 | )), 65 | |(at, scope, _)| { 66 | let mut out = String::new(); 67 | if let Some(at) = at { 68 | out.push_str(at); 69 | } 70 | out.push_str(scope); 71 | out 72 | }, 73 | ), 74 | )(input) 75 | } 76 | -------------------------------------------------------------------------------- /crates/package-spec/src/parsers/git.rs: -------------------------------------------------------------------------------- 1 | use nom::branch::alt; 2 | use nom::bytes::complete::{tag_no_case as tag, take_till1, take_while}; 3 | use nom::combinator::{cut, map, map_res, opt, peek, rest}; 4 | use nom::error::context; 5 | use nom::sequence::{preceded, terminated}; 6 | use nom::IResult; 7 | use oro_node_semver::VersionReq; 8 | use url::Url; 9 | 10 | use crate::error::SpecParseError; 11 | use crate::parsers::util; 12 | use crate::{GitHost, GitInfo, PackageSpec}; 13 | 14 | /// `git-spec := git-shorthand | git-scp | git-url` 15 | pub(crate) fn git_spec<'a>( 16 | input: &'a str, 17 | ) -> IResult<&'a str, PackageSpec, SpecParseError<&'a str>> { 18 | context( 19 | "git package", 20 | map(alt((git_shorthand, git_url, git_scp)), PackageSpec::Git), 21 | )(input) 22 | } 23 | 24 | /// `git-shorthand := [ hosted-git-prefix ] not('/')+ '/' repo` 25 | fn git_shorthand<'a>(input: &'a str) -> IResult<&'a str, GitInfo, SpecParseError<&'a str>> { 26 | let (input, maybe_host) = opt(hosted_git_prefix)(input)?; 27 | let (input, owner) = map_res(take_till1(|c| c == '/'), util::no_url_encode)(input)?; 28 | let (input, repo) = preceded(tag("/"), take_while(|c| c != '#'))(input)?; 29 | let (input, (committish, semver)) = committish(input)?; 30 | Ok(( 31 | input, 32 | GitInfo::Hosted { 33 | host: maybe_host.unwrap_or(GitHost::GitHub), 34 | owner: owner.into(), 35 | repo: repo.into(), 36 | committish: committish.map(String::from), 37 | semver, 38 | requested: None, 39 | }, 40 | )) 41 | } 42 | 43 | /// `hosted-git-prefix := 'github:' | 'bitbucket:' | 'gist:' | 'gitlab:'` 44 | fn hosted_git_prefix<'a>(input: &'a str) -> IResult<&'a str, GitHost, SpecParseError<&'a str>> { 45 | map_res( 46 | terminated( 47 | alt((tag("github"), tag("gist"), tag("gitlab"), tag("bitbucket"))), 48 | tag(":"), 49 | ), 50 | |host: &str| host.parse(), 51 | )(input) 52 | } 53 | 54 | fn committish<'a>( 55 | input: &'a str, 56 | ) -> IResult<&'a str, (Option, Option), SpecParseError<&'a str>> { 57 | let (input, hash) = opt(preceded( 58 | tag("#"), 59 | alt(( 60 | map(preceded(tag("semver:"), cut(semver_range)), |req| { 61 | (None, Some(req)) 62 | }), 63 | map(map_res(rest, util::no_url_encode), |com| (Some(com), None)), 64 | )), 65 | ))(input)?; 66 | Ok(( 67 | input, 68 | if let Some((maybe_comm, maybe_semver)) = hash { 69 | (maybe_comm.map(String::from), maybe_semver) 70 | } else { 71 | (None, None) 72 | }, 73 | )) 74 | } 75 | 76 | fn semver_range<'a>(input: &'a str) -> IResult<&'a str, VersionReq, SpecParseError<&'a str>> { 77 | let (input, range) = map_res(take_till1(|_| false), VersionReq::parse)(input)?; 78 | Ok((input, range)) 79 | } 80 | 81 | fn git_url<'a>(input: &'a str) -> IResult<&'a str, GitInfo, SpecParseError<&'a str>> { 82 | let (input, url) = preceded( 83 | alt((tag("git+"), peek(tag("git://")))), 84 | map_res(take_till1(|c| c == '#'), Url::parse), 85 | )(input)?; 86 | let (input, (committish, semver)) = committish(input)?; 87 | match url.host_str() { 88 | Some(host @ "github.com") 89 | | Some(host @ "gitlab.com") 90 | | Some(host @ "gist.github.com") 91 | | Some(host @ "bitbucket.org") => { 92 | let path = (&url.path()[1..]) 93 | .split('/') 94 | .map(String::from) 95 | .collect::>(); 96 | if let [owner, repo] = &path[..] { 97 | Ok(( 98 | input, 99 | GitInfo::Hosted { 100 | host: match host { 101 | "github.com" => GitHost::GitHub, 102 | "gitlab.com" => GitHost::GitLab, 103 | "gist.github.com" => GitHost::Gist, 104 | "bitbucket.org" => GitHost::Bitbucket, 105 | _ => unreachable!(), 106 | }, 107 | owner: owner.clone(), 108 | repo: if repo.ends_with(".git") { 109 | String::from(&repo[..].replace(".git", "")) 110 | } else { 111 | repo.clone() 112 | }, 113 | committish, 114 | semver, 115 | requested: Some(url.to_string()), 116 | }, 117 | )) 118 | } else { 119 | Ok(( 120 | input, 121 | GitInfo::Url { 122 | url, 123 | committish, 124 | semver, 125 | }, 126 | )) 127 | } 128 | } 129 | _ => Ok(( 130 | input, 131 | GitInfo::Url { 132 | url, 133 | committish, 134 | semver, 135 | }, 136 | )), 137 | } 138 | } 139 | 140 | fn git_scp<'a>(input: &'a str) -> IResult<&'a str, GitInfo, SpecParseError<&'a str>> { 141 | let (input, _) = preceded(opt(tag("git+")), tag("ssh://"))(input)?; 142 | let (input, username) = opt(terminated(take_till1(|c| c == '@'), tag("@")))(input)?; 143 | let (input, host) = take_till1(|c| c == ':' || c == '#')(input)?; 144 | let (input, path) = opt(preceded(tag(":"), take_till1(|c| c == '#')))(input)?; 145 | let (input, (committish, semver)) = committish(input)?; 146 | let mut raw = String::new(); 147 | if let Some(username) = username { 148 | raw.push_str(username); 149 | raw.push('@'); 150 | } 151 | raw.push_str(host); 152 | if let Some(path) = path { 153 | raw.push(':'); 154 | raw.push_str(path); 155 | } 156 | match host { 157 | "github.com" | "gitlab.com" | "gist.github.com" | "bitbucket.org" 158 | if path.is_some() && username.is_some() => 159 | { 160 | let path = path 161 | .unwrap() 162 | .split('/') 163 | .map(String::from) 164 | .collect::>(); 165 | if let [owner, repo] = &path[..] { 166 | let repo = if repo.ends_with(".git") { 167 | String::from(&repo[..].replace(".git", "")) 168 | } else { 169 | repo.clone() 170 | }; 171 | Ok(( 172 | input, 173 | GitInfo::Hosted { 174 | host: match host { 175 | "github.com" => GitHost::GitHub, 176 | "gitlab.com" => GitHost::GitLab, 177 | "gist.github.com" => GitHost::Gist, 178 | "bitbucket.org" => GitHost::Bitbucket, 179 | _ => unreachable!(), 180 | }, 181 | owner: owner.clone(), 182 | repo: if repo.ends_with(".git") { 183 | String::from(&repo[..].replace(".git", "")) 184 | } else { 185 | repo 186 | }, 187 | committish, 188 | semver, 189 | requested: Some(raw), 190 | }, 191 | )) 192 | } else { 193 | Ok(( 194 | input, 195 | GitInfo::Ssh { 196 | ssh: raw, 197 | committish, 198 | semver, 199 | }, 200 | )) 201 | } 202 | } 203 | _ => Ok(( 204 | input, 205 | GitInfo::Ssh { 206 | ssh: raw, 207 | committish, 208 | semver, 209 | }, 210 | )), 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /crates/package-spec/src/parsers/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod alias; 2 | pub mod git; 3 | pub mod npm; 4 | pub mod package; 5 | pub mod path; 6 | pub mod util; 7 | -------------------------------------------------------------------------------- /crates/package-spec/src/parsers/npm.rs: -------------------------------------------------------------------------------- 1 | use oro_node_semver::{Version as SemVerVersion, VersionReq as SemVerVersionReq}; 2 | 3 | use nom::branch::alt; 4 | use nom::bytes::complete::{tag_no_case as tag, take_till1}; 5 | use nom::character::complete::char; 6 | use nom::combinator::{cut, map, map_res, opt}; 7 | use nom::error::context; 8 | use nom::sequence::{delimited, preceded, tuple}; 9 | use nom::IResult; 10 | 11 | use crate::error::SpecParseError; 12 | use crate::parsers::util; 13 | use crate::{PackageSpec, VersionSpec}; 14 | 15 | /// npm-spec := [ '@' not('/')+ '/' ] not('@/')+ [ '@' version-req ] 16 | pub(crate) fn npm_spec<'a>( 17 | input: &'a str, 18 | ) -> IResult<&'a str, PackageSpec, SpecParseError<&'a str>> { 19 | context( 20 | "npm package spec", 21 | map( 22 | tuple(( 23 | opt(delimited( 24 | char('@'), 25 | map_res(take_till1(|c| c == '/'), util::no_url_encode), 26 | char('/'), 27 | )), 28 | map_res(take_till1(|x| x == '@' || x == '/'), util::no_url_encode), 29 | opt(preceded(tag("@"), cut(version_req))), 30 | )), 31 | |(scope_opt, name, req)| { 32 | let name = if let Some(scope) = scope_opt { 33 | format!("@{}/{}", scope, name) 34 | } else { 35 | name.into() 36 | }; 37 | PackageSpec::Npm { 38 | scope: scope_opt.map(|x| x.into()), 39 | name, 40 | requested: req, 41 | } 42 | }, 43 | ), 44 | )(input) 45 | } 46 | 47 | fn version_req<'a>(input: &'a str) -> IResult<&'a str, VersionSpec, SpecParseError<&'a str>> { 48 | context( 49 | "version requirement", 50 | alt((semver_version, semver_range, version_tag)), 51 | )(input) 52 | } 53 | 54 | fn semver_version<'a>(input: &'a str) -> IResult<&'a str, VersionSpec, SpecParseError<&'a str>> { 55 | let (input, version) = map_res(take_till1(|_| false), SemVerVersion::parse)(input)?; 56 | Ok((input, VersionSpec::Version(version))) 57 | } 58 | 59 | fn semver_range<'a>(input: &'a str) -> IResult<&'a str, VersionSpec, SpecParseError<&'a str>> { 60 | let (input, range) = map_res(take_till1(|_| false), SemVerVersionReq::parse)(input)?; 61 | Ok((input, VersionSpec::Range(range))) 62 | } 63 | 64 | fn version_tag<'a>(input: &'a str) -> IResult<&'a str, VersionSpec, SpecParseError<&'a str>> { 65 | context( 66 | "dist tag", 67 | map(map_res(take_till1(|_| false), util::no_url_encode), |t| { 68 | VersionSpec::Tag(t.into()) 69 | }), 70 | )(input) 71 | } 72 | -------------------------------------------------------------------------------- /crates/package-spec/src/parsers/package.rs: -------------------------------------------------------------------------------- 1 | use nom::branch::alt; 2 | use nom::bytes::complete::tag_no_case as tag; 3 | use nom::combinator::opt; 4 | use nom::error::context; 5 | use nom::sequence::preceded; 6 | use nom::IResult; 7 | 8 | use crate::error::SpecParseError; 9 | use crate::parsers::{alias, git, npm, path}; 10 | use crate::PackageSpec; 11 | 12 | /// package-spec := alias | ( [ "npm:" ] npm-pkg ) | ( [ "file:" ] path ) | git-pkg 13 | pub(crate) fn package_spec<'a>( 14 | input: &'a str, 15 | ) -> IResult<&'a str, PackageSpec, SpecParseError<&'a str>> { 16 | context( 17 | "package arg", 18 | alt(( 19 | alias::alias_spec, 20 | preceded(opt(tag("file:")), path::path_spec), 21 | git::git_spec, 22 | preceded(opt(tag("npm:")), npm::npm_spec), 23 | )), 24 | )(input) 25 | } 26 | -------------------------------------------------------------------------------- /crates/package-spec/src/parsers/path.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use nom::branch::alt; 4 | use nom::bytes::complete::tag_no_case as tag; 5 | use nom::character::complete::{anychar, one_of}; 6 | use nom::combinator::{map, map_res, opt, recognize, rest}; 7 | use nom::error::context; 8 | use nom::multi::{many0, many1}; 9 | use nom::sequence::{delimited, preceded, tuple}; 10 | use nom::IResult; 11 | 12 | use crate::error::{SpecErrorKind, SpecParseError}; 13 | use crate::PackageSpec; 14 | 15 | /// path := ( relative-dir | absolute-dir ) 16 | pub(crate) fn path_spec<'a>( 17 | input: &'a str, 18 | ) -> IResult<&'a str, PackageSpec, SpecParseError<&'a str>> { 19 | context( 20 | "path spec", 21 | map(alt((relative_path, absolute_path)), |p| PackageSpec::Dir { 22 | path: p, 23 | }), 24 | )(input) 25 | } 26 | 27 | /// relative-path := [ '.' ] '.' [path-sep] .* 28 | fn relative_path<'a>(input: &'a str) -> IResult<&'a str, PathBuf, SpecParseError<&'a str>> { 29 | context( 30 | "relative path", 31 | map( 32 | recognize(tuple((tag("."), opt(tag(".")), many0(path_sep), rest))), 33 | PathBuf::from, 34 | ), 35 | )(input) 36 | } 37 | 38 | /// absolute-path := [ alpha ':' ] path-sep+ [ '?' path-sep+ ] .* 39 | fn absolute_path<'a>(input: &'a str) -> IResult<&'a str, PathBuf, SpecParseError<&'a str>> { 40 | context( 41 | "absolute path", 42 | map( 43 | recognize(preceded( 44 | delimited( 45 | opt(preceded( 46 | map_res(anychar, |c| { 47 | if c.is_alphabetic() { 48 | Ok(c) 49 | } else { 50 | Err(SpecParseError { 51 | input, 52 | context: None, 53 | kind: Some(SpecErrorKind::InvalidDriveLetter(c)), 54 | }) 55 | } 56 | }), 57 | tag(":"), 58 | )), 59 | many1(path_sep), 60 | opt(preceded(tag("?"), many1(path_sep))), 61 | ), 62 | rest, 63 | )), 64 | PathBuf::from, 65 | ), 66 | )(input) 67 | } 68 | 69 | /// path-sep := ( '/' | '\' ) 70 | fn path_sep<'a>(input: &'a str) -> IResult<&'a str, char, SpecParseError<&'a str>> { 71 | one_of("/\\")(input) 72 | } 73 | -------------------------------------------------------------------------------- /crates/package-spec/src/parsers/util.rs: -------------------------------------------------------------------------------- 1 | use percent_encoding::{utf8_percent_encode, AsciiSet, NON_ALPHANUMERIC}; 2 | 3 | use crate::error::{SpecErrorKind, SpecParseError}; 4 | 5 | const JS_ENCODED: &AsciiSet = { 6 | &NON_ALPHANUMERIC 7 | .remove(b'-') 8 | .remove(b'_') 9 | .remove(b'.') 10 | .remove(b'!') 11 | .remove(b'~') 12 | .remove(b'*') 13 | .remove(b'\'') 14 | .remove(b'(') 15 | .remove(b')') 16 | }; 17 | 18 | pub(crate) fn no_url_encode(tag: &str) -> Result<&str, SpecParseError<&str>> { 19 | if format!("{}", utf8_percent_encode(tag, JS_ENCODED)) == tag { 20 | Ok(tag) 21 | } else { 22 | Err(SpecParseError { 23 | input: tag, 24 | context: None, 25 | kind: Some(SpecErrorKind::InvalidCharacters(tag.into())), 26 | }) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /crates/package-spec/tests/dir.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use oro_package_spec::{PackageSpec, PackageSpecError}; 4 | 5 | type Result = std::result::Result; 6 | 7 | fn parse(input: &str) -> Result { 8 | input.parse() 9 | } 10 | 11 | #[test] 12 | fn relative_path_current_dir() -> Result<()> { 13 | let res = parse("./")?; 14 | assert_eq!( 15 | res, 16 | PackageSpec::Dir { 17 | path: PathBuf::from("./"), 18 | } 19 | ); 20 | Ok(()) 21 | } 22 | 23 | #[test] 24 | fn relative_path_current_dir_no_slash() -> Result<()> { 25 | let res = parse(".")?; 26 | assert_eq!( 27 | res, 28 | PackageSpec::Dir { 29 | path: PathBuf::from("."), 30 | } 31 | ); 32 | Ok(()) 33 | } 34 | 35 | #[test] 36 | fn relative_path_unix() -> Result<()> { 37 | let res = parse("./foo/bar/baz")?; 38 | assert_eq!( 39 | res, 40 | PackageSpec::Dir { 41 | path: PathBuf::from("./foo/bar/baz"), 42 | } 43 | ); 44 | Ok(()) 45 | } 46 | 47 | #[test] 48 | fn absolute_path_unix() -> Result<()> { 49 | let res = parse("/foo/bar/baz")?; 50 | assert_eq!( 51 | res, 52 | PackageSpec::Dir { 53 | path: PathBuf::from("/foo/bar/baz"), 54 | } 55 | ); 56 | Ok(()) 57 | } 58 | 59 | #[test] 60 | fn relative_path_windows() -> Result<()> { 61 | let res = parse(".\\foo\\bar\\baz")?; 62 | assert_eq!( 63 | res, 64 | PackageSpec::Dir { 65 | path: PathBuf::from(".\\foo\\bar\\baz"), 66 | } 67 | ); 68 | Ok(()) 69 | } 70 | 71 | #[test] 72 | fn absolute_path_windows() -> Result<()> { 73 | let res = parse("C:\\foo\\bar\\baz")?; 74 | assert_eq!( 75 | res, 76 | PackageSpec::Dir { 77 | path: PathBuf::from("C:\\foo\\bar\\baz"), 78 | } 79 | ); 80 | Ok(()) 81 | } 82 | 83 | #[test] 84 | fn absolute_path_windows_qmark() -> Result<()> { 85 | let res = parse("\\\\?\\foo\\bar\\baz")?; 86 | assert_eq!( 87 | res, 88 | PackageSpec::Dir { 89 | path: PathBuf::from("\\\\?\\foo\\bar\\baz"), 90 | } 91 | ); 92 | Ok(()) 93 | } 94 | 95 | #[test] 96 | fn absolute_path_windows_double_slash() -> Result<()> { 97 | let res = parse("\\\\foo\\bar\\baz")?; 98 | assert_eq!( 99 | res, 100 | PackageSpec::Dir { 101 | path: PathBuf::from("\\\\foo\\bar\\baz"), 102 | } 103 | ); 104 | Ok(()) 105 | } 106 | 107 | #[test] 108 | fn absolute_path_windows_multiple_drive_letters() -> Result<()> { 109 | let res = parse("ACAB:\\foo\\bar\\baz"); 110 | assert!(res.is_err()); 111 | Ok(()) 112 | } 113 | 114 | #[test] 115 | fn named() -> Result<()> { 116 | let res = parse("foo@./hey")?; 117 | assert_eq!( 118 | res, 119 | PackageSpec::Alias { 120 | name: "foo".into(), 121 | spec: Box::new(PackageSpec::Dir { 122 | path: PathBuf::from("./hey"), 123 | }) 124 | } 125 | ); 126 | Ok(()) 127 | } 128 | 129 | #[test] 130 | fn spaces() -> Result<()> { 131 | // NOTE: This succeeds in NPM, but we treat it as an error because we 132 | // require ./ for relative paths. 133 | let res = parse("@f fo o al/ a d s ;f"); 134 | assert!(res.is_err()); 135 | Ok(()) 136 | } 137 | -------------------------------------------------------------------------------- /crates/package-spec/tests/npm.rs: -------------------------------------------------------------------------------- 1 | use oro_node_semver::{Version as SemVerVersion, VersionReq as SemVerVersionReq}; 2 | use oro_package_spec::{PackageSpec, PackageSpecError, VersionSpec}; 3 | 4 | type Result = std::result::Result; 5 | 6 | fn parse(input: &str) -> Result { 7 | input.parse() 8 | } 9 | 10 | fn version_req(input: &str) -> Option { 11 | Some(VersionSpec::Range(SemVerVersionReq::parse(input).unwrap())) 12 | } 13 | 14 | #[test] 15 | fn npm_pkg_basic() -> Result<()> { 16 | let res = parse("hello-world")?; 17 | assert_eq!( 18 | res, 19 | PackageSpec::Npm { 20 | scope: None, 21 | name: "hello-world".into(), 22 | requested: None 23 | } 24 | ); 25 | Ok(()) 26 | } 27 | 28 | #[test] 29 | fn npm_pkg_tag() -> Result<()> { 30 | let res = parse("hello-world@latest")?; 31 | assert_eq!( 32 | res, 33 | PackageSpec::Npm { 34 | scope: None, 35 | name: "hello-world".into(), 36 | requested: Some(VersionSpec::Tag("latest".into())) 37 | } 38 | ); 39 | Ok(()) 40 | } 41 | 42 | #[test] 43 | fn alias_npm_pkg_basic() -> Result<()> { 44 | let res = parse("foo@npm:hello-world")?; 45 | assert_eq!( 46 | res, 47 | PackageSpec::Alias { 48 | name: "foo".into(), 49 | spec: Box::new(PackageSpec::Npm { 50 | scope: None, 51 | name: "hello-world".into(), 52 | requested: None 53 | }) 54 | } 55 | ); 56 | Ok(()) 57 | } 58 | 59 | #[test] 60 | fn alias_not_recursive() -> Result<()> { 61 | let res = parse("foo@bar@npm:hello-world"); 62 | assert!(res.is_err()); 63 | Ok(()) 64 | } 65 | 66 | #[test] 67 | fn npm_pkg_prefixed() -> Result<()> { 68 | let res = parse("npm:hello-world")?; 69 | assert_eq!( 70 | res, 71 | PackageSpec::Npm { 72 | scope: None, 73 | name: "hello-world".into(), 74 | requested: None 75 | } 76 | ); 77 | Ok(()) 78 | } 79 | 80 | #[test] 81 | fn npm_pkg_scoped() -> Result<()> { 82 | let res = parse("@hello/world")?; 83 | assert_eq!( 84 | res, 85 | PackageSpec::Npm { 86 | scope: Some("hello".into()), 87 | name: "@hello/world".into(), 88 | requested: None 89 | } 90 | ); 91 | Ok(()) 92 | } 93 | 94 | #[test] 95 | fn npm_pkg_with_req() -> Result<()> { 96 | let res = parse("hello-world@1.2.3")?; 97 | assert_eq!( 98 | res, 99 | PackageSpec::Npm { 100 | scope: None, 101 | name: "hello-world".into(), 102 | requested: Some(VersionSpec::Version(SemVerVersion::parse("1.2.3").unwrap())) 103 | } 104 | ); 105 | Ok(()) 106 | } 107 | 108 | #[test] 109 | fn npm_pkg_with_tag() -> Result<()> { 110 | let res = parse("hello-world@howdy")?; 111 | assert_eq!( 112 | res, 113 | PackageSpec::Npm { 114 | scope: None, 115 | name: "hello-world".into(), 116 | requested: Some(VersionSpec::Tag("howdy".into())), 117 | } 118 | ); 119 | Ok(()) 120 | } 121 | 122 | #[test] 123 | fn npm_pkg_scoped_with_req() -> Result<()> { 124 | let res = parse("@hello/world@1.2.3")?; 125 | assert_eq!( 126 | res, 127 | PackageSpec::Npm { 128 | scope: Some("hello".into()), 129 | name: "@hello/world".into(), 130 | requested: Some(VersionSpec::Version(SemVerVersion::parse("1.2.3").unwrap())) 131 | } 132 | ); 133 | Ok(()) 134 | } 135 | 136 | #[test] 137 | fn npm_pkg_prefixed_with_req() -> Result<()> { 138 | let res = parse("npm:@hello/world@1.2.3")?; 139 | assert_eq!( 140 | res, 141 | PackageSpec::Npm { 142 | scope: Some("hello".into()), 143 | name: "@hello/world".into(), 144 | requested: Some(VersionSpec::Version(SemVerVersion::parse("1.2.3").unwrap())) 145 | } 146 | ); 147 | Ok(()) 148 | } 149 | 150 | #[test] 151 | fn odd_npm_example_with_prerelease() -> Result<()> { 152 | let res = parse("world@>1.1.0-beta-10")?; 153 | assert_eq!( 154 | res, 155 | PackageSpec::Npm { 156 | scope: None, 157 | name: "world".into(), 158 | requested: version_req(">1.1.0-beta-10"), 159 | } 160 | ); 161 | Ok(()) 162 | } 163 | 164 | #[test] 165 | fn approximately_equivalent_version() -> Result<()> { 166 | let res = parse("world@~1.1.0")?; 167 | assert_eq!( 168 | res, 169 | PackageSpec::Npm { 170 | scope: None, 171 | name: "world".into(), 172 | requested: version_req("~1.1.0"), 173 | } 174 | ); 175 | Ok(()) 176 | } 177 | 178 | #[test] 179 | fn compatible_equivalent_version() -> Result<()> { 180 | let res = parse("world@^1.1.0")?; 181 | assert_eq!( 182 | res, 183 | PackageSpec::Npm { 184 | scope: None, 185 | name: "world".into(), 186 | requested: version_req("^1.1.0"), 187 | } 188 | ); 189 | Ok(()) 190 | } 191 | 192 | #[test] 193 | fn x_version() -> Result<()> { 194 | let res = parse("world@1.1.x")?; 195 | assert_eq!( 196 | res, 197 | PackageSpec::Npm { 198 | scope: None, 199 | name: "world".into(), 200 | requested: version_req("1.1.x"), 201 | } 202 | ); 203 | Ok(()) 204 | } 205 | 206 | #[test] 207 | fn hyphen_version_range() -> Result<()> { 208 | let res = parse("world@1.5.0 - 2.1.0")?; 209 | assert_eq!( 210 | res, 211 | PackageSpec::Npm { 212 | scope: None, 213 | name: "world".into(), 214 | requested: version_req("1.5.0 - 2.1.0"), 215 | } 216 | ); 217 | Ok(()) 218 | } 219 | 220 | #[test] 221 | fn alternate_version_ranges() -> Result<()> { 222 | let res = parse("world@1.5.0 - 2.1.0 || 2.3.x")?; 223 | assert_eq!( 224 | res, 225 | PackageSpec::Npm { 226 | scope: None, 227 | name: "world".into(), 228 | requested: version_req("1.5.0 - 2.1.0 || 2.3.x"), 229 | } 230 | ); 231 | Ok(()) 232 | } 233 | 234 | #[test] 235 | fn npm_pkg_bad_tag() -> Result<()> { 236 | let res = parse("hello-world@%&W$@#$"); 237 | assert!(res.is_err()); 238 | Ok(()) 239 | } 240 | -------------------------------------------------------------------------------- /dist/volt.iss: -------------------------------------------------------------------------------- 1 | ; Script generated by the Inno Setup Script Wizard. 2 | ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! 3 | 4 | #define MyAppName "Volt" 5 | #define MyAppVersion "1.0.0-alpha" 6 | #define MyAppPublisher "TurboSurge" 7 | #define MyAppURL "https://voltpkg.com" 8 | #define MyAppExeName "volt_cli.exe" 9 | 10 | [Setup] 11 | ; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications. 12 | ; (To generate a new GUID, click Tools | Generate GUID inside the IDE.) 13 | AppId={{6D78E4B8-B1CA-48D8-AA62-073C1D750C6F} 14 | AppName={#MyAppName} 15 | AppVersion={#MyAppVersion} 16 | ;AppVerName={#MyAppName} {#MyAppVersion} 17 | AppPublisher={#MyAppPublisher} 18 | AppPublisherURL={#MyAppURL} 19 | AppSupportURL={#MyAppURL} 20 | AppUpdatesURL={#MyAppURL} 21 | DefaultDirName={autopf}\{#MyAppName} 22 | DefaultGroupName={#MyAppName} 23 | DisableProgramGroupPage=yes 24 | ; Remove the following line to run in administrative install mode (install for all users.) 25 | PrivilegesRequired=lowest 26 | PrivilegesRequiredOverridesAllowed=dialog 27 | OutputDir=C:\Users\xtrem\Desktop\volt\dist 28 | OutputBaseFilename=Volt Setup 29 | Compression=lzma 30 | SolidCompression=yes 31 | WizardStyle=modern 32 | LZMAUseSeparateProcess=yes 33 | LZMANumBlockThreads=6 34 | 35 | 36 | [Languages] 37 | Name: "english"; MessagesFile: "compiler:Default.isl" 38 | 39 | [Files] 40 | Source: "C:\Users\xtrem\Desktop\volt\target\release\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion 41 | ; NOTE: Don't use "Flags: ignoreversion" on any shared system files 42 | 43 | [Icons] 44 | Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}" 45 | 46 | [Run] 47 | Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent 48 | 49 | -------------------------------------------------------------------------------- /hooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | cargo fmt --all 3 | git add . 4 | echo '✅ Formatted Files' -------------------------------------------------------------------------------- /rust-toolchain: -------------------------------------------------------------------------------- 1 | # If you edit this file, be sure to update 2 | # the github action as well 3 | [toolchain] 4 | channel = "stable" 5 | components = [ "rustfmt", "clippy" ] 6 | profile = "minimal" 7 | 8 | # vim: ft=toml 9 | -------------------------------------------------------------------------------- /src/cli/cli.rs: -------------------------------------------------------------------------------- 1 | use crate::commands::{ 2 | add, clean, clone, discord, info, init, list, login, node, outdated, run, search, 3 | }; // remove outdated later 4 | use async_trait::async_trait; 5 | use clap::{crate_authors, crate_description, crate_name, crate_version, Parser, Subcommand}; 6 | 7 | use super::VoltConfig; 8 | 9 | /// A trait to be implemented by subcommands 10 | #[async_trait] 11 | pub trait VoltCommand { 12 | async fn exec(self, config: VoltConfig) -> miette::Result<()>; 13 | } 14 | 15 | /// Volt CLI subcommands 16 | #[derive(Debug, Subcommand)] 17 | pub enum VoltSubCmd { 18 | Add(add::Add), 19 | Clone(clone::Clone), 20 | Init(init::Init), 21 | Clean(clean::Clean), 22 | Discord(discord::Discord), 23 | Search(search::Search), 24 | Login(login::Login), 25 | Run(run::Run), 26 | Info(info::Info), 27 | Node(node::Node), 28 | Outdated(outdated::Outdated), // remove later??? 29 | List(list::List), // remove later??? 30 | } 31 | 32 | #[async_trait] 33 | impl VoltCommand for VoltSubCmd { 34 | async fn exec(self, config: VoltConfig) -> miette::Result<()> { 35 | match self { 36 | Self::Add(x) => x.exec(config).await, 37 | Self::Clone(x) => x.exec(config).await, 38 | Self::Init(x) => x.exec(config).await, 39 | Self::Clean(x) => x.exec(config).await, 40 | Self::Discord(x) => x.exec(config).await, 41 | Self::Search(x) => x.exec(config).await, 42 | Self::Login(x) => x.exec(config).await, 43 | Self::Run(x) => x.exec(config).await, 44 | Self::Info(x) => x.exec(config).await, 45 | Self::Node(x) => x.exec(config).await, 46 | Self::Outdated(x) => x.exec(config).await, // remove later 47 | Self::List(x) => x.exec(config).await, // remove later 48 | } 49 | } 50 | } 51 | 52 | #[derive(Debug, Parser)] 53 | #[clap( 54 | name = crate_name!(), 55 | version = crate_version!(), 56 | about = crate_description!(), 57 | author = crate_authors!(), 58 | disable_colored_help = true, 59 | )] 60 | #[allow(clippy::module_name_repetitions)] 61 | pub struct VoltCli { 62 | #[clap(flatten)] 63 | pub config: VoltConfig, 64 | 65 | #[clap(subcommand)] 66 | pub cmd: VoltSubCmd, 67 | } 68 | 69 | impl VoltCli { 70 | pub fn new() -> Self { 71 | Self::parse() 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/cli/config.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Volt Contributors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | use crate::core::utils::errors::VoltError; 18 | 19 | use clap::Parser; 20 | use ssri::Algorithm; 21 | use std::{env, path::PathBuf}; 22 | 23 | #[derive(Debug, Clone, Parser)] 24 | pub struct VoltConfig { 25 | /// Path to current working directory 26 | #[clap(short, long)] 27 | cwd: Option, 28 | } 29 | 30 | impl VoltConfig { 31 | pub const _OS: &'static str = env::consts::OS; 32 | pub const VOLT_HOME: &'static str = ".volt"; 33 | pub const _VOLT_LOCK: &'static str = "volt.lock"; 34 | 35 | pub fn home(&self) -> miette::Result { 36 | Ok(dirs::home_dir().ok_or(VoltError::GetHomeDirError)?) 37 | } 38 | 39 | /// Return the current directory (defaults to `.` if not provided) 40 | pub fn cwd(&self) -> miette::Result { 41 | Ok(self.cwd.to_owned().unwrap_or({ 42 | env::current_dir().map_err(|e| VoltError::EnvironmentError { 43 | env: "CURRENT_DIRECTORY".to_string(), 44 | source: e, 45 | })? 46 | })) 47 | } 48 | 49 | /// Path to the volt lockfile (defaults to `./volt.lock`) 50 | pub fn _lockfile(&self) -> miette::Result { 51 | Ok(self.cwd()?.join(Self::_VOLT_LOCK)) 52 | } 53 | 54 | /// Path to the `node_modules` directory (defaults to `./node_modules`) 55 | pub fn node_modules(&self) -> miette::Result { 56 | Ok(self.cwd()?.join("node_modules")) 57 | } 58 | 59 | /// Path to the config directory (defaults to `~/.volt`) 60 | pub fn volt_home(&self) -> miette::Result { 61 | Ok(self.home()?.join(Self::VOLT_HOME)) 62 | } 63 | 64 | /// Calculate the hash of a tarball 65 | /// 66 | /// ## Examples 67 | /// ```rs 68 | /// calc_hash(bytes::Bytes::new(), ssri::Algorithm::Sha1)?; 69 | /// ``` 70 | /// ## Returns 71 | /// * Result 72 | pub fn calc_hash(data: &bytes::Bytes, algorithm: Algorithm) -> miette::Result { 73 | let integrity = if algorithm == Algorithm::Sha1 { 74 | let hash = ssri::IntegrityOpts::new() 75 | .algorithm(Algorithm::Sha1) 76 | .chain(&data) 77 | .result(); 78 | format!("sha1-{}", hash.to_hex().1) 79 | } else { 80 | ssri::IntegrityOpts::new() 81 | .algorithm(Algorithm::Sha512) 82 | .chain(&data) 83 | .result() 84 | .to_string() 85 | }; 86 | 87 | Ok(integrity) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/cli/mod.rs: -------------------------------------------------------------------------------- 1 | #[allow(clippy::module_inception)] 2 | mod cli; 3 | mod config; 4 | 5 | pub use cli::*; 6 | pub use config::*; 7 | -------------------------------------------------------------------------------- /src/commands/audit.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Volt Contributors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | //! Handle an unknown command (can be listed in scripts). 18 | 19 | use async_trait::async_trait; 20 | use miette::Result; 21 | use serde::{Deserialize, Serialize}; 22 | 23 | use std::collections::HashMap; 24 | 25 | use crate::cli::{VoltCommand, VoltConfig}; 26 | 27 | pub struct Audit {} 28 | 29 | #[derive(Debug)] 30 | pub struct AuditObject { 31 | _name: String, 32 | _version: String, 33 | _install: Vec, 34 | _remove: Vec, 35 | _metadata: HashMap, 36 | _requires: HashMap, 37 | _dependencies: HashMap, 38 | } 39 | 40 | #[derive(Debug)] 41 | pub struct AuditDependency { 42 | _version: String, 43 | _integrity: String, 44 | _requires: HashMap, 45 | _dependencies: HashMap, 46 | _dev: bool, 47 | } 48 | 49 | #[derive(Debug, Serialize, Deserialize)] 50 | pub struct AuditResponse { 51 | actions: Vec, 52 | advisories: HashMap, 53 | muted: Vec, 54 | metadata: AuditMetadata, 55 | } 56 | 57 | #[derive(Debug, Serialize, Deserialize)] 58 | pub struct Vulnerabilities { 59 | info: u128, 60 | low: u128, 61 | moderate: u128, 62 | high: u128, 63 | critical: u128, 64 | } 65 | 66 | #[derive(Debug, Serialize, Deserialize)] 67 | #[serde(rename_all = "camelCase")] 68 | pub struct AuditMetadata { 69 | vulnerabilities: Vulnerabilities, 70 | dependencies: u128, 71 | dev_dependencies: u128, 72 | optional_dependencies: u128, 73 | total_dependencies: u128, 74 | } 75 | 76 | // pub fn flatten_dependency_tree( 77 | // package: &VoltPackage, 78 | // volt_packages: &HashMap, 79 | // ) -> Vec { 80 | // let mut packages: Vec = vec![]; 81 | 82 | // if package.dependencies.is_some() { 83 | // for dep in package.dependencies.as_ref().unwrap().iter() { 84 | // let dependency = volt_packages.get(dep).unwrap(); 85 | 86 | // let mut requires: HashMap = HashMap::new(); 87 | 88 | // if dependency.dependencies.is_some() { 89 | // for de in dependency.dependencies.as_ref().unwrap() { 90 | // let pkg = volt_packages.get(de).unwrap(); 91 | // requires.insert(de.to_owned(), pkg.version.clone()); 92 | // } 93 | // } 94 | 95 | // let fdt = &mut flatten_dependency_tree(dependency, volt_packages); 96 | 97 | // packages.push(AuditDependency { 98 | // version: dependency.version, 99 | // integrity: dependency.integrity, 100 | // requires: requires, 101 | // dependencies: HashMap::new() 102 | // .insert( 103 | // dep.to_owned(), 104 | // , 105 | // ) 106 | // .unwrap(), 107 | // dev: false, 108 | // }); 109 | // } 110 | // } 111 | 112 | // packages 113 | // } 114 | 115 | #[async_trait] 116 | impl VoltCommand for Audit { 117 | /// Execute the `volt audit` command 118 | /// 119 | /// Execute a audit command 120 | /// ## Arguments 121 | /// * `error` - Instance of the command (`Arc`) 122 | /// ## Examples 123 | /// ``` 124 | /// // 125 | /// // .exec() is an async call so you need to await it 126 | /// Audit.exec(app).await; 127 | /// ``` 128 | /// ## Returns 129 | /// * `Result<()>` 130 | async fn exec(self, _config: VoltConfig) -> Result<()> { 131 | // let package_json = PackageJson::from("package.json"); 132 | 133 | // let mut requires = package_json.dependencies; 134 | // requires.extend(package_json.dev_dependencies); 135 | 136 | // let responses = 137 | // utils::get_volt_response_multi(requires.keys().cloned().collect::>()).await; 138 | 139 | // let mut dependencies: HashMap = HashMap::new(); 140 | 141 | // let start = Instant::now(); 142 | 143 | // for res in responses { 144 | // let version = res.version; 145 | 146 | // let packages = &res.versions.get(&version).unwrap().packages; 147 | 148 | // for package in packages { 149 | // let flattened_dependency_tree: Vec = 150 | // flatten_dependency_tree(package.1, packages); 151 | 152 | // for dp in flattened_dependency_tree { 153 | // dependencies.insert(package.0.to_owned(), dp); 154 | // } 155 | // } 156 | // } 157 | 158 | // let audit = AuditObject { 159 | // name: package_json.name, 160 | // version: package_json.version, 161 | // install: vec![], 162 | // remove: vec![], 163 | // metadata: HashMap::new(), 164 | // requires: requires, 165 | // dependencies: dependencies, 166 | // }; 167 | 168 | // let mut response = chttp::post_async( 169 | // "http://registry.npmjs.org/-/npm/v1/security/audits", 170 | // format!("{:?}", audit), 171 | // ) 172 | // .await 173 | // .unwrap(); 174 | 175 | // let text = response.text_async().await.unwrap(); 176 | 177 | // let response: AuditResponse = serde_json::from_str(text.as_str()).unwrap(); 178 | 179 | Ok(()) 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /src/commands/cache.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Volt Contributors 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. 12 | */ 13 | 14 | //! Clean cached download files. 15 | 16 | use crate::{ 17 | App, 18 | Command, 19 | coreutils::constants::PROGRESS_CHARS, 20 | }; 21 | use volt_core::VERSION; 22 | 23 | use async_trait::async_trait; 24 | use colored::Colorize; 25 | use indicatif::{ProgressBar, ProgressStyle}; 26 | use miette::Result; 27 | 28 | use std::{ 29 | env::temp_dir, 30 | fs::remove_file, 31 | process::exit, 32 | sync::Arc, 33 | }; 34 | 35 | /// Struct implementation for the `Add` command. 36 | #[derive(Clone)] 37 | pub struct Cache {} 38 | 39 | #[async_trait] 40 | impl Command for Cache { 41 | /// Display a help menu for the `volt cahe` command. 42 | fn help() -> String { 43 | format!( 44 | r#"volt {} 45 | 46 | Handle the volt cache files. 47 | Usage: {} {} {} 48 | 49 | Commands: 50 | clean - Clean downloaded cache files and metadata. 51 | 52 | Options: 53 | 54 | {} {} Output verbose messages on internal operations. 55 | {} {} Disable progress bar."#, 56 | VERSION.bright_green().bold(), 57 | "volt".bright_green().bold(), 58 | "cache".bright_purple(), 59 | "[command]".bright_purple(), 60 | "--verbose".blue(), 61 | "(-v)".yellow(), 62 | "--no-progress".blue(), 63 | "(-np)".yellow() 64 | ) 65 | } 66 | 67 | /// Execute the `volt cache` command 68 | /// 69 | /// Clean your download cache. 70 | /// ## Arguments 71 | /// * `app` - Instance of the command (`Arc`) 72 | /// ## Examples 73 | /// ``` 74 | /// // Clean your download cache (does not break symlinks) 75 | /// // .exec() is an async call so you need to await it 76 | /// Add.exec(app).await; 77 | /// ``` 78 | /// ## Returns 79 | /// * `Result<()>` 80 | async fn exec(app: Arc) -> Result<()> { 81 | // if app.args.len() == 1 { 82 | // println!("{}", Self::help()); 83 | // exit(1); 84 | // } 85 | // if app.args[1].as_str() == "clean" { 86 | // let files: Vec<_> = fs::read_dir(temp_dir().join("volt")).unwrap().collect(); 87 | 88 | // let count = files.len(); 89 | 90 | // let progress_bar = ProgressBar::new(count.to_owned() as u64); 91 | 92 | // progress_bar.set_style( 93 | // ProgressStyle::default_bar() 94 | // .progress_chars(PROGRESS_CHARS) 95 | // .template(&format!( 96 | // "{} [{{bar:40.magenta/blue}}] {{msg:.blue}} {{len}} / {{pos}}", 97 | // "Deleting Cache".bright_blue() 98 | // )), 99 | // ); 100 | 101 | // for file in files { 102 | // let os_str = file.unwrap().file_name(); 103 | // let f_name = format!(r"{}volt\{}", temp_dir().display(), os_str.to_str().unwrap()); 104 | 105 | // remove_file(f_name).unwrap(); 106 | // progress_bar.inc(1); 107 | // } 108 | 109 | // progress_bar.finish(); 110 | // } 111 | Ok(()) 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/commands/check.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Volt Contributors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | //! Check for errors 18 | 19 | use crate::cli::{VoltCommand, VoltConfig}; 20 | 21 | use async_trait::async_trait; 22 | use miette::Result; 23 | 24 | /// Struct implementation for the `Check` command. 25 | pub struct Check; 26 | 27 | #[async_trait] 28 | impl VoltCommand for Check { 29 | /// Execute the `volt Check` command 30 | /// 31 | /// Removes a package from your direct dependencies. 32 | /// ## Examples 33 | /// ``` 34 | /// // .exec() is an async call so you need to await it 35 | /// Create.exec(app, vec![], vec!["--verbose"]).await; 36 | /// ``` 37 | /// ## Returns 38 | /// * `Result<()>` 39 | async fn exec(self, _config: VoltConfig) -> Result<()> { 40 | Ok(()) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/commands/clone.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Volt Contributors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | //! Clone and setup a repository from Github. 18 | 19 | use crate::cli::{VoltCommand, VoltConfig}; 20 | 21 | use async_trait::async_trait; 22 | use clap::Parser; 23 | use std::process; 24 | 25 | /// Clone a project and setup a project from a repository 26 | #[derive(Debug, Parser)] 27 | pub struct Clone { 28 | /// URL of the repository 29 | repository: String, 30 | } 31 | 32 | #[async_trait] 33 | impl VoltCommand for Clone { 34 | /// Execute the `volt clone` command 35 | /// 36 | /// Clone and setup a repository from Github 37 | /// ## Arguments 38 | /// * `app` - Instance of the command (`Arc`) 39 | /// ## Examples 40 | /// ``` 41 | /// // Clone the react repository (https://github.com/facebook/react) 42 | /// // .exec() is an async call so you need to await it 43 | /// Add.exec(app).await; 44 | /// ``` 45 | /// ## Returns 46 | /// * `Result<()>` 47 | async fn exec(self, _config: VoltConfig) -> miette::Result<()> { 48 | let exit_code = process::Command::new("cmd") 49 | .arg(format!("/C git clone {} --depth=1", self.repository).as_str()) 50 | .status() 51 | .unwrap(); 52 | 53 | if exit_code.success() { 54 | process::Command::new("volt") 55 | .arg("install") 56 | .spawn() 57 | .unwrap(); 58 | } else { 59 | } 60 | 61 | Ok(()) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/commands/create.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Volt Contributors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | //! Remove a package from your direct dependencies. 18 | 19 | use crate::cli::{VoltCommand, VoltConfig}; 20 | 21 | use async_trait::async_trait; 22 | use miette::Result; 23 | 24 | /// Struct implementation for the `Remove` command. 25 | pub struct Create; 26 | 27 | #[async_trait] 28 | impl VoltCommand for Create { 29 | /// Execute the `volt create` command 30 | /// 31 | /// Removes a package from your direct dependencies. 32 | /// ## Arguments 33 | /// * `app` - Instance of the command (`Arc`) 34 | /// * `template` - Template to create with 35 | /// * `flags` - List of flags passed in through the CLI (`Vec`) 36 | /// ## Examples 37 | /// ``` 38 | /// // Remove a package from your direct dependencies with logging level verbose 39 | /// // .exec() is an async call so you need to await it 40 | /// Create.exec(app, vec![], vec!["--verbose"]).await; 41 | /// ``` 42 | /// ## Returns 43 | /// * `Result<()>` 44 | async fn exec(self, _config: VoltConfig) -> Result<()> { 45 | // let args = app.args.clone(); 46 | // let templates: Vec = Template::options(); 47 | 48 | // let mut template: String = String::new(); 49 | 50 | // let mut app_name: String = String::new(); 51 | // if args.len() == 1 { 52 | // let select = Select { 53 | // message: String::from("Template"), 54 | // paged: true, 55 | // selected: Some(1), 56 | // items: templates.clone(), 57 | // }; 58 | 59 | // let selected = select.run().unwrap_or_else(|err| { 60 | // error!("{}", err.to_string()); 61 | // process::exit(1); 62 | // }); 63 | 64 | // template = Template::from_index(selected).unwrap().to_string(); 65 | // } else { 66 | // let _template = &args[1]; 67 | // if templates.contains(_template) { 68 | // template = _template.to_string(); 69 | // } else { 70 | // error!("Template {} doesn't exist!", _template.bright_blue()); 71 | // process::exit(1); 72 | // } 73 | // } 74 | 75 | // if args.len() > 1 { 76 | // app_name = Input::new() 77 | // .with_prompt("App name") 78 | // .with_initial_text("") 79 | // .default("my-app".into()) 80 | // .interact_text() 81 | // .unwrap(); 82 | 83 | // if app_name.is_empty() { 84 | // error!("Invalid app name!"); 85 | // process::exit(1); 86 | // } 87 | // } else { 88 | // let _app_name = &args[1]; 89 | // app_name = _app_name.to_string(); 90 | // } 91 | 92 | // let template_name = template.split('-').collect::>()[0]; 93 | // let version = "create-".to_owned() + template_name; 94 | // let package_json = get_package(&version).await.unwrap().unwrap_or_else(|| { 95 | // println!( 96 | // "{} Could not find template for {}", 97 | // "error".red().bold(), 98 | // template_name 99 | // ); 100 | // exit(1) 101 | // }); 102 | // // For dev checking 103 | // let v = package_json 104 | // .versions 105 | // .get(package_json.dist_tags.get("latest").unwrap()) 106 | // .unwrap_or_else(|| { 107 | // println!( 108 | // "{} Could not find template version for {}", 109 | // "error".red().bold(), 110 | // template_name 111 | // ); 112 | // exit(1) 113 | // }); 114 | 115 | // println!("HANDLE THIS"); 116 | // let tarball_file = utils::download_tarball_create(&app, &package_json, &version) 117 | // .await 118 | // .unwrap(); 119 | // let gz_decoder = GzDecoder::new(File::open(tarball_file).unwrap()); 120 | 121 | // let mut archive = Archive::new(gz_decoder); 122 | // let mut dir = std::env::current_dir().unwrap(); 123 | 124 | // archive.unpack(&dir.join(app_name)).unwrap(); 125 | 126 | Ok(()) 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/commands/deploy.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Volt Contributors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | //! Remove a package from your direct dependencies. 18 | 19 | use crate::cli::{VoltCommand, VoltConfig}; 20 | 21 | use async_trait::async_trait; 22 | use miette::Result; 23 | 24 | /// Struct implementation for the `Deploy` command. 25 | pub struct Deploy; 26 | 27 | #[async_trait] 28 | impl VoltCommand for Deploy { 29 | /// Execute the `volt deploy` command 30 | /// 31 | /// Removes a package from your direct dependencies. 32 | /// ## Arguments 33 | /// * `commit_msg` - Name of the commit message. 34 | /// ## Examples 35 | /// ``` 36 | /// // .exec() is an async call so you need to await it 37 | /// Create.exec(app, vec![], vec!["--verbose"]).await; 38 | /// ``` 39 | /// ## Returns 40 | /// * `Result<()>` 41 | async fn exec(self, _config: VoltConfig) -> Result<()> { 42 | // let args: Vec = app.args.clone(); 43 | // if args.is_empty() { 44 | // error!("expected commit name"); 45 | // process::exit(1); 46 | // } else { 47 | // let commit_msg = &args[0]; 48 | // env::set_current_dir(env::current_dir().unwrap()).unwrap(); 49 | // // println!("current dir: {:?}", env::current_dir()?); 50 | // process::Command::new("git") 51 | // .args(&["add", "."]) 52 | // .output() 53 | // .expect("Failed to add"); 54 | // process::Command::new("git") 55 | // .args(&["commit", "-m", commit_msg.as_str()]) 56 | // .output() 57 | // .expect("Failed to commit"); 58 | // process::Command::new("git") 59 | // .args(&["push"]) 60 | // .output() 61 | // .expect("Failed to push"); 62 | // } 63 | Ok(()) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/commands/discord.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Volt Contributors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | use crate::cli::{VoltCommand, VoltConfig}; 18 | 19 | use async_trait::async_trait; 20 | use clap::Parser; 21 | use colored::Colorize; 22 | use miette::Result; 23 | 24 | /// Join the official volt discord server 25 | #[derive(Debug, Parser)] 26 | pub struct Discord; 27 | 28 | #[async_trait] 29 | impl VoltCommand for Discord { 30 | /// Execute the `volt discord` command 31 | /// 32 | /// Join the official volt discord server. 33 | /// ## Arguments 34 | /// * `app` - Instance of the command (`Arc`) 35 | /// ## Examples 36 | /// ``` 37 | /// // Opens a link to the official volt discord server. 38 | /// // .exec() is an async call so you need to await it 39 | /// Discord.exec(app).await; 40 | /// ``` 41 | /// ## Returns 42 | /// * `Result<()>` 43 | async fn exec(self, _config: VoltConfig) -> Result<()> { 44 | match webbrowser::open("https://discord.gg/fY7BMcrcYr") { 45 | Ok(_) => { 46 | println!("Successfully opened an invite to the official {} server on your default browser.", "discord".truecolor(88, 101, 242).bold()); 47 | } 48 | Err(_) => { 49 | println!("Failed to open an invite to the official {} server on your default browser.\nFeel free to join using this link instead: {}", "discord".truecolor(88, 101, 242).bold(), "https://discord.gg/fY7BMcrcYr".bright_purple().underline()); 50 | } 51 | }; 52 | 53 | Ok(()) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/commands/fix.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Volt Contributors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | //! Fix common errors in the package.json file 18 | 19 | use crate::cli::{VoltCommand, VoltConfig}; 20 | 21 | use async_trait::async_trait; 22 | use colored::Colorize; 23 | use miette::Result; 24 | 25 | /// Struct implementation for the `Deploy` command. 26 | pub struct Fix; 27 | 28 | #[async_trait] 29 | impl VoltCommand for Fix { 30 | /// Execute the `volt fix` command 31 | /// 32 | /// Removes a package from your direct dependencies. 33 | /// ## Examples 34 | /// ``` 35 | /// // .exec() is an async call so you need to await it 36 | /// Create.exec(app, vec![], vec!["--verbose"]).await; 37 | /// ``` 38 | /// ## Returns 39 | /// * `Result<()>` 40 | async fn exec(self, _config: VoltConfig) -> Result<()> { 41 | println!("{}", "Scanning for errors".bright_cyan()); 42 | 43 | Ok(()) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/commands/init.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021, 2022 Volt Contributors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | use crate::{ 18 | cli::{VoltCommand, VoltConfig}, 19 | core::{ 20 | classes::init_data::{InitData, License}, 21 | prompt::prompts::{Confirm, Input, Select}, 22 | utils, 23 | utils::errors::VoltError, 24 | utils::extensions::PathExtensions, 25 | }, 26 | }; 27 | 28 | use async_trait::async_trait; 29 | use clap::Parser; 30 | use colored::Colorize; 31 | use miette::{IntoDiagnostic, Result}; 32 | use regex::Regex; 33 | use std::{fs::File, io::Write, time::Instant}; 34 | 35 | const PACKAGE_JSON: &str = "package.json"; 36 | 37 | /// Interactively create or update a package.json file for a project 38 | #[derive(Debug, Parser)] 39 | pub struct Init { 40 | /// Use default options 41 | #[clap(short, long)] 42 | yes: bool, 43 | } 44 | 45 | #[async_trait] 46 | impl VoltCommand for Init { 47 | /// Execute the `volt init` command 48 | /// 49 | /// Interactively create or update a package.json file for a project. 50 | /// ## Arguments 51 | /// * `app` - Instance of the command (`Arc`) 52 | /// * `packages` - List of packages to add (`Vec`) 53 | /// * `flags` - List of flags passed in through the CLI (`Vec`) 54 | /// ## Examples 55 | /// ``` 56 | /// // Initialize a new package.json file without any prompts 57 | /// // .exec() is an async call so you need to await it 58 | /// Init.exec(app, vec![], vec!["--yes"]).await; 59 | /// ``` 60 | /// ## Returns 61 | /// * `Result<()>` 62 | async fn exec(self, config: VoltConfig) -> Result<()> { 63 | let _start = Instant::now(); 64 | 65 | // get name of cwd 66 | let cwd_name = config 67 | .cwd()? 68 | .file_name_as_string() 69 | .ok_or(VoltError::GetCurrentDirNameError)?; 70 | 71 | let data = if self.yes { 72 | // Set name to current directory name 73 | automatic_initialization(cwd_name, &config)? 74 | } else { 75 | manual_initialization(cwd_name, &config)? 76 | }; 77 | 78 | let mut file = File::create(PACKAGE_JSON).map_err(|e| VoltError::WriteFileError { 79 | source: e, 80 | name: String::from(PACKAGE_JSON), 81 | })?; 82 | 83 | file.write(data.into_string().as_bytes()) 84 | .map_err(|e| VoltError::WriteFileError { 85 | source: e, 86 | name: String::from(PACKAGE_JSON), 87 | })?; 88 | 89 | println!("{}", "Successfully Initialized package.json".bright_green()); 90 | 91 | Ok(()) 92 | } 93 | } 94 | 95 | fn automatic_initialization(name: String, config: &VoltConfig) -> Result { 96 | let version = "0.1.0".to_string(); 97 | 98 | let description = None; 99 | 100 | let main = "index.js".to_string(); 101 | 102 | let author = { 103 | let git_user_name = utils::get_git_config(config, "user.name")?; 104 | let git_email = utils::get_git_config(config, "user.email")?; 105 | 106 | if let (Some(git_user_name), Some(git_email)) = (git_user_name, git_email) { 107 | Some(format!("{} <{}>", git_user_name, git_email)) 108 | } else { 109 | None 110 | } 111 | }; 112 | 113 | let license = License::default(); 114 | 115 | Ok(InitData { 116 | name, 117 | version, 118 | description, 119 | main, 120 | author, 121 | license, 122 | private: None, 123 | }) 124 | } 125 | 126 | fn manual_initialization(default_name: String, config: &VoltConfig) -> Result { 127 | // Get "name" 128 | let input = Input { 129 | message: "name".into(), 130 | default: Some(default_name.into()), 131 | allow_empty: false, 132 | }; 133 | 134 | let re_name = Regex::new("^(?:@[a-z0-9-*~][a-z0-9-*._~]*/)?[a-z0-9-~][a-z0-9-._~]*$") 135 | .expect("Valid regex"); 136 | let mut name; 137 | loop { 138 | name = input.run().into_diagnostic()?; 139 | 140 | if re_name.is_match(&name) { 141 | break; 142 | } 143 | 144 | println!("{}", "Name cannot contain special characters".red()); 145 | } 146 | 147 | // Get "version" 148 | let input = Input { 149 | message: "version".into(), 150 | default: Some("1.0.0".into()), 151 | allow_empty: false, 152 | }; 153 | 154 | let version = input.run().into_diagnostic()?; 155 | 156 | // Get "description" 157 | let input = Input { 158 | message: "description".into(), 159 | default: None, 160 | allow_empty: true, 161 | }; 162 | 163 | let description = input.run().into_diagnostic()?; 164 | 165 | // Get "main" 166 | let input = Input { 167 | message: "main".into(), 168 | default: Some("index.js".into()), 169 | allow_empty: false, 170 | }; 171 | 172 | let main = input.run().into_diagnostic()?; 173 | 174 | // Get "author" 175 | let git_user_name = utils::get_git_config(config, "user.name")?; 176 | 177 | let git_email = utils::get_git_config(config, "user.email")?; 178 | 179 | let input = Input { 180 | message: "author".into(), 181 | default: git_user_name 182 | .zip(git_email) 183 | .map(|(git_user_name, git_email)| format!("{} <{}>", git_user_name, git_email).into()), 184 | allow_empty: true, 185 | }; 186 | 187 | let author = input.run().into_diagnostic()?; 188 | 189 | // Get "license" 190 | let select = Select { 191 | message: "License".into(), 192 | paged: true, 193 | selected: Some(1), 194 | items: License::OPTIONS.iter().map(|&l| l.into()).collect(), 195 | }; 196 | 197 | select.run().into_diagnostic()?; 198 | 199 | let license = License::from_index(select.selected.unwrap()).unwrap(); 200 | 201 | let input = Confirm { 202 | message: "private".into(), 203 | default: false, 204 | }; 205 | 206 | let private = input.run().into_diagnostic()?; 207 | 208 | Ok(InitData { 209 | name, 210 | version, 211 | description: Some(description), 212 | main, 213 | author: Some(author), 214 | license, 215 | private: Some(private), 216 | }) 217 | } 218 | -------------------------------------------------------------------------------- /src/commands/install.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Volt Contributors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | //! Installs dependencies for a project. 18 | 19 | use crate::cli::{VoltCommand, VoltConfig}; 20 | 21 | use async_trait::async_trait; 22 | use miette::Result; 23 | 24 | /// Struct implementation for the `Install` command. 25 | pub struct Install; 26 | 27 | #[async_trait] 28 | impl VoltCommand for Install { 29 | /// Execute the `volt install` command 30 | /// 31 | /// Install dependencies for a project. 32 | /// ## Arguments 33 | /// * `app` - Instance of the command (`Arc`) 34 | /// * `packages` - List of packages to add (`Vec`) 35 | /// * `flags` - List of flags passed in through the CLI (`Vec`) 36 | /// ## Examples 37 | /// ``` 38 | /// // Install dependencies for a project with logging level verbose 39 | /// // .exec() is an async call so you need to await it 40 | /// Install.exec(app, vec![], vec!["--verbose"]).await; 41 | /// ``` 42 | /// ## Returns 43 | /// * `Result<()>` 44 | async fn exec(self, _config: VoltConfig) -> Result<()> { 45 | // let package_file 46 | Ok(()) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/commands/logout.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Volt Contributors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | //! Logout for a package. 18 | 19 | use async_trait::async_trait; 20 | use miette::Result; 21 | 22 | use crate::cli::{VoltCommand, VoltConfig}; 23 | 24 | pub struct Logout {} 25 | 26 | #[async_trait] 27 | impl VoltCommand for Logout { 28 | /// Execute the `volt search` command 29 | /// 30 | /// Logout for a package 31 | /// ## Arguments 32 | /// * `error` - Instance of the command (`Arc`) 33 | /// ## Examples 34 | /// ``` 35 | /// // Logout for a package 36 | /// // .exec() is an async call so you need to await it 37 | /// Logout.exec(app).await; 38 | /// ``` 39 | /// ## Returns 40 | /// * `Result<()>` 41 | async fn exec(self, _config: VoltConfig) -> Result<()> { 42 | Ok(()) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/commands/migrate.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Volt Contributors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | //! Migrates a package from your direct dependencies. 18 | 19 | use crate::cli::{VoltCommand, VoltConfig}; 20 | 21 | use async_trait::async_trait; 22 | use miette::Result; 23 | 24 | /// Struct implementation for the `Migrate` command. 25 | pub struct Migrate; 26 | 27 | #[async_trait] 28 | impl VoltCommand for Migrate { 29 | /// Execute the `volt migrate` command 30 | /// 31 | /// Migrates a project from yarn or npm to volt including lockfiles. 32 | /// ## Arguments 33 | /// * `app` - Instance of the command (`Arc`) 34 | /// * `package_manager_name` - The (`String`) 35 | /// * `flags` - List of flags passed in through the CLI (`Vec`) 36 | /// ## Examples 37 | /// ``` 38 | /// // Migrate a package from your direct dependencies with logging level verbose 39 | /// // .exec() is an async call so you need to await it 40 | /// Migrate.exec(app, vec![], vec!["--verbose"]).await; 41 | /// ``` 42 | /// ## Returns 43 | /// * `Result<()>` 44 | async fn exec(self, _config: VoltConfig) -> Result<()> { 45 | // let packagemanagers: Vec = PackageManager::options(); 46 | // let mut packagemanager: String = String::new(); 47 | // if app.args.len() == 1 { 48 | // packagemanager = app.args[0].to_string(); 49 | // } else if app.args.len() == 1 { 50 | // let select = Select { 51 | // message: String::from("Package Manager"), 52 | // paged: true, 53 | // selected: Some(1), 54 | // items: packagemanagers.clone(), 55 | // }; 56 | // let selected = select.run().unwrap_or_else(|err| { 57 | // error!("{}", err.to_string()); 58 | // process::exit(1); 59 | // }); 60 | 61 | // packagemanager = PackageManager::from_index(selected).unwrap().to_string(); 62 | // } else { 63 | // error!("{}", "volt migrate only takes 1 argument"); 64 | // } 65 | 66 | // if packagemanager.eq_ignore_ascii_case("volt") { 67 | // std::fs::remove_dir_all("node_modules").unwrap(); 68 | 69 | // let files = fs::read_dir(env::current_dir().unwrap()).unwrap(); 70 | // files 71 | // .filter_map(Result::ok) 72 | // .filter(|d| { 73 | // if let Some(e) = d.path().extension() { 74 | // String::from(e.to_str().unwrap()).contains("lock") 75 | // } else { 76 | // false 77 | // } 78 | // }) 79 | // .for_each(|f| std::fs::remove_file(f.file_name()).unwrap()); 80 | // println!("{}", "$ volt install".truecolor(147, 148, 148)); 81 | // install::command::Install::exec(app).await?; // NOTE WILL ONLY WORK IF THE VAR DEPENDENCIES is populated 82 | // } else if packagemanager.eq_ignore_ascii_case("yarn") { 83 | // std::fs::remove_dir_all("node_modules").unwrap(); 84 | 85 | // let files = fs::read_dir(env::current_dir().unwrap()).unwrap(); 86 | // files 87 | // .filter_map(Result::ok) 88 | // .filter(|d| { 89 | // if let Some(e) = d.path().extension() { 90 | // e == "lock" 91 | // } else { 92 | // false 93 | // } 94 | // }) 95 | // .for_each(|f| std::fs::remove_file(f.file_name()).unwrap()); 96 | 97 | // println!("{}", "$ yarn".truecolor(147, 148, 148)); 98 | // std::process::Command::new("yarn") 99 | // .spawn() 100 | // .expect("failed to execute") 101 | // .wait() 102 | // .unwrap(); 103 | // } else if packagemanager.eq_ignore_ascii_case("pnpm") { 104 | // std::fs::remove_dir_all("node_modules").unwrap(); 105 | 106 | // let files = fs::read_dir(env::current_dir().unwrap()).unwrap(); 107 | // files 108 | // .filter_map(Result::ok) 109 | // .filter(|d| { 110 | // if let Some(e) = d.path().file_name() { 111 | // String::from(e.to_str().unwrap()).contains("lock") 112 | // } else { 113 | // false 114 | // } 115 | // }) 116 | // .for_each(|f| std::fs::remove_file(f.file_name()).unwrap()); 117 | 118 | // println!("{}", "$ pnpm install".truecolor(147, 148, 148)); 119 | // std::process::Command::new("pnpm") 120 | // .arg("install") 121 | // .spawn() 122 | // .expect("failed to execute") 123 | // .wait() 124 | // .unwrap(); 125 | // } else if packagemanager.eq_ignore_ascii_case("npm") { 126 | // std::fs::remove_dir_all("node_modules").unwrap(); 127 | 128 | // let files = fs::read_dir(env::current_dir().unwrap()).unwrap(); 129 | // files 130 | // .filter_map(Result::ok) 131 | // .filter(|d| { 132 | // if let Some(e) = d.path().file_name() { 133 | // String::from(e.to_str().unwrap()).contains("lock") 134 | // } else { 135 | // false 136 | // } 137 | // }) 138 | // .for_each(|f| std::fs::remove_file(f.file_name()).unwrap()); 139 | 140 | // println!("{}", "$ npm install".truecolor(147, 148, 148)); 141 | // std::process::Command::new("npm") 142 | // .arg("install") 143 | // .spawn() 144 | // .expect("failed to execute") 145 | // .wait() 146 | // .unwrap(); 147 | // } else { 148 | // println!("Volt accepts only volt, yarn, pnpm or npm for volt migrate's args it does not support {}" ,app.args[0].to_string().red()); 149 | // } 150 | Ok(()) 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/commands/mod.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Volt Contributors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | pub mod add; 17 | pub mod audit; 18 | pub mod check; 19 | pub mod clean; 20 | pub mod clone; 21 | pub mod create; 22 | pub mod deploy; 23 | pub mod discord; 24 | pub mod fix; 25 | pub mod info; 26 | pub mod init; 27 | pub mod install; 28 | pub mod list; 29 | pub mod login; 30 | pub mod logout; 31 | pub mod migrate; 32 | pub mod node; 33 | pub mod outdated; 34 | pub mod owner; 35 | pub mod publish; 36 | pub mod remove; 37 | pub mod run; 38 | pub mod search; 39 | pub mod set; 40 | pub mod stat; 41 | pub mod tag; 42 | pub mod team; 43 | pub mod update; 44 | pub mod watch; 45 | -------------------------------------------------------------------------------- /src/commands/node.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Volt Contributors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | //! Manage local node versions 18 | 19 | mod node_install; 20 | mod node_list; 21 | mod node_remove; 22 | mod node_use; 23 | 24 | pub use node_install::*; 25 | pub use node_list::*; 26 | pub use node_remove::*; 27 | pub use node_use::*; 28 | 29 | use async_trait::async_trait; 30 | use clap::{Parser, Subcommand}; 31 | use miette::Result; 32 | use node_semver::Version; 33 | use serde::{Deserialize, Deserializer}; 34 | 35 | use crate::cli::{VoltCommand, VoltConfig}; 36 | 37 | #[derive(Deserialize)] 38 | #[serde(untagged)] 39 | enum Lts { 40 | No(bool), 41 | Yes(String), 42 | } 43 | 44 | impl From for Option { 45 | fn from(val: Lts) -> Self { 46 | match val { 47 | Lts::No(_) => None, 48 | Lts::Yes(x) => Some(x), 49 | } 50 | } 51 | } 52 | 53 | pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> 54 | where 55 | D: Deserializer<'de>, 56 | { 57 | Ok(Lts::deserialize(deserializer)?.into()) 58 | } 59 | 60 | #[derive(Deserialize, Debug)] 61 | pub struct NodeVersion { 62 | pub version: Version, 63 | #[serde(deserialize_with = "deserialize")] 64 | pub lts: Option, 65 | pub files: Vec, 66 | } 67 | 68 | /// Manage node versions 69 | #[derive(Debug, Parser)] 70 | pub struct Node { 71 | #[clap(subcommand)] 72 | cmd: NodeCommand, 73 | } 74 | 75 | #[async_trait] 76 | impl VoltCommand for Node { 77 | async fn exec(self, config: VoltConfig) -> Result<()> { 78 | match self.cmd { 79 | NodeCommand::Use(x) => x.exec(config).await, 80 | NodeCommand::Install(x) => x.exec(config).await, 81 | NodeCommand::Remove(x) => x.exec(config).await, 82 | NodeCommand::List(x) => x.exec(config).await, 83 | } 84 | } 85 | } 86 | 87 | #[derive(Debug, Subcommand)] 88 | pub enum NodeCommand { 89 | Use(NodeUse), 90 | Install(NodeInstall), 91 | Remove(NodeRemove), 92 | List(NodeList), 93 | } 94 | -------------------------------------------------------------------------------- /src/commands/node/node_list.rs: -------------------------------------------------------------------------------- 1 | use async_trait::async_trait; 2 | use clap::Parser; 3 | use miette::Result; 4 | 5 | use crate::cli::{VoltCommand, VoltConfig}; 6 | 7 | /// List available NodeJS versions 8 | #[derive(Debug, Parser)] 9 | pub struct NodeList {} 10 | 11 | #[async_trait] 12 | impl VoltCommand for NodeList { 13 | // On windows, versions install to C:\Users\[name]\AppData\Roaming\volt\node\[version] 14 | async fn exec(self, _config: VoltConfig) -> Result<()> { 15 | let node_path = { 16 | let datadir = dirs::data_dir().unwrap().join("volt").join("node"); 17 | if !datadir.exists() { 18 | eprintln!("No NodeJS versions installed!"); 19 | std::process::exit(1); 20 | }; 21 | datadir 22 | }; 23 | 24 | let mut versions = std::fs::read_dir(&node_path) 25 | .unwrap() 26 | .map(|d| d.unwrap().file_name().to_str().unwrap().to_owned()) 27 | .filter(|f| f != "current") 28 | .collect::>(); 29 | 30 | if versions.is_empty() { 31 | eprintln!("No NodeJS versions installed!"); 32 | std::process::exit(0); 33 | } 34 | 35 | // Sort in descending order 36 | versions.sort_by(|a, b| b.cmp(a)); 37 | 38 | for version in versions { 39 | println!("{version}"); 40 | } 41 | 42 | Ok(()) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/commands/node/node_remove.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use crate::cli::{VoltCommand, VoltConfig}; 4 | use async_trait::async_trait; 5 | use clap::{CommandFactory, ErrorKind, Parser}; 6 | use miette::{IntoDiagnostic, Result}; 7 | 8 | /// Uninstall a specified version of node 9 | #[derive(Debug, Parser)] 10 | pub struct NodeRemove { 11 | /// Versions to remove 12 | versions: Vec, 13 | } 14 | 15 | fn get_node_dir() -> PathBuf { 16 | dirs::data_dir().unwrap().join("volt").join("node") 17 | } 18 | 19 | // #[cfg(unix)] 20 | #[async_trait] 21 | impl VoltCommand for NodeRemove { 22 | async fn exec(self, _config: VoltConfig) -> Result<()> { 23 | if self.versions.is_empty() { 24 | NodeRemove::command() 25 | .error( 26 | ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand, 27 | "Must have at least one version", 28 | ) 29 | .exit(); 30 | } 31 | 32 | let node_dir = get_node_dir(); 33 | 34 | let current_dir = if node_dir.join("current").exists() { 35 | let curr = std::fs::canonicalize(node_dir.join("current")) 36 | .unwrap() 37 | .parent() 38 | .unwrap() 39 | .to_owned(); 40 | Some(curr) 41 | } else { 42 | None 43 | }; 44 | 45 | let current_version = current_dir 46 | .as_ref() 47 | .map(|dir| dir.file_name().unwrap().to_str().unwrap()); 48 | 49 | for v in self.versions { 50 | let version_dir = node_dir.join(&v); 51 | 52 | if !version_dir.exists() { 53 | eprintln!("Version {v} not installed"); 54 | continue; 55 | } 56 | 57 | if matches!(current_version, Some(ver) if ver == v) { 58 | let current_dir = current_dir.as_ref().unwrap(); 59 | let current_bin = std::fs::read_dir(current_dir.join("bin")).unwrap(); 60 | 61 | // Remove all the installed symlinks 62 | for binary in current_bin { 63 | let b = binary.unwrap(); 64 | let result = 65 | std::fs::remove_file(dirs::executable_dir().unwrap().join(b.file_name())); 66 | 67 | match result { 68 | Ok(_) => {} 69 | Err(e) => return Err(e).into_diagnostic(), 70 | } 71 | } 72 | 73 | let result = std::fs::remove_file(node_dir.join("current")); 74 | 75 | match result { 76 | Ok(_) => {} 77 | Err(e) => return Err(e).into_diagnostic(), 78 | } 79 | } 80 | 81 | // Always remove the version directory, regardless of current version status 82 | let result = std::fs::remove_dir_all(node_dir.join(v)); 83 | 84 | match result { 85 | Ok(_) => {} 86 | Err(e) => return Err(e).into_diagnostic(), 87 | } 88 | } 89 | Ok(()) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/commands/node/node_use.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use async_trait::async_trait; 4 | 5 | use clap::Parser; 6 | use miette::Result; 7 | 8 | use crate::cli::{VoltCommand, VoltConfig}; 9 | 10 | /// Switch current node version 11 | #[derive(Debug, Parser)] 12 | pub struct NodeUse { 13 | /// Version to use 14 | version: String, 15 | } 16 | 17 | fn get_node_dir() -> PathBuf { 18 | dirs::data_dir().unwrap().join("volt").join("node") 19 | } 20 | 21 | #[async_trait] 22 | impl VoltCommand for NodeUse { 23 | async fn exec(self, _config: VoltConfig) -> Result<()> { 24 | #[cfg(target_family = "windows")] 25 | { 26 | use_windows(self.version).await; 27 | } 28 | 29 | #[cfg(target_family = "unix")] 30 | { 31 | let node_path = get_node_dir().join(&self.version); 32 | 33 | if node_path.exists() { 34 | let link_dir = dirs::home_dir().unwrap().join(".local").join("bin"); 35 | 36 | let to_install = node_path.join("bin"); 37 | let current = node_path.parent().unwrap().join("current"); 38 | 39 | // TODO: Handle file deletion errors 40 | if current.exists() { 41 | // Remove all the currently installed links 42 | for f in std::fs::read_dir(¤t).unwrap() { 43 | let original = f.unwrap().file_name(); 44 | let installed = link_dir.join(&original); 45 | if installed.exists() { 46 | std::fs::remove_file(installed).unwrap(); 47 | } 48 | } 49 | 50 | // Remove the old link 51 | std::fs::remove_file(¤t).unwrap(); 52 | 53 | // Make a new one to the currently installed version 54 | std::os::unix::fs::symlink(&to_install, current).unwrap(); 55 | } else { 56 | println!("Installing first version"); 57 | std::os::unix::fs::symlink(&to_install, current).unwrap(); 58 | } 59 | 60 | // Install all the links for the new version 61 | for f in std::fs::read_dir(&to_install).unwrap() { 62 | let original = f.unwrap().path(); 63 | let fname = original.file_name().unwrap(); 64 | let link = link_dir.join(fname); 65 | 66 | // INFO: DOC: Need to run `rehash` in zsh for the changes to take effect 67 | println!("Linking to {:?} from {:?}", link, original); 68 | 69 | // TODO: Do something with this error 70 | let _ = std::fs::remove_file(&link); 71 | 72 | // maybe ship `vnm` as a shell function to run `volt node use ... && rehash` on 73 | // zsh? 74 | let _symlink = std::os::unix::fs::symlink(original, link).unwrap(); 75 | } 76 | } else { 77 | println!("That version of node is not installed!\nTry \"volt node install {}\" to install that version.", self.version) 78 | } 79 | } 80 | Ok(()) 81 | } 82 | } 83 | 84 | #[cfg(windows)] 85 | async fn use_windows(version: String) { 86 | use std::{env, path::Path, process::Command}; 87 | use tokio::fs; 88 | 89 | let node_path = get_node_dir().join(&version).join("node.exe"); 90 | let path = Path::new(&node_path); 91 | 92 | if path.exists() { 93 | println!("Using version {}", version); 94 | 95 | let link_dir = dirs::data_dir() 96 | .unwrap() 97 | .join("volt") 98 | .join("bin") 99 | .into_os_string() 100 | .into_string() 101 | .unwrap(); 102 | 103 | let link_file = dirs::data_dir() 104 | .unwrap() 105 | .join("volt") 106 | .join("bin") 107 | .join("node.exe"); 108 | let link_file = Path::new(&link_file); 109 | 110 | if link_file.exists() { 111 | fs::remove_file(link_file).await.unwrap(); 112 | } 113 | 114 | let newfile = std::fs::copy(node_path, link_file); 115 | 116 | match newfile { 117 | Ok(_) => {} 118 | Err(_) => { 119 | println!("Sorry, something went wrong."); 120 | return; 121 | } 122 | } 123 | 124 | let vfpath = dirs::data_dir().unwrap().join("volt").join("current"); 125 | let vfpath = Path::new(&vfpath); 126 | let vfile = std::fs::write(vfpath, version); 127 | 128 | let path = env::var("PATH").unwrap(); 129 | if !path.contains(&link_dir) { 130 | let command = format!("[Environment]::SetEnvironmentVariable('Path', [Environment]::GetEnvironmentVariable('Path', 'User') + '{}', 'User')", &link_dir); 131 | 132 | Command::new("Powershell") 133 | .args(&["-Command", &command]) 134 | .output() 135 | .unwrap(); 136 | 137 | println!("PATH environment variable updated.\nYou will need to restart your terminal for changes to apply."); 138 | } 139 | } else { 140 | println!("That version of node is not installed!\nTry \"volt node install {}\" to install that version.", version); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/commands/owner.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Volt Contributors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | //! Check for outdated packages. 18 | 19 | use async_trait::async_trait; 20 | use miette::Result; 21 | 22 | use crate::cli::{VoltCommand, VoltConfig}; 23 | 24 | pub struct Owner {} 25 | 26 | #[async_trait] 27 | impl VoltCommand for Owner { 28 | /// Execute the `volt outdated` command 29 | /// 30 | /// Check for outdated packages 31 | /// ## Arguments 32 | /// * `error` - Instance of the command (`Arc`) 33 | /// ## Examples 34 | /// ``` 35 | /// // Check for outdated packages 36 | /// // .exec() is an async call so you need to await it 37 | /// Owner.exec(app).await; 38 | /// ``` 39 | /// ## Returns 40 | /// * `Result<()>` 41 | async fn exec(self, _config: VoltConfig) -> Result<()> { 42 | Ok(()) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/commands/ping.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Volt Contributors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | //! Ping the volt & npm registry. 18 | 19 | use async_trait::async_trait; 20 | use miette::Result; 21 | 22 | use std::sync::Arc; 23 | 24 | use crate::cli::{VoltCommand, VoltConfig}; 25 | 26 | pub struct Owner {} 27 | 28 | #[async_trait] 29 | impl VoltCommand for Owner { 30 | /// Execute the `volt ping` command 31 | /// 32 | /// Ping the volt & npm registry. 33 | /// ## Arguments 34 | /// * `error` - Instance of the command (`Arc`) 35 | /// ## Examples 36 | /// ``` 37 | /// // Ping the volt & npm registry.s 38 | /// // .exec() is an async call so you need to await it 39 | /// Owner.exec(app).await; 40 | /// ``` 41 | /// ## Returns 42 | /// * `Result<()>` 43 | async fn exec(self, config: VoltConfig) -> Result<()> { 44 | 45 | Ok(()) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/commands/publish.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Volt Contributors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | //! Check for outdated packages. 18 | 19 | use async_trait::async_trait; 20 | use miette::Result; 21 | 22 | use crate::cli::{VoltCommand, VoltConfig}; 23 | 24 | pub struct Publish {} 25 | 26 | #[async_trait] 27 | impl VoltCommand for Publish { 28 | /// Execute the `volt outdated` command 29 | /// 30 | /// Check for outdated packages 31 | /// ## Arguments 32 | /// * `error` - Instance of the command (`Arc`) 33 | /// ## Examples 34 | /// ``` 35 | /// // Check for outdated packages 36 | /// // .exec() is an async call so you need to await it 37 | /// Publish.exec(app).await; 38 | /// ``` 39 | /// ## Returns 40 | /// * `Result<()>` 41 | async fn exec(self, _config: VoltConfig) -> Result<()> { 42 | Ok(()) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/commands/remove.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Volt Contributors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | //! Remove a package from your direct dependencies. 18 | 19 | use crate::cli::{VoltCommand, VoltConfig}; 20 | 21 | use async_trait::async_trait; 22 | use miette::Result; 23 | 24 | /// Struct implementation for the `Remove` command. 25 | pub struct Remove; 26 | 27 | #[async_trait] 28 | impl VoltCommand for Remove { 29 | /// Execute the `volt remove` command 30 | /// 31 | /// Removes a package from your direct dependencies. 32 | /// ## Arguments 33 | /// * `app` - Instance of the command (`Arc`) 34 | /// * `packages` - List of packages to add (`Vec`) 35 | /// * `flags` - List of flags passed in through the CLI (`Vec`) 36 | /// ## Examples 37 | /// ``` 38 | /// // Remove a package from your direct dependencies with logging level verbose 39 | /// // .exec() is an async call so you need to await it 40 | /// Remove.exec(app, vec![], vec!["--verbose"]).await; 41 | /// ``` 42 | /// ## Returns 43 | /// * `Result<()>` 44 | async fn exec(self, _config: VoltConfig) -> Result<()> { 45 | // if app.args.len() == 1 { 46 | // println!("{}", Self::help()); 47 | // process::exit(1); 48 | // } 49 | 50 | // let mut packages = vec![]; 51 | // for arg in &app.args { 52 | // if arg != "remove" { 53 | // packages.push(arg.clone()); 54 | // } 55 | // } 56 | 57 | // let package_file = Arc::new(Mutex::new(PackageJson::from("package.json"))); 58 | 59 | // // let mut handles = vec![]; 60 | 61 | // println!("{}", "Removing dependencies".bright_purple()); 62 | 63 | // for package in packages { 64 | // let package_file = package_file.clone(); 65 | // let app_new = app.clone(); 66 | 67 | // // handles.push(tokio::spawn(async move { 68 | // let mut package_json_file = package_file.lock().await; 69 | 70 | // package_json_file.dependencies.remove(&package); 71 | 72 | // package_json_file.save(); 73 | 74 | // let mut lock_file = LockFile::load(app_new.lock_file_path.to_path_buf()) 75 | // .unwrap_or_else(|_| LockFile::new(app_new.lock_file_path.to_path_buf())); 76 | 77 | // // let response = get_volt_response(&package).await?; 78 | 79 | // // let current_version = response.versions.get(&response.version).unwrap(); 80 | 81 | // // for object in current_version.values() { 82 | // // // This is doing nothing? I guess it's still WIP?... 83 | // // // let mut lock_dependencies: HashMap = HashMap::new(); 84 | 85 | // // // if object.dependencies.is_some() { 86 | // // // for dep in object.clone().dependencies.unwrap().iter() { 87 | // // // // TODO: Change this to real version 88 | // // // lock_dependencies.insert(dep.clone(), String::new()); 89 | // // // } 90 | // // // } 91 | 92 | // // lock_file 93 | // // .dependencies 94 | // // .remove(&DependencyID(object.clone().name, object.clone().version)); 95 | 96 | // // let scripts = Path::new("node_modules/scripts") 97 | // // .join(format!("{}.cmd", object.clone().name).as_str()); 98 | 99 | // // if scripts.exists() { 100 | // // remove_file(format!( 101 | // // "node_modules/scripts/{}", 102 | // // scripts.file_name().unwrap().to_str().unwrap() 103 | // // )) 104 | // // .await 105 | // // .unwrap_and_handle_error(); 106 | // // } 107 | // // } 108 | 109 | // // lock_file.save().unwrap(); 110 | 111 | // // let node_modules_dir = std::env::current_dir().unwrap().join("node_modules"); 112 | // // let dep_dir = node_modules_dir.join(&package); 113 | // // if dep_dir.exists() { 114 | // // remove_dir_all(dep_dir).await.unwrap_and_handle_error(); 115 | // // } 116 | // } 117 | 118 | // // if handles.len() > 0 { 119 | // // for handle in handles { 120 | // // handle.await?; 121 | // // } 122 | // // } 123 | 124 | // println!("{}", "Successfully Removed Packages".bright_blue()); 125 | 126 | Ok(()) 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/commands/run.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Volt Contributors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | use crate::cli::{VoltCommand, VoltConfig}; 18 | 19 | use async_trait::async_trait; 20 | use clap::Parser; 21 | use colored::Colorize; 22 | use miette::Result; 23 | use std::path::Path; 24 | use std::process::{Command, Stdio}; 25 | 26 | /// Run a pre-defined package script 27 | #[derive(Debug, Parser)] 28 | pub struct Run { 29 | /// Name of the script to run 30 | script: String, 31 | } 32 | 33 | #[async_trait] 34 | impl VoltCommand for Run { 35 | /// Display a help menu for the `volt run` command. 36 | /// Execute the `volt run` command 37 | /// 38 | /// Interactively create or update a package.json file for a project. 39 | /// ## Arguments 40 | /// * `app` - Instance of the command (`Arc`) 41 | /// * `packages` - List of packages to add (`Vec`) 42 | /// * `flags` - List of flags passed in through the CLI (`Vec`) 43 | /// ## Examples 44 | /// ``` 45 | /// // Run a defined script. 46 | /// // .exec() is an async call so you need to await it 47 | /// Run.exec(app, vec![], vec!["--yes"]).await; 48 | /// ``` 49 | /// ## Returns 50 | /// * `Result<()>` 51 | async fn exec(self, _config: VoltConfig) -> Result<()> { 52 | if cfg!(target_os = "windows") { 53 | Command::new("cmd").args(&["/C", "babel"]).spawn().unwrap(); 54 | } else if cfg!(target_os = "linux") { 55 | println!("{}", format!("$ {}", self.script).truecolor(156, 156, 156)); 56 | 57 | let mut child = 58 | Command::new(Path::new("node_modules/").join(".bin/").join(self.script)) 59 | .stdout(Stdio::inherit()) 60 | .stderr(Stdio::inherit()) 61 | .spawn() 62 | .expect("failed to execute child"); 63 | 64 | child.wait().unwrap(); 65 | } 66 | Ok(()) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/commands/scripts.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Volt Contributors 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. 12 | */ 13 | 14 | //! Handle an unknown command (can be listed in scripts). 15 | 16 | // use crate::core::utils::errors; 17 | // use crate::core::utils::package::PackageJson; 18 | use crate::App; 19 | use crate::Command; 20 | 21 | use async_trait::async_trait; 22 | // use colored::Colorize; 23 | use miette::Result; 24 | 25 | use std::sync::Arc; 26 | 27 | pub struct Script {} 28 | 29 | #[async_trait] 30 | impl Command for Script { 31 | fn help() -> String { 32 | todo!() 33 | } 34 | 35 | /// Execute the `volt {script}` command 36 | /// 37 | /// Execute a script command (any script command specified in package.json) 38 | /// ## Arguments 39 | /// * `app` - Instance of the command (`Arc`) 40 | /// ## Examples 41 | /// ``` 42 | /// // Clone the react repository (https://github.com/facebook/react) 43 | /// // .exec() is an async call so you need to await it 44 | /// Add.exec(app).await; 45 | /// ``` 46 | /// ## Returns 47 | /// * `Result<()>` 48 | async fn exec(app: Arc) -> Result<()> { 49 | 50 | 51 | Ok(()) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/commands/search.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Volt Contributors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | //! Search for a package. 18 | 19 | use crate::cli::{VoltCommand, VoltConfig}; 20 | 21 | use async_trait::async_trait; 22 | use clap::Parser; 23 | use comfy_table::{ 24 | modifiers::UTF8_ROUND_CORNERS, presets::UTF8_FULL, Attribute, Cell, Color, ContentArrangement, 25 | Table, 26 | }; 27 | use isahc::AsyncReadResponseExt; 28 | use miette::Result; 29 | use serde::{Deserialize, Serialize}; 30 | 31 | #[derive(Serialize, Deserialize)] 32 | pub struct Objects { 33 | objects: Vec, 34 | } 35 | 36 | #[derive(Serialize, Deserialize)] 37 | pub struct SearchResults { 38 | package: SearchResult, 39 | } 40 | 41 | #[derive(Serialize, Deserialize)] 42 | pub struct SearchResult { 43 | name: String, 44 | version: String, 45 | description: String, 46 | } 47 | 48 | /// Searches for a package 49 | #[derive(Debug, Parser)] 50 | pub struct Search { 51 | /// Search query 52 | query: String, 53 | } 54 | 55 | #[derive(Debug, Serialize, Deserialize, Default)] 56 | pub struct SearchData { 57 | pub name: String, 58 | pub version: String, 59 | pub description: Option, 60 | } 61 | 62 | #[async_trait] 63 | impl VoltCommand for Search { 64 | /// Execute the `volt search` command 65 | /// 66 | /// Search for a package 67 | /// ## Arguments 68 | /// * `error` - Instance of the command (`Arc`) 69 | /// ## Examples 70 | /// ``` 71 | /// // Search for a package 72 | /// // .exec() is an async call so you need to await it 73 | /// Search.exec(app).await; 74 | /// ``` 75 | /// ## Returns 76 | /// * `Result<()>` 77 | async fn exec(self, _config: VoltConfig) -> Result<()> { 78 | let response = isahc::get_async(format!( 79 | "https://registry.npmjs.org/-/v1/search?text={}&popularity=1.0", 80 | self.query 81 | )) 82 | .await 83 | .unwrap() 84 | .text() 85 | .await 86 | .unwrap(); 87 | 88 | let s: Objects = serde_json::from_str(&response).unwrap(); 89 | 90 | let mut table = Table::new(); 91 | 92 | table 93 | .load_preset(UTF8_FULL) 94 | .apply_modifier(UTF8_ROUND_CORNERS) 95 | .set_content_arrangement(ContentArrangement::DynamicFullWidth); 96 | 97 | table.set_header(vec![ 98 | Cell::new("Name") 99 | .fg(Color::Green) 100 | .add_attribute(Attribute::Bold), 101 | Cell::new("Version") 102 | .fg(Color::Blue) 103 | .add_attribute(Attribute::Bold), 104 | Cell::new("Description") 105 | .fg(Color::Yellow) 106 | .add_attribute(Attribute::Bold), 107 | ]); 108 | 109 | for i in &s.objects { 110 | let mut description: String = i.package.description.clone(); 111 | 112 | if i.package.description.len() > 150 { 113 | description.truncate(147); 114 | description = format!("{}...", description); 115 | } 116 | 117 | table.add_row(vec![ 118 | Cell::new(&i.package.name), 119 | Cell::new(&i.package.version), 120 | Cell::new(description), 121 | ]); 122 | } 123 | 124 | println!("{}", table); 125 | 126 | Ok(()) 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/commands/set.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Volt Contributors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | //! Set a configuration option. 18 | 19 | use async_trait::async_trait; 20 | use miette::Result; 21 | 22 | use crate::cli::{VoltCommand, VoltConfig}; 23 | 24 | pub struct Set {} 25 | 26 | #[async_trait] 27 | impl VoltCommand for Set { 28 | /// Execute the `volt set` command 29 | /// 30 | /// Set a configuration option. 31 | /// ## Arguments 32 | /// * `app` - Instance of the command (`Arc`) 33 | /// ## Examples 34 | /// ``` 35 | /// // Set a configuration option. 36 | /// // .exec() is an async call so you need to await it 37 | /// Set.exec(app).await; 38 | /// ``` 39 | /// ## Returns 40 | /// * `Result<()>` 41 | async fn exec(self, _config: VoltConfig) -> Result<()> { 42 | Ok(()) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/commands/stat.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Volt Contributors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | //! Display stats on a specific package 18 | 19 | use crate::cli::{VoltCommand, VoltConfig}; 20 | 21 | use async_trait::async_trait; 22 | use miette::Result; 23 | 24 | /// Struct implementation for the `stat` command. 25 | pub struct Stat; 26 | 27 | #[async_trait] 28 | impl VoltCommand for Stat { 29 | /// Display a help menu for the `volt stat` command. 30 | /// Execute the `volt stat` command 31 | /// 32 | /// Displays stats on a specific package 33 | /// ## Examples 34 | /// ``` 35 | /// // .exec() is an async call so you need to await it 36 | /// Create.exec(app, vec![], vec!["--verbose"]).await; 37 | /// ``` 38 | /// ## Returns 39 | /// * `Result<()>` 40 | async fn exec(self, _config: VoltConfig) -> Result<()> { 41 | // let args = &app.args; 42 | 43 | // if args.len() <= 1 { 44 | // error!("Missing Package Name!"); 45 | // process::exit(1); 46 | // } 47 | 48 | // let package = &args[1]; 49 | 50 | // println!( 51 | // "{}{}\n", 52 | // "Download stats for ".bright_cyan().bold(), 53 | // package.bright_cyan().bold() 54 | // ); 55 | 56 | // // Get downloads for the past week 57 | // let url = format!( 58 | // "https://api.npmjs.org/downloads/point/last-week/{}", 59 | // package 60 | // ); 61 | 62 | // let response = get(url).await.unwrap_or_else(|e| { 63 | // eprintln!("{}", e.to_string()); 64 | // std::process::exit(1) 65 | // }); 66 | 67 | // let file_contents = response.text().await.unwrap_or_else(|e| { 68 | // eprintln!("{}", e.to_string()); 69 | // std::process::exit(1) 70 | // }); 71 | 72 | // let data: Value = from_str(&file_contents).unwrap(); 73 | 74 | // let downloads = &data["downloads"]; 75 | // println!( 76 | // "{} {}", 77 | // downloads.to_string().bright_green(), 78 | // "downloads in the past week!".bright_green() 79 | // ); 80 | 81 | // // Get downloads for the past month 82 | // let url = format!( 83 | // "https://api.npmjs.org/downloads/point/last-month/{}", 84 | // package 85 | // ); 86 | 87 | // let response = get(url).await.unwrap_or_else(|e| { 88 | // eprintln!("{}", e.to_string()); 89 | // std::process::exit(1) 90 | // }); 91 | 92 | // let file_contents = response.text().await.unwrap_or_else(|e| { 93 | // eprintln!("{}", e.to_string()); 94 | // std::process::exit(1) 95 | // }); 96 | 97 | // let data: Value = serde_json::from_str(&file_contents).unwrap(); 98 | 99 | // let downloads = &data["downloads"]; 100 | // println!( 101 | // "{} {}", 102 | // downloads.to_string().bright_green(), 103 | // "downloads in the past month!".bright_green() 104 | // ); 105 | 106 | // // Get downloads for the past year 107 | // let url = format!( 108 | // "https://api.npmjs.org/downloads/point/last-year/{}", 109 | // package 110 | // ); 111 | 112 | // let response = get(url).await.unwrap_or_else(|e| { 113 | // eprintln!("{}", e.to_string()); 114 | // process::exit(1) 115 | // }); 116 | 117 | // let file_contents = response.text().await.unwrap_or_else(|e| { 118 | // eprintln!("{}", e.to_string()); 119 | // process::exit(1) 120 | // }); 121 | 122 | // let data: Value = serde_json::from_str(&file_contents).unwrap(); 123 | 124 | // let downloads = &data["downloads"]; 125 | // println!( 126 | // "{} {}", 127 | // downloads.to_string().bright_green(), 128 | // "downloads in the past year!".bright_green() 129 | // ); 130 | 131 | Ok(()) 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/commands/tag.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Volt Contributors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | //! Check for outdated packages. 18 | 19 | use crate::cli::{VoltCommand, VoltConfig}; 20 | 21 | use async_trait::async_trait; 22 | use miette::Result; 23 | 24 | pub struct Tag {} 25 | 26 | #[async_trait] 27 | impl VoltCommand for Tag { 28 | /// Execute the `volt outdated` command 29 | /// 30 | /// Check for outdated packages 31 | /// ## Arguments 32 | /// * `error` - Instance of the command (`Arc`) 33 | /// ## Examples 34 | /// ``` 35 | /// // Check for outdated packages 36 | /// // .exec() is an async call so you need to await it 37 | /// Tag.exec(app).await; 38 | /// ``` 39 | /// ## Returns 40 | /// * `Result<()>` 41 | async fn exec(self, _config: VoltConfig) -> Result<()> { 42 | Ok(()) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/commands/team.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Volt Contributors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | //! Check for outdated packages. 18 | 19 | use async_trait::async_trait; 20 | use miette::Result; 21 | 22 | use crate::cli::{VoltCommand, VoltConfig}; 23 | 24 | pub struct Team {} 25 | 26 | #[async_trait] 27 | impl VoltCommand for Team { 28 | /// Execute the `volt outdated` command 29 | /// 30 | /// Check for outdated packages 31 | /// ## Arguments 32 | /// * `error` - Instance of the command (`Arc`) 33 | /// ## Examples 34 | /// ``` 35 | /// // Check for outdated packages 36 | /// // .exec() is an async call so you need to await it 37 | /// Team.exec(app).await; 38 | /// ``` 39 | /// ## Returns 40 | /// * `Result<()>` 41 | async fn exec(self, _config: VoltConfig) -> Result<()> { 42 | Ok(()) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/commands/update.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Volt Contributors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | use crate::cli::{VoltCommand, VoltConfig}; 18 | 19 | use async_trait::async_trait; 20 | use miette::Result; 21 | 22 | /// Struct implementation for the `Update` command. 23 | pub struct Update; 24 | 25 | #[async_trait] 26 | impl VoltCommand for Update { 27 | /// Display a help menu for the `volt update` command. 28 | // fn help() -> String { 29 | // format!( 30 | // r#"volt {} 31 | // 32 | // Update project dependencies 33 | // 34 | // Usage: {} {} {} 35 | // 36 | // Options: 37 | // 38 | // {} {} Output verbose messages on internal operations."#, 39 | // VERSION.bright_green().bold(), 40 | // "volt".bright_green().bold(), 41 | // "update".bright_purple(), 42 | // "file-name".white(), 43 | // "--verbose".blue(), 44 | // "(-v)".yellow() 45 | // ) 46 | // } 47 | 48 | /// Execute the `volt update` command 49 | /// 50 | /// update project dependencies 51 | /// ## Arguments 52 | /// * `app` - Instance of the command (`Arc`) 53 | /// * `packages` - List of packages to add (`Vec`) 54 | /// * `flags` - List of flags passed in through the CLI (`Vec`) 55 | /// ## Examples 56 | /// ``` 57 | /// // update project dependencies 58 | /// // .exec() is an async call so you need to await it 59 | /// update.exec(app, vec![], vec!["--yes"]).await; 60 | /// ``` 61 | /// ## Returns 62 | /// * `Result<()>` 63 | async fn exec(self, _config: VoltConfig) -> Result<()> { 64 | Ok(()) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/core/classes.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Volt Contributors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | pub mod create_templates; 18 | pub mod init_data; 19 | pub mod meta; 20 | pub mod package_manager; 21 | -------------------------------------------------------------------------------- /src/core/classes/create_templates.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Volt Contributors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | use serde::{Deserialize, Serialize}; 18 | use serde_json::to_string_pretty; 19 | 20 | use std::fmt; 21 | 22 | #[derive(Serialize, Deserialize, Debug)] 23 | pub enum Template { 24 | ReactApp, 25 | ReactAppTS, 26 | NextApp, 27 | NextAppTS, 28 | } 29 | 30 | impl Template { 31 | pub const fn as_str(&self) -> &'static str { 32 | match self { 33 | Self::ReactApp => "react-app", 34 | Self::ReactAppTS => "react-app-ts", 35 | Self::NextApp => "next-app", 36 | Self::NextAppTS => "next-app-ts", 37 | } 38 | } 39 | } 40 | 41 | impl fmt::Display for Template { 42 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 43 | write!(f, "{}", self.as_str()) 44 | } 45 | } 46 | 47 | impl Default for Template { 48 | fn default() -> Self { 49 | Self::ReactApp 50 | } 51 | } 52 | 53 | impl Template { 54 | pub const _OPTIONS: [&'static str; 4] = [ 55 | Self::ReactApp.as_str(), 56 | Self::ReactAppTS.as_str(), 57 | Self::NextApp.as_str(), 58 | Self::NextAppTS.as_str(), 59 | ]; 60 | 61 | #[allow(dead_code)] 62 | pub fn from_index(index: usize) -> Option { 63 | match index { 64 | 0 => Some(Self::ReactApp), 65 | 1 => Some(Self::ReactAppTS), 66 | 2 => Some(Self::NextApp), 67 | 3 => Some(Self::NextAppTS), 68 | _ => None, 69 | } 70 | } 71 | } 72 | 73 | #[derive(Serialize, Deserialize)] 74 | pub struct CreateTemplate { 75 | pub template: Template, 76 | } 77 | 78 | impl CreateTemplate { 79 | pub fn _into_string(self) -> String { 80 | to_string_pretty(&self).expect("Valid serialization state") 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/core/classes/init_data.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Volt Contributors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | use serde::{Deserialize, Serialize}; 18 | use serde_json::to_string_pretty; 19 | 20 | use std::fmt; 21 | 22 | #[derive(Debug, PartialEq)] 23 | pub enum License { 24 | Mit, 25 | Apache2, 26 | BSD3, 27 | BSD2, 28 | Gpl, 29 | Lgpl, 30 | Mpl, 31 | Cddl, 32 | Unlicense, 33 | Other, 34 | } 35 | 36 | impl License { 37 | pub const fn as_str(&self) -> &'static str { 38 | match self { 39 | Self::Mit => "MIT License", 40 | Self::Apache2 => "Apache License 2.0", 41 | Self::BSD3 => "BSD 3-Clause \"New\" or \"Revised\" License", 42 | Self::BSD2 => "BSD 2-Clause \"Simplified\" or \"FreeBSD\" License", 43 | Self::Gpl => "GNU General Public License (GPL)", 44 | Self::Lgpl => "GNU Library or \"Lesser\" General Public License (LGPL)", 45 | Self::Mpl => "Mozilla Public License 2.0", 46 | Self::Cddl => "Common Development and Distribution License", 47 | Self::Unlicense => "The Unlicense", 48 | Self::Other => "Other", 49 | } 50 | } 51 | } 52 | 53 | impl fmt::Display for License { 54 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 55 | write!(f, "{}", self.as_str()) 56 | } 57 | } 58 | 59 | impl Default for License { 60 | fn default() -> Self { 61 | Self::Mit 62 | } 63 | } 64 | 65 | impl License { 66 | pub const OPTIONS: [&'static str; 10] = [ 67 | Self::Mit.as_str(), 68 | Self::Apache2.as_str(), 69 | Self::BSD3.as_str(), 70 | Self::BSD2.as_str(), 71 | Self::Gpl.as_str(), 72 | Self::Lgpl.as_str(), 73 | Self::Mpl.as_str(), 74 | Self::Cddl.as_str(), 75 | Self::Unlicense.as_str(), 76 | Self::Other.as_str(), 77 | ]; 78 | 79 | #[allow(dead_code)] 80 | pub fn from_index(index: usize) -> Option { 81 | match index { 82 | 0 => Some(Self::Mit), 83 | 1 => Some(Self::Apache2), 84 | 2 => Some(Self::BSD3), 85 | 3 => Some(Self::BSD2), 86 | 4 => Some(Self::Gpl), 87 | 5 => Some(Self::Lgpl), 88 | 6 => Some(Self::Mpl), 89 | 7 => Some(Self::Cddl), 90 | 8 => Some(Self::Unlicense), 91 | 9 => Some(Self::Other), 92 | _ => None, 93 | } 94 | } 95 | } 96 | 97 | impl TryFrom<&str> for License { 98 | type Error = (); 99 | 100 | fn try_from(value: &str) -> Result { 101 | License::OPTIONS 102 | .iter() 103 | .position(|e| &value == e) 104 | .and_then(License::from_index) 105 | .ok_or(()) 106 | } 107 | } 108 | 109 | impl Serialize for License { 110 | fn serialize(&self, serializer: S) -> Result 111 | where 112 | S: serde::Serializer, 113 | { 114 | serializer.serialize_str(self.as_str()) 115 | } 116 | } 117 | 118 | impl<'de> Deserialize<'de> for License { 119 | fn deserialize(deserializer: D) -> Result 120 | where 121 | D: serde::Deserializer<'de>, 122 | { 123 | struct LicenseVisitor; 124 | impl<'de> serde::de::Visitor<'de> for LicenseVisitor { 125 | type Value = License; 126 | 127 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 128 | formatter.write_str("a valid License value") 129 | } 130 | 131 | fn visit_str(self, v: &str) -> Result 132 | where 133 | E: serde::de::Error, 134 | { 135 | License::try_from(v) 136 | .map_err(|_| E::custom(format!("{v} is not a valid License value"))) 137 | } 138 | } 139 | 140 | deserializer.deserialize_str(LicenseVisitor) 141 | } 142 | } 143 | 144 | #[derive(Serialize, Deserialize, Debug)] 145 | pub struct InitData { 146 | pub name: String, 147 | pub version: String, 148 | #[serde(skip_serializing_if = "Option::is_none")] 149 | pub description: Option, 150 | pub main: String, 151 | #[serde(skip_serializing_if = "Option::is_none")] 152 | pub author: Option, 153 | pub license: License, 154 | #[serde(skip_serializing_if = "Option::is_none")] 155 | pub private: Option, 156 | } 157 | 158 | impl InitData { 159 | pub fn into_string(self) -> String { 160 | to_string_pretty(&self).expect("Valid serialization state") 161 | } 162 | } 163 | 164 | #[cfg(test)] 165 | mod tests { 166 | use crate::core::classes::init_data::License; 167 | 168 | #[test] 169 | fn check_serialization_is_correct() { 170 | for i in 0..=9 { 171 | let l = License::from_index(i).unwrap(); 172 | let l_quoted_string = format!("\"{}\"", l.as_str().replace("\"", "\\\"")); 173 | 174 | let serialization = serde_json::to_string(&l).unwrap(); 175 | 176 | assert_eq!(serialization, l_quoted_string); 177 | } 178 | } 179 | 180 | #[test] 181 | fn check_deserialization_is_correct() { 182 | for i in 0..=9 { 183 | let l = License::from_index(i).unwrap(); 184 | let l_quoted_string = format!("\"{}\"", l.as_str().replace("\"", "\\\"")); 185 | 186 | let deserialization: License = serde_json::from_str(&l_quoted_string).unwrap(); 187 | 188 | assert_eq!(deserialization, l); 189 | } 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /src/core/classes/meta.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Volt Contributors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // TODO: Actually use this somewhere 18 | #[allow(dead_code)] 19 | pub struct Meta { 20 | pub verbose: bool, 21 | pub debug: bool, 22 | pub silent: bool, 23 | pub no_color: bool, 24 | } 25 | -------------------------------------------------------------------------------- /src/core/classes/package_manager.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Volt Contributors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | use serde::{Deserialize, Serialize}; 18 | use serde_json::to_string_pretty; 19 | 20 | use std::fmt; 21 | 22 | #[derive(Serialize, Deserialize)] 23 | pub enum PackageManager { 24 | Volt, 25 | Yarn, 26 | Pnpm, 27 | Npm, 28 | } 29 | 30 | impl PackageManager { 31 | pub const fn as_str(&self) -> &'static str { 32 | match self { 33 | Self::Volt => "Volt", 34 | Self::Yarn => "Yarn", 35 | Self::Pnpm => "pnpm", 36 | Self::Npm => "npm", 37 | } 38 | } 39 | } 40 | 41 | impl fmt::Display for PackageManager { 42 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 43 | write!(f, "{}", self.as_str()) 44 | } 45 | } 46 | 47 | impl Default for PackageManager { 48 | fn default() -> Self { 49 | Self::Volt 50 | } 51 | } 52 | 53 | impl PackageManager { 54 | pub const _OPTIONS: [&'static str; 4] = [ 55 | Self::Volt.as_str(), 56 | Self::Yarn.as_str(), 57 | Self::Pnpm.as_str(), 58 | Self::Npm.as_str(), 59 | ]; 60 | 61 | #[allow(dead_code)] 62 | pub fn from_index(index: usize) -> Option { 63 | match index { 64 | 0 => Some(Self::Volt), 65 | 1 => Some(Self::Yarn), 66 | 2 => Some(Self::Pnpm), 67 | 3 => Some(Self::Npm), 68 | _ => None, 69 | } 70 | } 71 | } 72 | 73 | #[derive(Serialize, Deserialize)] 74 | pub struct ChangePackageManger { 75 | pub template: PackageManager, 76 | } 77 | 78 | impl ChangePackageManger { 79 | #[allow(dead_code)] 80 | pub fn into_string(self) -> String { 81 | to_string_pretty(&self).unwrap() 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/core/io.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Volt Contributors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | use crate::{ 18 | cli::VoltConfig, 19 | core::{classes::meta::Meta, utils::voltapi::VoltPackage}, 20 | }; 21 | 22 | use colored::Colorize; 23 | use miette::IntoDiagnostic; 24 | use ssri::Integrity; 25 | use tar::Archive; 26 | 27 | use std::{ 28 | collections::HashMap, 29 | io::{Cursor, Read, Write}, 30 | path::PathBuf, 31 | }; 32 | 33 | pub fn _write(text: &str, metadata: &Meta) { 34 | if !metadata.silent { 35 | if metadata.no_color { 36 | println!("{}", text.bright_white()); 37 | } else { 38 | println!("{}", text); 39 | } 40 | } 41 | } 42 | 43 | pub fn _write_verbose(text: &str, metadata: &Meta) { 44 | if !metadata.silent && metadata.verbose { 45 | if metadata.no_color { 46 | println!( 47 | "{}: {}", 48 | "verbose".bright_white().bold(), 49 | text.bright_white() 50 | ); 51 | } else { 52 | println!( 53 | "{}: {}", 54 | "verbose".bright_green().bold(), 55 | text.bright_white() 56 | ); 57 | } 58 | } 59 | } 60 | 61 | pub fn _write_debug(text: &str, metadata: &Meta) { 62 | if !metadata.silent && metadata.debug { 63 | if metadata.no_color { 64 | println!("{}: {}", "debug".bright_white().bold(), text.bright_white()); 65 | } else { 66 | println!( 67 | "{}: {}", 68 | "debug".bright_yellow().bold(), 69 | text.bright_white() 70 | ); 71 | } 72 | } 73 | } 74 | 75 | pub fn extract_tarball( 76 | data: Vec, 77 | package: &VoltPackage, 78 | config: &VoltConfig, 79 | ) -> miette::Result<()> { 80 | // Generate the tarball archive given the decompressed bytes 81 | let mut node_archive = Archive::new(Cursor::new(data)); 82 | 83 | // extract to both the global store + node_modules (in the case of them using the pnpm linking algorithm) 84 | let mut cas_file_map: HashMap = HashMap::new(); 85 | 86 | // Add package's directory to list of created directories 87 | let mut created_directories: Vec = vec![]; 88 | 89 | for entry in node_archive.entries().into_diagnostic()? { 90 | let mut entry = entry.into_diagnostic()?; 91 | 92 | // Read the contents of the entry 93 | let mut buffer = Vec::with_capacity(entry.size() as usize); 94 | entry.read_to_end(&mut buffer).into_diagnostic()?; 95 | 96 | let entry_path = entry.path().unwrap(); 97 | 98 | // Remove `package/` from `package/lib/index.js` 99 | let cleaned_entry_path_string = entry_path.strip_prefix("package/").unwrap(); 100 | 101 | // Create the path to the local .volt directory 102 | let mut package_directory = config.node_modules()?.join(VoltConfig::VOLT_HOME); 103 | 104 | // Add package's directory to it 105 | package_directory.push(package.directory_name()); 106 | 107 | // push node_modules/.volt/send@0.17.2 to the list (because we created it in the previous step) 108 | created_directories.push(package_directory.clone()); 109 | 110 | // Add the cleaned path to the package's directory 111 | let mut entry_path = package_directory.clone(); 112 | 113 | entry_path.push("node_modules"); 114 | 115 | entry_path.push(&package.name); 116 | 117 | entry_path.push(cleaned_entry_path_string); 118 | 119 | // Get the entry's parent 120 | let entry_path_parent = entry_path.parent().unwrap(); 121 | 122 | // If we haven't created this directory yet, create it 123 | if !created_directories.iter().any(|p| p == entry_path_parent) { 124 | created_directories.push(entry_path_parent.to_path_buf()); 125 | std::fs::create_dir_all(entry_path_parent).into_diagnostic()?; 126 | } 127 | 128 | let mut file_path = package_directory.join("node_modules"); 129 | 130 | file_path.push(package.name.clone()); 131 | 132 | file_path.push(cleaned_entry_path_string); 133 | 134 | // Write the contents to node_modules 135 | let mut file = std::fs::File::create(&file_path).unwrap(); 136 | 137 | file.write_all(&buffer).into_diagnostic()?; 138 | 139 | // Write the contents of the entry into the content-addressable store located at `app.volt_dir` 140 | // We get a hash of the file 141 | let sri = cacache::write_hash_sync(&config.volt_home()?, &buffer).into_diagnostic()?; 142 | 143 | // Insert the name of the file and map it to the hash of the file 144 | cas_file_map.insert(cleaned_entry_path_string.to_str().unwrap().to_string(), sri); 145 | } 146 | 147 | // Write the file, shasum map to the content-addressable store 148 | cacache::write_sync( 149 | &config.volt_home()?, 150 | &package.cacache_key(), 151 | serde_json::to_string(&cas_file_map).into_diagnostic()?, 152 | ) 153 | .into_diagnostic()?; 154 | 155 | Ok(()) 156 | } 157 | -------------------------------------------------------------------------------- /src/core/mod.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Volt Contributors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #[macro_use] 18 | pub mod utils; 19 | pub mod classes; 20 | pub mod io; 21 | pub mod model; 22 | pub mod net; 23 | pub mod prompt; 24 | -------------------------------------------------------------------------------- /src/core/model.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Volt Contributors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | pub mod http_manager; 18 | pub mod lock_file; 19 | -------------------------------------------------------------------------------- /src/core/model/http_manager.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Volt Contributors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | use crate::core::utils::package::NpmPackage; 18 | 19 | use isahc::{http::StatusCode, AsyncReadResponseExt}; 20 | use thiserror::Error; 21 | 22 | use std::io; 23 | 24 | #[derive(Error, Debug)] 25 | pub enum GetPackageError { 26 | #[error("network request failed with registry")] 27 | Request(isahc::Error), 28 | #[error("unable to read network response")] 29 | IO(io::Error), 30 | #[error("unable to deserialize network response: {0:?}")] 31 | Json(serde_json::Error), 32 | } 33 | 34 | #[allow(dead_code)] 35 | /// Request a package from `registry.yarnpkg.com` 36 | /// 37 | /// Uses `chttp` async implementation to send a `get` request for the package 38 | /// ## Arguments 39 | /// * `name` - Name of the package to request from `registry.yarnpkg.com` 40 | /// ## Examples 41 | /// ``` 42 | /// // Await an async response 43 | /// get_package("react").await; 44 | /// ``` 45 | /// ## Returns 46 | /// * `Result, GetPackageError>` 47 | pub async fn get_package(name: &str) -> Result, GetPackageError> { 48 | let mut resp = isahc::get_async(format!("http://registry.yarnpkg.com/{}", name)) 49 | .await 50 | .map_err(GetPackageError::Request)?; 51 | 52 | if !resp.status().is_success() { 53 | match resp.status() { 54 | StatusCode::NOT_FOUND => {} 55 | StatusCode::INTERNAL_SERVER_ERROR => {} 56 | StatusCode::METHOD_NOT_ALLOWED => {} 57 | _ => {} 58 | } 59 | } 60 | 61 | let body_string = resp.text().await.map_err(GetPackageError::IO)?; 62 | let package: NpmPackage = serde_json::from_str(&body_string).map_err(GetPackageError::Json)?; 63 | 64 | Ok(Some(package)) 65 | } 66 | -------------------------------------------------------------------------------- /src/core/model/lock_file.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Volt Contributors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | use miette::Result; 18 | use serde::{Deserialize, Serialize}; 19 | use speedy::{Readable, Writable}; 20 | use thiserror::Error; 21 | 22 | use std::{ 23 | collections::HashMap, 24 | fs::File, 25 | io::{self, BufReader, Write}, 26 | path::Path, 27 | }; 28 | 29 | use crate::core::utils::voltapi::VoltPackage; 30 | 31 | #[derive(Error, Debug)] 32 | pub enum LockFileError { 33 | #[error("unable to read lock file")] 34 | _IO(io::Error), 35 | #[error("unable to deserialize lock file")] 36 | #[allow(dead_code)] 37 | Decode(serde_json::Error), 38 | // #[error("unable to serialize lock file")] 39 | // Encode(serde_json::Error), 40 | } 41 | 42 | /// The lock file is responsible for locking/pinning dependency versions in a given project. 43 | /// It stores a list of dependencies along with their resolved version, registry url, and sha1 checksum. 44 | /// 45 | /// ## Examples 46 | /// 47 | /// ``` 48 | /// // Load the lock file for the current project or create new lock file 49 | /// let mut lock_file = LockFile::load(lock_file_path) 50 | /// .unwrap_or_else(|| LockFile::new(lock_file_path)); 51 | /// 52 | /// // Add dependency 53 | /// lock_file.add( 54 | /// ("react".to_string(), "^1.0.0".to_string()), 55 | /// DependencyLock { 56 | /// name: "react".to_string(), 57 | /// version: "1.2.6".to_string(), 58 | /// tarball: String::new(), 59 | /// sha1: String::new(), 60 | /// } 61 | /// ); 62 | /// 63 | /// // Save changes to disk 64 | /// lock_file.save().expect("Unable to save lock file"); 65 | /// ``` 66 | #[derive(Clone, Debug, Writable, Readable, Serialize, Deserialize)] 67 | pub struct LockFile { 68 | pub path: String, 69 | #[speedy(skip)] 70 | pub global: bool, 71 | #[speedy(skip)] 72 | pub dependencies: HashMap, 73 | } 74 | 75 | impl LockFile { 76 | /// Creates a new instance of a lock file with a path it should be saved at. 77 | /// It can be saved to the file by calling [`Self::save()`]. 78 | pub fn _new>(path: P, global: bool) -> Self { 79 | Self { 80 | path: path.as_ref().to_str().unwrap().to_string(), 81 | global, 82 | dependencies: HashMap::with_capacity(1), // We will be installing at least 1 dependency 83 | } 84 | } 85 | 86 | pub fn _add(_package: VoltPackage) {} 87 | 88 | /// Loads a lock file from the given path. 89 | pub fn _load>(path: P, global: bool) -> Result { 90 | let path = path.as_ref(); 91 | 92 | let dependencies = if path.exists() { 93 | let f = File::open(path).map_err(LockFileError::_IO)?; 94 | let reader = BufReader::new(f); 95 | 96 | if global { 97 | LockFile::read_from_buffer(reader.buffer()).unwrap() 98 | } else { 99 | serde_json::from_reader(reader).unwrap() 100 | } 101 | } else { 102 | LockFile { 103 | path: path.to_str().unwrap().to_string(), 104 | global, 105 | dependencies: HashMap::new(), 106 | } 107 | }; 108 | 109 | Ok(dependencies) 110 | } 111 | 112 | // Saves a lock file dumping pretty, formatted json 113 | // pub fn save_pretty(&self) -> Result<(), LockFileError> { 114 | // let lock_file = File::create(&self.path).map_err(LockFileError::IO)?; 115 | // let writer = BufWriter::new(lock_file); 116 | // serde_json::to_writer_pretty(writer, &self.dependencies).map_err(LockFileError::Encode) 117 | // } 118 | 119 | // Saves a lock file to the same path it was opened from. 120 | pub fn _save(&self) -> Result<()> { 121 | let mut lock_file = File::create(&self.path).unwrap(); 122 | 123 | lock_file 124 | .write_all(&self.dependencies.write_to_vec().unwrap()) 125 | .unwrap(); 126 | 127 | Ok(()) 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/core/net.rs: -------------------------------------------------------------------------------- 1 | use std::time::Instant; 2 | 3 | use crate::core::{ 4 | utils::constants::MAX_RETRIES, 5 | utils::errors::VoltError, 6 | utils::voltapi::{VoltPackage, VoltResponse}, 7 | utils::State, 8 | }; 9 | 10 | use colored::Colorize; 11 | use futures_util::{stream::FuturesUnordered, StreamExt}; 12 | use indicatif::ProgressBar; 13 | use isahc::AsyncReadResponseExt; 14 | use miette::{IntoDiagnostic, Result}; 15 | use package_spec::PackageSpec; 16 | use reqwest::StatusCode; 17 | use speedy::Readable; 18 | 19 | pub async fn get_volt_response_multi( 20 | packages: &[PackageSpec], 21 | progress_bar: &ProgressBar, 22 | ) -> Vec> { 23 | packages 24 | .iter() 25 | .map(|spec| { 26 | if let PackageSpec::Npm { 27 | name, requested, .. 28 | } = spec 29 | { 30 | let mut version: String = "latest".to_string(); 31 | 32 | if requested.is_some() { 33 | version = requested.as_ref().unwrap().to_string(); 34 | }; 35 | 36 | progress_bar.set_message(format!("{}@{}", name, version.truecolor(125, 125, 125))); 37 | } 38 | 39 | get_volt_response(spec) 40 | }) 41 | .collect::>() 42 | .collect::>>() 43 | .await 44 | } 45 | 46 | // Get response from volt CDN 47 | pub async fn get_volt_response(package_spec: &PackageSpec) -> Result { 48 | // number of retries 49 | let mut retries = 0; 50 | 51 | // we know that PackageSpec is of type npm (we filtered the non-npm ones out) 52 | 53 | if let PackageSpec::Npm { name, .. } = package_spec { 54 | // loop until MAX_RETRIES reached. 55 | loop { 56 | // get a response 57 | let mut response = 58 | isahc::get_async(format!("http://registry.voltpkg.com/{}.sp", &package_spec)) 59 | .await 60 | .map_err(VoltError::NetworkError)?; 61 | 62 | // check the status of the response 63 | match response.status() { 64 | // 200 (OK) 65 | StatusCode::OK => { 66 | let mut response: VoltResponse = 67 | VoltResponse::read_from_buffer(&response.bytes().await.unwrap()).unwrap(); 68 | 69 | response.name = name.to_string(); 70 | 71 | return Ok(response); 72 | } 73 | // 429 (TOO_MANY_REQUESTS) 74 | StatusCode::TOO_MANY_REQUESTS => { 75 | return Err(VoltError::TooManyRequests { 76 | url: format!("http://registry.voltpkg.com/{}.sp", &package_spec), 77 | } 78 | .into()); 79 | } 80 | // 400 (BAD_REQUEST) 81 | StatusCode::BAD_REQUEST => { 82 | return Err(VoltError::BadRequest { 83 | url: format!("http://registry.voltpkg.com/{}.sp", &package_spec), 84 | } 85 | .into()); 86 | } 87 | // 404 (NOT_FOUND) 88 | StatusCode::NOT_FOUND if retries == MAX_RETRIES => { 89 | return Err(VoltError::PackageNotFound { 90 | url: format!("http://registry.voltpkg.com/{}.sp", &package_spec), 91 | package_name: package_spec.to_string(), 92 | } 93 | .into()); 94 | } 95 | // Other Errors 96 | _ => { 97 | if retries == MAX_RETRIES { 98 | return Err(VoltError::NetworkUnknownError { 99 | url: format!("http://registry.voltpkg.com/{}.sp", name), 100 | package_name: package_spec.to_string(), 101 | code: response.status().as_str().to_string(), 102 | } 103 | .into()); 104 | } 105 | } 106 | } 107 | 108 | retries += 1; 109 | } 110 | } else { 111 | panic!("Volt does not support non-npm package specifications yet."); 112 | } 113 | } 114 | 115 | /// downloads and extracts tarball file from package 116 | pub async fn fetch_tarball(package: &VoltPackage, state: State) -> Result { 117 | // Recieve the tarball from the npm registry 118 | let response = state 119 | .http_client 120 | .get(&package.tarball) 121 | .send() 122 | .await 123 | .into_diagnostic()? 124 | .bytes() 125 | .await 126 | .into_diagnostic()?; 127 | 128 | Ok(response) 129 | } 130 | 131 | pub async fn fetch_dep_tree( 132 | data: &[PackageSpec], 133 | progress_bar: &ProgressBar, 134 | ) -> Result> { 135 | if data.len() > 1 { 136 | Ok(get_volt_response_multi(data, progress_bar) 137 | .await 138 | .into_iter() 139 | .collect::>>()?) 140 | } else { 141 | if let PackageSpec::Npm { 142 | name, requested, .. 143 | } = &data[0] 144 | { 145 | let mut version: String = "latest".to_string(); 146 | 147 | if requested.is_some() { 148 | version = requested.as_ref().unwrap().to_string(); 149 | }; 150 | 151 | progress_bar.set_message(format!("{}@{}", name, version.truecolor(125, 125, 125))); 152 | } 153 | 154 | Ok(vec![get_volt_response(&data[0]).await?]) 155 | } 156 | } 157 | 158 | pub async fn _ping() { 159 | let _ping = Instant::now(); 160 | 161 | println!("PING! http://registry.voltpkg.com/"); 162 | 163 | let response = isahc::get_async("http://registry.voltpkg.com/ping") 164 | .await 165 | .unwrap(); 166 | 167 | match response.status() { 168 | StatusCode::OK => { 169 | let pong = Instant::now(); 170 | 171 | println!( 172 | "PONG! http://registry.voltpkg.com/ {}", 173 | pong.elapsed().as_secs_f32() 174 | ); 175 | } 176 | _ => { 177 | println!("Ping failed"); 178 | } 179 | } 180 | 181 | let _ping = Instant::now(); 182 | 183 | println!("PING! https://registry.npmjs.org/"); 184 | 185 | let response = isahc::get_async("https://registry.npmjs.org/") 186 | .await 187 | .unwrap(); 188 | 189 | match response.status() { 190 | StatusCode::OK => { 191 | let pong = Instant::now(); 192 | 193 | println!( 194 | "PONG! https://registry.npmjs.org/ {}", 195 | pong.elapsed().as_secs_f32() 196 | ); 197 | } 198 | _ => { 199 | println!("Ping failed"); 200 | } 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /src/core/prompt.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Volt Contributors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | pub mod input; 18 | pub mod prompts; 19 | -------------------------------------------------------------------------------- /src/core/utils/api.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Volt Contributors 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. 12 | */ 13 | 14 | use speedy::{Readable, Writable}; 15 | use std::collections::HashMap; 16 | 17 | #[derive(Debug, Clone, Writable, Readable, Debug, Clone, Writable, Readable)] 18 | struct VoltResponse { 19 | version: String, // the latest version of the package 20 | versions: Vec, // list of versions of the package 21 | tree: HashMap, // the flattened dependency tree for the latest version of the package 22 | } 23 | 24 | #[derive(Debug, Clone, Writable, Readable)] 25 | struct VoltPackage { 26 | pub name: String, // the name of the package 27 | pub version: String, // the version of the package 28 | pub integrity: String, // sha-1 base64 encoded hash or the "integrity" field if it exists 29 | pub tarball: String, // url to the tarball to fetch 30 | pub bin: Option>, // binary scripts required by / for the package 31 | pub dependencies: Option>, // dependencies of the package 32 | pub dev_dependencies: Option>, // dev dependencies of the package 33 | pub peer_dependencies: Option>, // peer dependencies of the package 34 | pub peer_dependencies_meta: Option>, // peer dependencies metadata of the package 35 | pub optional_dependencies: Option>, // optional dependencies of the package 36 | pub overrides: Option>, // overrides specific to the package 37 | pub engines: Option>, // engines compatible with the package 38 | pub os: Option>, // operating systems compatible with the package 39 | pub cpu: Option>, // cpu architectures compatible with the package 40 | } -------------------------------------------------------------------------------- /src/core/utils/constants.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Volt Contributors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // pub static PROGRESS_CHARS: &str = "██ "; 18 | pub static MAX_RETRIES: u8 = 4; 19 | -------------------------------------------------------------------------------- /src/core/utils/errors.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Volt Contributors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | use miette::Diagnostic; 18 | use thiserror::Error; 19 | 20 | #[derive(Debug, Error, Diagnostic)] 21 | pub enum VoltError { 22 | // #[error("failed to enable ansi support")] 23 | // #[diagnostic(code(volt::environment::enable_ansi_support))] 24 | // EnableAnsiSupport(), 25 | #[error("failed to detect `{env}`")] 26 | #[diagnostic(code(volt::environment::get))] 27 | EnvironmentError { source: std::io::Error, env: String }, 28 | 29 | #[error("failed to parse package specification: `{spec}`")] 30 | #[diagnostic(code(volt::package_spec::parse))] 31 | _PackageSpecificationError { spec: String }, 32 | 33 | #[error("failed to detect your home directory")] 34 | #[diagnostic(code(volt::environment::home_dir))] 35 | GetHomeDirError, 36 | 37 | #[error("failed to get the name of the current directory")] 38 | #[diagnostic(code(volt::environment::current_dir_name))] 39 | GetCurrentDirNameError, 40 | 41 | // #[error("failed to initialize lz4 decoder")] 42 | // #[diagnostic(code(volt::decode::lz4::initialize))] 43 | // DecoderError(#[source] std::io::Error), 44 | 45 | // #[error("failed to decode lz4 encoded data")] 46 | // #[diagnostic(code(volt::decode::lz4::decode))] 47 | // DecodeError(#[source] std::io::Error), 48 | #[error("failed to recieve response from the registry")] 49 | #[diagnostic(code(volt::network))] 50 | NetworkError(isahc::Error), 51 | 52 | // #[error("failed to recieve byte response")] 53 | // #[diagnostic(code(volt::network::rec))] 54 | // NetworkRecError(#[source] std::io::Error), 55 | #[error("failed to create directory")] 56 | #[diagnostic(code(volt::io::create_dir))] 57 | _CreateDirError(#[source] std::io::Error), 58 | 59 | #[error("GET {url} - 404 - {package_name} was not found in the volt registry, or you don't have the permission to request it.")] 60 | #[diagnostic(code(volt::registry::volt::package_not_found))] 61 | PackageNotFound { url: String, package_name: String }, 62 | 63 | #[error("GET {url} - 429 - Too many requests has been sent to {url} on the volt registry. Please try again later.")] 64 | #[diagnostic(code(volt::registry::volt::too_many_requests))] 65 | TooManyRequests { url: String }, 66 | 67 | #[error("GET {url} - 400 - Bad request. Please try again later.")] 68 | #[diagnostic(code(volt::registry::volt::bad_request))] 69 | BadRequest { url: String }, 70 | 71 | #[error("GET {url} - {} - An unknown error occured. Please try again later.")] 72 | #[diagnostic(code(volt::registry::volt::unknown_error))] 73 | NetworkUnknownError { 74 | url: String, 75 | package_name: String, 76 | code: String, 77 | }, 78 | 79 | #[error("failed to parse {hash} integrity hash.")] 80 | #[diagnostic(code(volt::integrity::parse))] 81 | _HashParseError { hash: String }, 82 | 83 | #[error("failed to copy bytes to hasher.")] 84 | #[diagnostic(code(volt::hasher::copy))] 85 | _HasherCopyError(#[source] std::io::Error), 86 | 87 | #[error("failed to verify tarball checksum")] 88 | #[diagnostic(code(volt::integrity::verify))] 89 | _ChecksumVerificationError, 90 | 91 | #[error("failed to convert integrity into hex")] 92 | #[diagnostic(code(volt::integrity::convert))] 93 | _IntegrityConversionError, 94 | 95 | #[error("failed to deserialize slice to `SpeedyVoltResponse`")] 96 | #[diagnostic(code(volt::integrity::convert))] 97 | _DeserializeError, 98 | 99 | #[error("failed to build request client")] 100 | #[diagnostic(code(volt::network::builder))] 101 | _RequestBuilderError(#[source] isahc::http::Error), 102 | 103 | #[error("failed to build recieve response text")] 104 | #[diagnostic(code(volt::io::rec::text))] 105 | _IoTextRecError(#[source] std::io::Error), 106 | 107 | #[error("failed to find a hash that matches the specified version requirement: {version}")] 108 | #[diagnostic(code(volt::io::rec::text))] 109 | _HashLookupError { version: String }, 110 | 111 | #[error("failed to find a version that matches the specified version requirement for {name}")] 112 | #[diagnostic(code(volt::io::rec::text))] 113 | _VersionLookupError { name: String }, 114 | 115 | #[error("failed to read `{name}`")] 116 | #[diagnostic(code(volt::io::file::read))] 117 | ReadFileError { 118 | source: std::io::Error, 119 | name: String, 120 | }, 121 | 122 | #[error("failed to write to `{name}`")] 123 | #[diagnostic(code(volt::io::file::write))] 124 | WriteFileError { 125 | source: std::io::Error, 126 | name: String, 127 | }, 128 | 129 | // Convert error to `String` instead of having a `source` because `git_config::parser::Error` 130 | // has a lifetime parameter 131 | #[error("failed to parse git configuration file: `{error_text}`")] 132 | #[diagnostic(code(volt::git::parse))] 133 | GitConfigParseError { error_text: String }, 134 | 135 | #[error("an unknown error occured.")] 136 | #[diagnostic(code(volt::unknown))] 137 | _UnknownError, 138 | } 139 | -------------------------------------------------------------------------------- /src/core/utils/extensions.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | 3 | #[allow(clippy::module_name_repetitions)] 4 | pub trait PathExtensions { 5 | fn file_name_as_str(&self) -> Option<&str>; 6 | fn file_name_as_string(&self) -> Option; 7 | } 8 | 9 | impl PathExtensions for Path { 10 | fn file_name_as_str(&self) -> Option<&str> { 11 | self.file_name()?.to_str() 12 | } 13 | 14 | fn file_name_as_string(&self) -> Option { 15 | self.file_name_as_str().map(String::from) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/core/utils/helper.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Volt Contributors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | use colored::{ColoredString, Colorize}; 18 | 19 | use std::fmt::Display; 20 | 21 | pub trait CustomColorize: Colorize { 22 | fn caused_by_style(self) -> ColoredString 23 | where 24 | Self: Sized, 25 | { 26 | self.italic().truecolor(190, 190, 190) 27 | } 28 | 29 | fn error_style(self) -> ColoredString 30 | where 31 | Self: Sized, 32 | { 33 | self.on_bright_red().black() 34 | } 35 | 36 | fn warning_style(self) -> ColoredString 37 | where 38 | Self: Sized, 39 | { 40 | self.bright_yellow().bold() 41 | } 42 | 43 | fn info_style(self) -> ColoredString 44 | where 45 | Self: Sized, 46 | { 47 | self.bright_purple().bold() 48 | } 49 | 50 | fn success_style(self) -> ColoredString 51 | where 52 | Self: Sized, 53 | { 54 | self.bright_green().bold() 55 | } 56 | } 57 | 58 | impl CustomColorize for T {} 59 | 60 | pub trait ResultLogErrorExt { 61 | fn unwrap_and_handle_error(self); 62 | } 63 | 64 | impl ResultLogErrorExt for Result<(), E> { 65 | fn unwrap_and_handle_error(self) { 66 | if let Err(e) = self { 67 | println!("{} {}", "error".error_style(), e); 68 | } 69 | } 70 | } 71 | 72 | /* 73 | * macro_rules! error { 74 | * ($($tt:tt)*) => { print!("{} ", $crate::core::utils::helper::CustomColorize::error_style(" ERROR ")); println!($($tt)*); }; 75 | * } 76 | * 77 | * macro_rules! warning { 78 | * ($($tt:tt)*) => { print!("{}", $crate::core::utils::helper::CustomColorize::warning_style("warning: ")); println!($($tt)*); }; 79 | * } 80 | * 81 | * macro_rules! info { 82 | * ($($tt:tt)*) => { print!("{}", $crate::core::utils::helper::CustomColorize::info_style("info: ")); println!($($tt)*); }; 83 | * } 84 | */ 85 | -------------------------------------------------------------------------------- /src/core/utils/scripts.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Volt Contributors 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. 12 | */ 13 | 14 | //! Handle an unknown command (can be listed in scripts). 15 | 16 | // use crate::core::utils::errors; 17 | // use crate::core::utils::package::PackageJson; 18 | use async_trait::async_trait; 19 | // use colored::Colorize; 20 | use miette::Result; 21 | 22 | use crate::cli::{VoltCommand, VoltConfig}; 23 | 24 | pub struct Script {} 25 | 26 | #[async_trait] 27 | impl VoltCommand for Script { 28 | /// Execute the `volt {script}` command 29 | /// 30 | /// Execute a script command (any script command specified in package.json) 31 | /// ## Arguments 32 | /// * `app` - Instance of the command (`Arc`) 33 | /// ## Examples 34 | /// ``` 35 | /// // Clone the react repository (https://github.com/facebook/react) 36 | /// // .exec() is an async call so you need to await it 37 | /// Add.exec(app).await; 38 | /// ``` 39 | /// ## Returns 40 | /// * `Result<()>` 41 | async fn exec(self, _config: VoltConfig) -> Result<()> { 42 | // let package_json = PackageJson::from("package.json"); 43 | 44 | // let args = app.args.clone(); 45 | // let command: &str = args[0].as_str(); 46 | 47 | // if package_json.scripts.contains_key(command) { 48 | // // let script = package_json.scripts.get(command).unwrap(); 49 | // // let mut split: Vec<&str> = script.split_ascii_whitespace().into_iter().collect(); 50 | // // let mut bin_cmd = format!("{}.cmd", split[0]); 51 | 52 | // // split[0] = bin_cmd.as_str(); 53 | 54 | // // let exec = format!("node_modules\\scripts\\{}", split.join(" ")); 55 | 56 | // // if cfg!(target_os = "unix") { 57 | // // #[allow(unused_assignments)] 58 | // // bin_cmd = format!("{}.sh", split[0]); 59 | // // } 60 | 61 | // if cfg!(target_os = "windows ") { 62 | // // std::process::Command::new("cmd.exe") 63 | // // .arg("/C") 64 | // // .arg(exec) 65 | // // .spawn() 66 | // // .unwrap(); 67 | // } else { 68 | // // std::process::Command::new("sh").arg(exec).spawn().unwrap(); 69 | // } 70 | // } else { 71 | // error!("{} is not a valid command.", command.bright_yellow().bold()); 72 | // } 73 | 74 | Ok(()) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/core/utils/voltapi.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Volt Contributors 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. 12 | */ 13 | 14 | use serde::{Deserialize, Serialize}; 15 | use speedy::{Readable, Writable}; 16 | use std::collections::HashMap; 17 | 18 | #[derive(Debug, Clone, Writable, Readable)] 19 | pub struct VoltResponse { 20 | #[speedy(skip)] 21 | pub name: String, 22 | pub version: String, // the latest version of the package 23 | pub versions: Vec, // list of versions of the package 24 | pub tree: HashMap, // the flattened dependency tree for the latest version of the package 25 | } 26 | 27 | #[derive(Serialize, Deserialize, Debug, Clone, Writable, Readable)] 28 | pub struct VoltPackage { 29 | pub name: String, // the name of the package 30 | pub version: String, // the version of the package 31 | pub optional: bool, // whether the package is optional or not 32 | pub integrity: String, // sha-1 base64 encoded hash or the "integrity" field if it exists 33 | pub tarball: String, // url to the tarball to fetch 34 | pub bin: Option, // binary scripts required by / for the package 35 | pub scripts: Option>, // scripts required by / for the package 36 | pub dependencies: Option>, // dependencies of the package 37 | pub peer_dependencies: Option>, // peer dependencies of the package 38 | pub peer_dependencies_meta: Option>, // peer dependencies metadata of the package 39 | pub optional_dependencies: Option>, // optional dependencies of the package 40 | pub overrides: Option>, // overrides specific to the package 41 | pub engines: Option, // engines compatible with the package 42 | pub os: Option>, // operating systems compatible with the package 43 | pub cpu: Option>, // cpu architectures compatible with the package 44 | } 45 | 46 | impl VoltPackage { 47 | pub fn directory_name(&self) -> String { 48 | format!("{}@{}", self.name.replace('/', "+"), self.version) 49 | } 50 | 51 | pub fn cacache_key(&self) -> String { 52 | format!("pkg::{}::{}::{}", self.name, self.version, self.integrity) 53 | } 54 | } 55 | 56 | #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Readable, Writable)] 57 | #[serde(untagged)] 58 | pub enum Engine { 59 | String(String), 60 | List(Vec), 61 | Map(HashMap), 62 | } 63 | 64 | impl Default for Engine { 65 | fn default() -> Self { 66 | Self::String(String::new()) 67 | } 68 | } 69 | 70 | #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Readable, Writable)] 71 | #[serde(untagged)] 72 | pub enum Bin { 73 | String(String), 74 | Map(HashMap), 75 | } 76 | 77 | impl Default for Bin { 78 | fn default() -> Self { 79 | Self::String(String::new()) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Copyright 2021 Volt Contributors 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | use mimalloc::MiMalloc; 18 | 19 | #[global_allocator] 20 | static GLOBAL: MiMalloc = MiMalloc; 21 | 22 | mod cli; 23 | mod commands; 24 | mod core; 25 | 26 | use std::{str::FromStr, time::Instant}; 27 | 28 | use tracing::Level; 29 | use tracing_subscriber::EnvFilter; 30 | 31 | use crate::cli::{VoltCli, VoltCommand}; 32 | 33 | //#[tokio::main(worker_threads = 6)] 34 | //#[tokio::main(flavor = "current_thread")] 35 | fn main() -> miette::Result<()> { 36 | let body = async { 37 | tracing_subscriber::fmt() 38 | .with_max_level(Level::TRACE) 39 | .with_env_filter( 40 | EnvFilter::try_from_default_env() 41 | .unwrap_or_else(|_| EnvFilter::from_str("volt=info").unwrap()), 42 | ) 43 | .without_time() 44 | .init(); 45 | 46 | if cfg!(windows) { 47 | core::utils::enable_ansi_support().unwrap(); 48 | } 49 | 50 | let start = Instant::now(); 51 | 52 | let app = VoltCli::new(); 53 | 54 | app.cmd.exec(app.config).await?; 55 | 56 | println!("Finished in {:.2}s", start.elapsed().as_secs_f32()); 57 | 58 | Ok(()) 59 | }; 60 | 61 | tokio::runtime::Builder::new_multi_thread() 62 | .worker_threads(6) 63 | .max_blocking_threads(6) 64 | .thread_name("volt") 65 | .enable_all() 66 | .build() 67 | .expect("Failed to build the runtime") 68 | .block_on(body) 69 | 70 | /* 71 | *tokio::runtime::Builder::new_multi_thread() 72 | * .worker_threads(2) 73 | * .enable_all() 74 | * .build() 75 | * .expect("Failed building the Runtime") 76 | * .block_on(body) 77 | */ 78 | } 79 | --------------------------------------------------------------------------------