├── .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 | [](https://github.com/voltpkg/volt/stargazers)
78 |
79 | [](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