The response has been limited to 50k tokens of the smallest files in the repo. You can remove this limitation by removing the max tokens filter.
├── .cargo
    └── config.toml
├── .github
    ├── dependabot.yml
    └── workflows
    │   ├── api-docs.yml
    │   ├── release.yml
    │   └── test.yml
├── .gitignore
├── .vscode
    ├── launch.json
    └── settings.json
├── CODE_OF_CONDUCT.md
├── COMPATIBILITY.md
├── CONTRIBUTING.md
├── Cargo.lock
├── Cargo.toml
├── LICENSE
├── README.md
├── RELEASES.md
├── ci
    ├── build-linux.sh
    ├── build-macos.sh
    ├── docker
    │   └── Dockerfile
    └── volta.manifest
├── crates
    ├── archive
    │   ├── Cargo.toml
    │   ├── fixtures
    │   │   ├── tarballs
    │   │   │   └── test-file.tar.gz
    │   │   └── zips
    │   │   │   └── test-file.zip
    │   └── src
    │   │   ├── lib.rs
    │   │   ├── tarball.rs
    │   │   └── zip.rs
    ├── fs-utils
    │   ├── Cargo.toml
    │   └── src
    │   │   └── lib.rs
    ├── progress-read
    │   ├── Cargo.toml
    │   └── src
    │   │   └── lib.rs
    ├── test-support
    │   ├── Cargo.toml
    │   └── src
    │   │   ├── lib.rs
    │   │   ├── matchers.rs
    │   │   ├── paths.rs
    │   │   └── process.rs
    ├── validate-npm-package-name
    │   ├── Cargo.toml
    │   └── src
    │   │   └── lib.rs
    ├── volta-core
    │   ├── Cargo.toml
    │   ├── fixtures
    │   │   ├── basic
    │   │   │   ├── node_modules
    │   │   │   │   ├── .bin
    │   │   │   │   │   └── rsvp
    │   │   │   │   ├── @namespace
    │   │   │   │   │   └── some-dep
    │   │   │   │   │   │   └── package.json
    │   │   │   │   ├── @namespaced
    │   │   │   │   │   └── something-else
    │   │   │   │   │   │   └── package.json
    │   │   │   │   ├── eslint
    │   │   │   │   │   └── package.json
    │   │   │   │   ├── rsvp
    │   │   │   │   │   └── package.json
    │   │   │   │   └── typescript
    │   │   │   │   │   └── package.json
    │   │   │   ├── package.json
    │   │   │   ├── rsvp
    │   │   │   └── subdir
    │   │   │   │   └── .gitkeep
    │   │   ├── cycle-1
    │   │   │   ├── package.json
    │   │   │   └── volta.json
    │   │   ├── cycle-2
    │   │   │   ├── package.json
    │   │   │   ├── workspace-1.json
    │   │   │   └── workspace-2.json
    │   │   ├── hooks
    │   │   │   ├── bins.json
    │   │   │   ├── event_url.json
    │   │   │   ├── format_github.json
    │   │   │   ├── format_npm.json
    │   │   │   ├── prefixes.json
    │   │   │   ├── project
    │   │   │   │   ├── .volta
    │   │   │   │   │   └── hooks.json
    │   │   │   │   └── package.json
    │   │   │   └── templates.json
    │   │   ├── nested
    │   │   │   ├── node_modules
    │   │   │   │   └── .bin
    │   │   │   │   │   └── eslint
    │   │   │   ├── package.json
    │   │   │   └── subproject
    │   │   │   │   ├── inner_project
    │   │   │   │       ├── node_modules
    │   │   │   │       │   └── .bin
    │   │   │   │       │   │   └── tsc
    │   │   │   │       └── package.json
    │   │   │   │   ├── node_modules
    │   │   │   │       └── .bin
    │   │   │   │       │   └── rsvp
    │   │   │   │   └── package.json
    │   │   ├── no_toolchain
    │   │   │   └── package.json
    │   │   └── yarn
    │   │   │   ├── pnp-cjs
    │   │   │       ├── .pnp.cjs
    │   │   │       └── package.json
    │   │   │   ├── pnp-js
    │   │   │       ├── .pnp.js
    │   │   │       └── package.json
    │   │   │   └── yarnrc-yml
    │   │   │       ├── .yarnrc.yml
    │   │   │       └── package.json
    │   └── src
    │   │   ├── command.rs
    │   │   ├── error
    │   │       ├── kind.rs
    │   │       ├── mod.rs
    │   │       └── reporter.rs
    │   │   ├── event.rs
    │   │   ├── fs.rs
    │   │   ├── hook
    │   │       ├── mod.rs
    │   │       ├── serial.rs
    │   │       └── tool.rs
    │   │   ├── inventory.rs
    │   │   ├── layout
    │   │       ├── mod.rs
    │   │       ├── unix.rs
    │   │       └── windows.rs
    │   │   ├── lib.rs
    │   │   ├── log.rs
    │   │   ├── monitor.rs
    │   │   ├── platform
    │   │       ├── image.rs
    │   │       ├── mod.rs
    │   │       ├── system.rs
    │   │       └── tests.rs
    │   │   ├── project
    │   │       ├── mod.rs
    │   │       ├── serial.rs
    │   │       └── tests.rs
    │   │   ├── run
    │   │       ├── binary.rs
    │   │       ├── executor.rs
    │   │       ├── mod.rs
    │   │       ├── node.rs
    │   │       ├── npm.rs
    │   │       ├── npx.rs
    │   │       ├── parser.rs
    │   │       ├── pnpm.rs
    │   │       └── yarn.rs
    │   │   ├── session.rs
    │   │   ├── shim.rs
    │   │   ├── signal.rs
    │   │   ├── style.rs
    │   │   ├── sync.rs
    │   │   ├── tool
    │   │       ├── mod.rs
    │   │       ├── node
    │   │       │   ├── fetch.rs
    │   │       │   ├── metadata.rs
    │   │       │   ├── mod.rs
    │   │       │   └── resolve.rs
    │   │       ├── npm
    │   │       │   ├── fetch.rs
    │   │       │   ├── mod.rs
    │   │       │   └── resolve.rs
    │   │       ├── package
    │   │       │   ├── configure.rs
    │   │       │   ├── install.rs
    │   │       │   ├── manager.rs
    │   │       │   ├── metadata.rs
    │   │       │   ├── mod.rs
    │   │       │   └── uninstall.rs
    │   │       ├── pnpm
    │   │       │   ├── fetch.rs
    │   │       │   ├── mod.rs
    │   │       │   └── resolve.rs
    │   │       ├── registry.rs
    │   │       ├── serial.rs
    │   │       └── yarn
    │   │       │   ├── fetch.rs
    │   │       │   ├── metadata.rs
    │   │       │   ├── mod.rs
    │   │       │   └── resolve.rs
    │   │   ├── toolchain
    │   │       ├── mod.rs
    │   │       └── serial.rs
    │   │   └── version
    │   │       ├── mod.rs
    │   │       └── serial.rs
    ├── volta-layout-macro
    │   ├── Cargo.toml
    │   └── src
    │   │   ├── ast.rs
    │   │   ├── ir.rs
    │   │   └── lib.rs
    ├── volta-layout
    │   ├── Cargo.toml
    │   └── src
    │   │   ├── lib.rs
    │   │   ├── macros.rs
    │   │   ├── v0.rs
    │   │   ├── v1.rs
    │   │   ├── v2.rs
    │   │   ├── v3.rs
    │   │   └── v4.rs
    └── volta-migrate
    │   ├── Cargo.toml
    │   └── src
    │       ├── empty.rs
    │       ├── lib.rs
    │       ├── v0.rs
    │       ├── v1.rs
    │       ├── v2.rs
    │       ├── v3.rs
    │       ├── v3
    │           └── config.rs
    │       └── v4.rs
├── dev
    ├── package.json
    ├── rpm
    │   ├── build-rpm.sh
    │   └── volta.spec
    └── unix
    │   ├── SHASUMS256.txt
    │   ├── boot-install.sh
    │   ├── build.sh
    │   ├── install.sh.in
    │   ├── release.sh
    │   ├── test-events
    │   ├── tests
    │       └── install-script.bats
    │   ├── volta-install-legacy.sh
    │   └── volta-install.sh
├── rust-toolchain.toml
├── src
    ├── cli.rs
    ├── command
    │   ├── completions.rs
    │   ├── fetch.rs
    │   ├── install.rs
    │   ├── list
    │   │   ├── human.rs
    │   │   ├── mod.rs
    │   │   ├── plain.rs
    │   │   └── toolchain.rs
    │   ├── mod.rs
    │   ├── pin.rs
    │   ├── run.rs
    │   ├── setup.rs
    │   ├── uninstall.rs
    │   ├── use.rs
    │   └── which.rs
    ├── common.rs
    ├── main.rs
    ├── volta-migrate.rs
    └── volta-shim.rs
├── tests
    ├── acceptance
    │   ├── corrupted_download.rs
    │   ├── direct_install.rs
    │   ├── direct_uninstall.rs
    │   ├── execute_binary.rs
    │   ├── hooks.rs
    │   ├── main.rs
    │   ├── merged_platform.rs
    │   ├── migrations.rs
    │   ├── run_shim_directly.rs
    │   ├── support
    │   │   ├── events_helpers.rs
    │   │   ├── mod.rs
    │   │   └── sandbox.rs
    │   ├── verbose_errors.rs
    │   ├── volta_bypass.rs
    │   ├── volta_install.rs
    │   ├── volta_pin.rs
    │   ├── volta_run.rs
    │   └── volta_uninstall.rs
    ├── fixtures
    │   ├── cli-dist-2.4.159.tgz
    │   ├── cli-dist-3.12.99.tgz
    │   ├── cli-dist-3.2.42.tgz
    │   ├── cli-dist-3.7.71.tgz
    │   ├── node-v0.0.1-darwin-x64.tar.gz
    │   ├── node-v0.0.1-linux-arm64.tar.gz
    │   ├── node-v0.0.1-linux-x64.tar.gz
    │   ├── node-v0.0.1-win-x64.zip
    │   ├── node-v0.0.1-win-x86.zip
    │   ├── node-v10.99.1040-darwin-x64.tar.gz
    │   ├── node-v10.99.1040-linux-arm64.tar.gz
    │   ├── node-v10.99.1040-linux-x64.tar.gz
    │   ├── node-v10.99.1040-win-x64.zip
    │   ├── node-v10.99.1040-win-x86.zip
    │   ├── node-v6.19.62-darwin-x64.tar.gz
    │   ├── node-v6.19.62-linux-arm64.tar.gz
    │   ├── node-v6.19.62-linux-x64.tar.gz
    │   ├── node-v6.19.62-win-x64.zip
    │   ├── node-v6.19.62-win-x86.zip
    │   ├── node-v8.9.10-darwin-x64.tar.gz
    │   ├── node-v8.9.10-linux-arm64.tar.gz
    │   ├── node-v8.9.10-linux-x64.tar.gz
    │   ├── node-v8.9.10-win-x64.zip
    │   ├── node-v8.9.10-win-x86.zip
    │   ├── node-v9.27.6-darwin-x64.tar.gz
    │   ├── node-v9.27.6-linux-arm64.tar.gz
    │   ├── node-v9.27.6-linux-x64.tar.gz
    │   ├── node-v9.27.6-win-x64.zip
    │   ├── node-v9.27.6-win-x86.zip
    │   ├── npm-1.2.3.tgz
    │   ├── npm-4.5.6.tgz
    │   ├── npm-8.1.5.tgz
    │   ├── pnpm-0.0.1.tgz
    │   ├── pnpm-6.34.0.tgz
    │   ├── pnpm-7.7.1.tgz
    │   ├── volta-test-1.0.0.tgz
    │   ├── yarn-0.0.1.tgz
    │   ├── yarn-1.12.99.tgz
    │   ├── yarn-1.2.42.tgz
    │   ├── yarn-1.4.159.tgz
    │   └── yarn-1.7.71.tgz
    └── smoke
    │   ├── autodownload.rs
    │   ├── direct_install.rs
    │   ├── direct_upgrade.rs
    │   ├── main.rs
    │   ├── npm_link.rs
    │   ├── package_migration.rs
    │   ├── support
    │       ├── mod.rs
    │       └── temp_project.rs
    │   ├── volta_fetch.rs
    │   ├── volta_install.rs
    │   └── volta_run.rs
├── volta.iml
├── volta.png
└── wix
    ├── License.rtf
    ├── main.wxs
    ├── shim.cmd
    └── volta.ico


/.cargo/config.toml:
--------------------------------------------------------------------------------
1 | [target.x86_64-pc-windows-msvc]
2 | rustflags = ["-C", "target-feature=+crt-static"]
3 | [target.aarch64-pc-windows-msvc]
4 | rustflags = ["-C", "target-feature=+crt-static"]
5 | 


--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
 1 | version: 2
 2 | updates:
 3 |   - package-ecosystem: "cargo"
 4 |     open-pull-requests-limit: 5
 5 |     schedule:
 6 |       interval: "daily"
 7 |     directories:
 8 |       - "/"
 9 |       - "/crates/*"
10 | 


--------------------------------------------------------------------------------
/.github/workflows/api-docs.yml:
--------------------------------------------------------------------------------
 1 | on:
 2 |   push:
 3 |     branches:
 4 |       - main
 5 | 
 6 | name: API Docs
 7 | 
 8 | jobs:
 9 |   publish:
10 |     name: Build and publish
11 |     runs-on: ubuntu-latest
12 |     steps:
13 |       - name: Check out code
14 |         uses: actions/checkout@v4
15 |         with:
16 |           persist-credentials: false
17 |       - name: Set up cargo
18 |         uses: actions-rust-lang/setup-rust-toolchain@v1
19 |       - name: Cargo Cache
20 |         uses: Swatinem/rust-cache@v2
21 |       - name: Build docs
22 |         run: |
23 |           cargo doc --all --features cross-platform-docs --no-deps --document-private-items
24 |       - name: Prepare docs for publication
25 |         run: |
26 |           mkdir -p publish
27 |           mv target/doc publish/main
28 |           echo '<!doctype html><a href="volta">volta</a>' > publish/main/index.html
29 |           echo '<!doctype html><a href="main">main</a>' > publish/index.html
30 |       - name: Publish docs to GitHub pages
31 |         uses: JamesIves/github-pages-deploy-action@releases/v3
32 |         with:
33 |           COMMIT_MESSAGE: Publishing GitHub Pages
34 |           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
35 |           BRANCH: gh-pages
36 |           FOLDER: publish
37 |           SINGLE_COMMIT: true
38 | 


--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
 1 | on:
 2 |   push:
 3 |     branches:
 4 |       - main
 5 |   pull_request:
 6 |     branches:
 7 |       - main
 8 | 
 9 | name: Test
10 | 
11 | jobs:
12 |   tests:
13 |     strategy:
14 |       matrix:
15 |         os:
16 |           - ubuntu
17 |           - macos
18 |           - windows
19 |     name: Acceptance Tests (${{ matrix.os }})
20 |     runs-on: ${{ matrix.os }}-latest
21 |     env:
22 |       RUST_BACKTRACE: full
23 |     steps:
24 |       - name: Check out code
25 |         uses: actions/checkout@v4
26 |       - name: Set up cargo
27 |         uses: actions-rust-lang/setup-rust-toolchain@v1
28 |       - name: Run tests
29 |         run: |
30 |           cargo test --all --features mock-network
31 |       - name: Lint with clippy
32 |         run: cargo clippy
33 |       - name: Lint tests with clippy
34 |         run: |
35 |           cargo clippy --tests --features mock-network
36 | 
37 |   smoke-tests:
38 |     name: Smoke Tests
39 |     runs-on: macos-latest
40 |     env:
41 |       RUST_BACKTRACE: full
42 |     steps:
43 |       - name: Check out code
44 |         uses: actions/checkout@v4
45 |       - name: Set up cargo
46 |         uses: actions-rust-lang/setup-rust-toolchain@v1
47 |       - name: Run tests
48 |         run: |
49 |           cargo test --test smoke --features smoke-tests -- --test-threads 1
50 | 
51 |   shell-tests:
52 |     name: Shell Script Tests
53 |     runs-on: ubuntu-latest
54 |     steps:
55 |       - name: Setup BATS
56 |         run: sudo npm install -g bats
57 |       - name: Check out code
58 |         uses: actions/checkout@v4
59 |       - name: Run tests
60 |         run: bats dev/unix/tests/
61 | 
62 |   check-formatting:
63 |     name: Check code formatting
64 |     runs-on: ubuntu-latest
65 |     steps:
66 |       - name: Check out code
67 |         uses: actions/checkout@v4
68 |       - name: Set up cargo
69 |         uses: actions-rust-lang/setup-rust-toolchain@v1
70 |       - name: Run check
71 |         run: |
72 |           cargo fmt --all --quiet -- --check
73 | 
74 |   validate-installer-checksum:
75 |     name: Validate installer checksum
76 |     runs-on: ubuntu-latest
77 |     steps:
78 |       - name: Check out code
79 |         uses: actions/checkout@v4
80 |       - name: Run check
81 |         run: |
82 |           cd dev/unix
83 |           sha256sum --check SHASUMS256.txt
84 | 


--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
 1 | /target/
 2 | **/*.rs.bk
 3 | Notion.msi
 4 | Volta.msi
 5 | dev/windows/*.log
 6 | dev/windows/Notion.wixobj
 7 | dev/windows/Volta.wixobj
 8 | dev/windows/Notion.wixpdb
 9 | dev/windows/Volta.wixpdb
10 | dev/unix/install.sh
11 | /.idea/
12 | /rls/
13 | /rls*
14 | 
15 | 
16 | # Created by https://www.gitignore.io/api/intellij (and then modified heavily)
17 | 
18 | ### Intellij ###
19 | # Ignore all IDEA files. This means you may have to rebuild the project on your
20 | # new machines at times, but avoids checking in a bunch of files which are not
21 | # generally relevant to other developers.
22 | .idea
23 | 
24 | # CMake
25 | cmake-build-*/
26 | 
27 | # File-based project format
28 | *.iws
29 | 
30 | # IntelliJ
31 | out/
32 | 
33 | # mpeltonen/sbt-idea plugin
34 | .idea_modules/
35 | 
36 | # JIRA plugin
37 | atlassian-ide-plugin.xml
38 | 
39 | # Crashlytics plugin (for Android Studio and IntelliJ)
40 | com_crashlytics_export_strings.xml
41 | crashlytics.properties
42 | crashlytics-build.properties
43 | fabric.properties
44 | 
45 | # End of https://www.gitignore.io/api/intellij
46 | 


--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "version": "0.2.0",
 3 |   "configurations": [
 4 |     {
 5 |       "type": "lldb",
 6 |       "request": "launch",
 7 |       "name": "Cargo run volta core",
 8 |       "cargo": {
 9 |         "args": ["run", "--bin", "volta"],
10 |         "filter": {
11 |           "kind": "bin",
12 |           "name": "volta"
13 |         }
14 |       },
15 |       "program": "${cargo:program}",
16 |       "args": [],
17 |       "sourceLanguages": ["rust"]
18 |     },
19 |     {
20 |       "type": "lldb",
21 |       "request": "launch",
22 |       "name": "Cargo test volta core",
23 |       "cargo": {
24 |         "args": [
25 |           "test",
26 |           "--lib",
27 |           "--no-run",
28 |           "--package",
29 |           "volta-core",
30 |           "--",
31 |           "--test-threads",
32 |           "1"
33 |         ],
34 |         "filter": {
35 |           "kind": "lib",
36 |           "name": "volta-core"
37 |         }
38 |       },
39 |       "program": "${cargo:program}",
40 |       "args": [],
41 |       "sourceLanguages": ["rust"]
42 |     }
43 |   ]
44 | }
45 | 


--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 |   "editor.formatOnSave": true
3 | }
4 | 


--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
 1 | # Contributor Covenant Code of Conduct
 2 | 
 3 | ## Our Pledge
 4 | 
 5 | In the interest of fostering an open and welcoming environment, we as
 6 | contributors and maintainers pledge to making participation in our project and
 7 | our community a harassment-free experience for everyone, regardless of age, body
 8 | size, disability, ethnicity, gender identity and expression, level of experience,
 9 | nationality, personal appearance, race, religion, or sexual identity and
10 | orientation.
11 | 
12 | ## Our Standards
13 | 
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 | 
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 | 
23 | Examples of unacceptable behavior by participants include:
24 | 
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 |   advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 |   address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 |   professional setting
33 | 
34 | ## Our Responsibilities
35 | 
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 | 
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 | 
46 | ## Scope
47 | 
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 | 
55 | ## Enforcement
56 | 
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at david.herman@gmail.com. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 | 
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 | 
68 | ## Attribution
69 | 
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 | 
73 | [homepage]: https://www.contributor-covenant.org
74 | 


--------------------------------------------------------------------------------
/COMPATIBILITY.md:
--------------------------------------------------------------------------------
 1 | # Compatibility
 2 | 
 3 | Volta currently tests against the following platforms, and will treat it as a breaking change to drop support for them:
 4 | 
 5 | - macOS
 6 |     - x86-64
 7 |     - Apple Silicon
 8 | - Linux x86-64
 9 | - Windows x86-64
10 | 
11 | We compile release artifacts compatible with the following, and likewise will treat it as a breaking change to drop support for them:
12 | 
13 | - macOS v11
14 | - RHEL and CentOS v7
15 | - Windows 10
16 | 
17 | In general, Volta should build and run against any other modern hardware and operating system supported by stable Rust, and we will make a best effort not to break them. However, we do *not* include them in our SemVer guarantees or test against them.
18 | 


--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | Please see https://docs.volta.sh/contributing/
2 | 


--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
 1 | [package]
 2 | name = "volta"
 3 | version = "2.0.2"
 4 | authors = ["David Herman <david.herman@gmail.com>", "Charles Pierce <cpierce.grad@gmail.com>"]
 5 | license = "BSD-2-Clause"
 6 | repository = "https://github.com/volta-cli/volta"
 7 | edition = "2021"
 8 | 
 9 | [features]
10 | cross-platform-docs = ["volta-core/cross-platform-docs"]
11 | mock-network = ["mockito", "volta-core/mock-network"]
12 | volta-dev = []
13 | smoke-tests = []
14 | 
15 | [[bin]]
16 | name = "volta-shim"
17 | path = "src/volta-shim.rs"
18 | 
19 | [[bin]]
20 | name = "volta-migrate"
21 | path = "src/volta-migrate.rs"
22 | 
23 | [profile.release]
24 | lto = "fat"
25 | codegen-units = 1
26 | 
27 | [dependencies]
28 | volta-core = { path = "crates/volta-core" }
29 | serde = { version = "1.0", features = ["derive"] }
30 | serde_json = "1.0.135"
31 | once_cell = "1.19.0"
32 | log = { version = "0.4", features = ["std"] }
33 | node-semver = "2"
34 | clap = { version = "4.5.24", features = ["color", "derive", "wrap_help"] }
35 | clap_complete = "4.5.46"
36 | mockito = { version = "0.31.1", optional = true }
37 | textwrap = "0.16.1"
38 | which = "7.0.1"
39 | dirs = "6.0.0"
40 | volta-migrate = { path = "crates/volta-migrate" }
41 | 
42 | [target.'cfg(windows)'.dependencies]
43 | winreg = "0.53.0"
44 | 
45 | [dev-dependencies]
46 | hamcrest2 = "0.3.0"
47 | envoy = "0.1.3"
48 | ci_info = "0.14.14"
49 | headers = "0.4"
50 | cfg-if = "1.0"
51 | test-support = { path = "crates/test-support" }
52 | 
53 | [workspace]
54 | 


--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
 1 | BSD 2-CLAUSE LICENSE
 2 | 
 3 | Copyright (c) 2017, The Volta Contributors.
 4 | All rights reserved.
 5 | 
 6 | This product includes:
 7 | 
 8 | Contributions from LinkedIn Corporation
 9 | Copyright (c) 2017, LinkedIn Corporation.
10 | 
11 | Redistribution and use in source and binary forms, with or without
12 | modification, are permitted provided that the following conditions are met:
13 | 
14 | 1. Redistributions of source code must retain the above copyright notice, this
15 |    list of conditions and the following disclaimer.
16 | 2. Redistributions in binary form must reproduce the above copyright notice,
17 |    this list of conditions and the following disclaimer in the documentation
18 |    and/or other materials provided with the distribution.
19 | 
20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
21 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
24 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
26 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
27 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 | 
31 | The views and conclusions contained in the software and documentation are those
32 | of the authors and should not be interpreted as representing official policies,
33 | either expressed or implied, of the FreeBSD Project.
34 | 


--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
 1 | <p align="center">
 2 |   <a href="https://www.volta.sh/">
 3 |     <img alt="Volta" src="./volta.png?raw=true" width="360">
 4 |   </a>
 5 | </p>
 6 | 
 7 | <p align="center">
 8 |   The Hassle-Free JavaScript Tool Manager
 9 | </p>
10 | 
11 | <p align="center">
12 |   <img alt="Production Build Status" src="https://github.com/volta-cli/volta/workflows/Production/badge.svg" />
13 |   <a href="https://github.com/volta-cli/volta/actions?query=workflow%3ATest">
14 |     <img alt="Test Status" src="https://github.com/volta-cli/volta/workflows/Test/badge.svg" />
15 |   </a>
16 | </p>
17 | 
18 | **Fast:** Install and run any JS tool quickly and seamlessly! Volta is built in Rust and ships as a snappy static binary.
19 | 
20 | **Reliable:** Ensure everyone in your project has the same tools—without interfering with their workflow.
21 | 
22 | **Universal:** No matter the package manager, Node runtime, or OS, one command is all you need: `volta install`.
23 | 
24 | ## Features
25 | 
26 | - Speed 🚀
27 | - Seamless, per-project version switching
28 | - Cross-platform support, including Windows and all Unix shells
29 | - Support for multiple package managers
30 | - Stable tool installation—no reinstalling on every Node upgrade!
31 | - Extensibility hooks for site-specific customization
32 | 
33 | ## Installing Volta
34 | 
35 | Read the [Getting Started Guide](https://docs.volta.sh/guide/getting-started) on our website for detailed instructions on how to install Volta.
36 | 
37 | ## Using Volta
38 | 
39 | Read the [Understanding Volta Guide](https://docs.volta.sh/guide/understanding) on our website for detailed instructions on how to use Volta.
40 | 
41 | ## Contributing to Volta
42 | 
43 | Contributions are always welcome, no matter how large or small. Substantial feature ideas should be proposed as an [RFC](https://github.com/volta-cli/rfcs). Before contributing, please read the [code of conduct](CODE_OF_CONDUCT.md).
44 | 
45 | See the [Contributing Guide](https://docs.volta.sh/contributing/) on our website for detailed instructions on how to contribute to Volta.
46 | 
47 | ## Who is using Volta?
48 | 
49 | <table>
50 |   <tbody>
51 |     <tr>
52 |       <td align="center">
53 |         <a href="https://github.com/microsoft/TypeScript" target="_blank">
54 |           <img src="https://raw.githubusercontent.com/microsoft/TypeScript-Website/v2/packages/typescriptlang-org/static/branding/ts-logo-512.svg" alt="TypeScript" width="100" height="100">
55 |         </a>
56 |       </td>
57 |       <td align="center">
58 |         <a href="https://github.com/getsentry/sentry-javascript" target="_blank">
59 |           <img src="https://avatars.githubusercontent.com/u/1396951?s=100" alt="Sentry" width="100" height="100">
60 |         </a>
61 |       </td>
62 |     </tr>
63 |   </tbody>
64 | </table>
65 | 
66 | See [here](https://sourcegraph.com/search?q=context:global+%22volta%22+file:package.json&patternType=literal) for more Volta users.
67 | 


--------------------------------------------------------------------------------
/ci/build-linux.sh:
--------------------------------------------------------------------------------
 1 | #!/bin/bash
 2 | 
 3 | set -e
 4 | 
 5 | # Activate the upgraded versions of GCC and binutils
 6 | # See https://linux.web.cern.ch/centos7/docs/softwarecollections/#inst
 7 | source /opt/rh/devtoolset-8/enable
 8 | 
 9 | echo "Building Volta"
10 | 
11 | cargo build --release
12 | 
13 | echo "Packaging Binaries"
14 | 
15 | cd target/release
16 | tar -zcvf "$1.tar.gz" volta volta-shim volta-migrate
17 | 


--------------------------------------------------------------------------------
/ci/build-macos.sh:
--------------------------------------------------------------------------------
 1 | #!/bin/bash
 2 | 
 3 | set -e
 4 | 
 5 | echo "Building Volta"
 6 | 
 7 | MACOSX_DEPLOYMENT_TARGET=11.0 cargo build --release --target=aarch64-apple-darwin
 8 | MACOSX_DEPLOYMENT_TARGET=11.0 cargo build --release --target=x86_64-apple-darwin
 9 | 
10 | echo "Packaging Binaries"
11 | 
12 | mkdir -p target/universal-apple-darwin/release
13 | 
14 | for exe in volta volta-shim volta-migrate
15 | do
16 |     lipo -create -output target/universal-apple-darwin/release/$exe target/x86_64-apple-darwin/release/$exe target/aarch64-apple-darwin/release/$exe
17 | done
18 | 
19 | cd target/universal-apple-darwin/release
20 | 
21 | tar -zcvf "$1.tar.gz" volta volta-shim volta-migrate
22 | 


--------------------------------------------------------------------------------
/ci/docker/Dockerfile:
--------------------------------------------------------------------------------
 1 | FROM cern/cc7-base
 2 | 
 3 | # This repo file references a URL that is no longer valid. It also isn't used by the build
 4 | # toolchain, so we can safely remove it entirely
 5 | RUN rm /etc/yum.repos.d/epel.repo
 6 | 
 7 | # https://linux.web.cern.ch/centos7/docs/softwarecollections/#inst
 8 | # Tools needed for the build and setup process
 9 | RUN yum -y install wget tar
10 | # Fetch the repo information for the devtoolset repo
11 | RUN yum install -y centos-release-scl
12 | # Install more recent GCC and binutils, to allow us to compile
13 | RUN yum install -y devtoolset-8
14 | 
15 | RUN curl https://sh.rustup.rs -sSf | sh -s -- -y
16 | ENV PATH="/root/.cargo/bin:${PATH}"
17 | 


--------------------------------------------------------------------------------
/ci/volta.manifest:
--------------------------------------------------------------------------------
1 | volta
2 | volta-shim
3 | volta-migrate
4 | 


--------------------------------------------------------------------------------
/crates/archive/Cargo.toml:
--------------------------------------------------------------------------------
 1 | [package]
 2 | name = "archive"
 3 | version = "0.1.0"
 4 | authors = ["David Herman <david.herman@gmail.com>"]
 5 | edition = "2021"
 6 | 
 7 | [dependencies]
 8 | flate2 = "1.0"
 9 | tar = "0.4.13"
10 | # Set features manually to drop usage of `time` crate: we do not rely on that
11 | # set of capabilities, and it has a vulnerability. We also don't need to use
12 | # every single compression algorithm feature since we are only downloading
13 | # Node as a zip file
14 | zip_rs = { version = "=2.1.6", package = "zip", default-features = false, features = ["deflate", "bzip2"] }
15 | tee = "0.1.0"
16 | fs-utils = { path = "../fs-utils" }
17 | progress-read = { path = "../progress-read" }
18 | verbatim = "0.1"
19 | cfg-if = "1.0"
20 | headers = "0.4"
21 | thiserror = "2.0.0"
22 | attohttpc = { version = "0.28", default-features = false, features = ["json", "compress", "tls-rustls-native-roots"] }
23 | log = { version = "0.4", features = ["std"] }
24 | 


--------------------------------------------------------------------------------
/crates/archive/fixtures/tarballs/test-file.tar.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/volta-cli/volta/a7384fa4fc7a0eca961032da4d962d94218b5868/crates/archive/fixtures/tarballs/test-file.tar.gz


--------------------------------------------------------------------------------
/crates/archive/fixtures/zips/test-file.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/volta-cli/volta/a7384fa4fc7a0eca961032da4d962d94218b5868/crates/archive/fixtures/zips/test-file.zip


--------------------------------------------------------------------------------
/crates/archive/src/lib.rs:
--------------------------------------------------------------------------------
  1 | //! This crate provides types for fetching and unpacking compressed
  2 | //! archives in tarball or zip format.
  3 | use std::fs::File;
  4 | use std::path::Path;
  5 | 
  6 | use attohttpc::header::HeaderMap;
  7 | use headers::{ContentLength, Header, HeaderMapExt};
  8 | use thiserror::Error;
  9 | 
 10 | mod tarball;
 11 | mod zip;
 12 | 
 13 | pub use crate::tarball::Tarball;
 14 | pub use crate::zip::Zip;
 15 | 
 16 | /// Error type for this crate
 17 | #[derive(Error, Debug)]
 18 | pub enum ArchiveError {
 19 |     #[error("HTTP failure ({0})")]
 20 |     HttpError(attohttpc::StatusCode),
 21 | 
 22 |     #[error("HTTP header '{0}' not found")]
 23 |     MissingHeaderError(&'static attohttpc::header::HeaderName),
 24 | 
 25 |     #[error("unexpected content length in HTTP response: {0}")]
 26 |     UnexpectedContentLengthError(u64),
 27 | 
 28 |     #[error("{0}")]
 29 |     IoError(#[from] std::io::Error),
 30 | 
 31 |     #[error("{0}")]
 32 |     AttohttpcError(#[from] attohttpc::Error),
 33 | 
 34 |     #[error("{0}")]
 35 |     ZipError(#[from] zip_rs::result::ZipError),
 36 | }
 37 | 
 38 | /// Metadata describing whether an archive comes from a local or remote origin.
 39 | #[derive(Copy, Clone)]
 40 | pub enum Origin {
 41 |     Local,
 42 |     Remote,
 43 | }
 44 | 
 45 | pub trait Archive {
 46 |     fn compressed_size(&self) -> u64;
 47 | 
 48 |     /// Unpacks the zip archive to the specified destination folder.
 49 |     fn unpack(
 50 |         self: Box<Self>,
 51 |         dest: &Path,
 52 |         progress: &mut dyn FnMut(&(), usize),
 53 |     ) -> Result<(), ArchiveError>;
 54 | 
 55 |     fn origin(&self) -> Origin;
 56 | }
 57 | 
 58 | cfg_if::cfg_if! {
 59 |     if #[cfg(unix)] {
 60 |         /// Load an archive in the native OS-preferred format from the specified file.
 61 |         ///
 62 |         /// On Windows, the preferred format is zip. On Unixes, the preferred format
 63 |         /// is tarball.
 64 |         pub fn load_native(source: File) -> Result<Box<dyn Archive>, ArchiveError> {
 65 |             Tarball::load(source)
 66 |         }
 67 | 
 68 |         /// Fetch a remote archive in the native OS-preferred format from the specified
 69 |         /// URL and store its results at the specified file path.
 70 |         ///
 71 |         /// On Windows, the preferred format is zip. On Unixes, the preferred format
 72 |         /// is tarball.
 73 |         pub fn fetch_native(url: &str, cache_file: &Path) -> Result<Box<dyn Archive>, ArchiveError> {
 74 |             Tarball::fetch(url, cache_file)
 75 |         }
 76 |     } else if #[cfg(windows)] {
 77 |         /// Load an archive in the native OS-preferred format from the specified file.
 78 |         ///
 79 |         /// On Windows, the preferred format is zip. On Unixes, the preferred format
 80 |         /// is tarball.
 81 |         pub fn load_native(source: File) -> Result<Box<dyn Archive>, ArchiveError> {
 82 |             Zip::load(source)
 83 |         }
 84 | 
 85 |         /// Fetch a remote archive in the native OS-preferred format from the specified
 86 |         /// URL and store its results at the specified file path.
 87 |         ///
 88 |         /// On Windows, the preferred format is zip. On Unixes, the preferred format
 89 |         /// is tarball.
 90 |         pub fn fetch_native(url: &str, cache_file: &Path) -> Result<Box<dyn Archive>, ArchiveError> {
 91 |             Zip::fetch(url, cache_file)
 92 |         }
 93 |     } else {
 94 |         compile_error!("Unsupported OS (expected 'unix' or 'windows').");
 95 |     }
 96 | }
 97 | 
 98 | /// Determines the length of an HTTP response's content in bytes, using
 99 | /// the HTTP `"Content-Length"` header.
100 | fn content_length(headers: &HeaderMap) -> Result<u64, ArchiveError> {
101 |     headers
102 |         .typed_get()
103 |         .map(|ContentLength(v)| v)
104 |         .ok_or_else(|| ArchiveError::MissingHeaderError(ContentLength::name()))
105 | }
106 | 


--------------------------------------------------------------------------------
/crates/archive/src/tarball.rs:
--------------------------------------------------------------------------------
 1 | //! Provides types and functions for fetching and unpacking a Node installation
 2 | //! tarball in Unix operating systems.
 3 | 
 4 | use std::fs::File;
 5 | use std::io::Read;
 6 | use std::path::Path;
 7 | 
 8 | use super::{content_length, Archive, ArchiveError, Origin};
 9 | use flate2::read::GzDecoder;
10 | use fs_utils::ensure_containing_dir_exists;
11 | use progress_read::ProgressRead;
12 | use tee::TeeReader;
13 | 
14 | /// A Node installation tarball.
15 | pub struct Tarball {
16 |     compressed_size: u64,
17 |     data: Box<dyn Read>,
18 |     origin: Origin,
19 | }
20 | 
21 | impl Tarball {
22 |     /// Loads a tarball from the specified file.
23 |     pub fn load(source: File) -> Result<Box<dyn Archive>, ArchiveError> {
24 |         let compressed_size = source.metadata()?.len();
25 |         Ok(Box::new(Tarball {
26 |             compressed_size,
27 |             data: Box::new(source),
28 |             origin: Origin::Local,
29 |         }))
30 |     }
31 | 
32 |     /// Initiate fetching of a tarball from the given URL, returning a
33 |     /// tarball that can be streamed (and that tees its data to a local
34 |     /// file as it streams).
35 |     pub fn fetch(url: &str, cache_file: &Path) -> Result<Box<dyn Archive>, ArchiveError> {
36 |         let (status, headers, response) = attohttpc::get(url).send()?.split();
37 | 
38 |         if !status.is_success() {
39 |             return Err(ArchiveError::HttpError(status));
40 |         }
41 | 
42 |         let compressed_size = content_length(&headers)?;
43 | 
44 |         ensure_containing_dir_exists(&cache_file)?;
45 |         let file = File::create(cache_file)?;
46 |         let data = Box::new(TeeReader::new(response, file));
47 | 
48 |         Ok(Box::new(Tarball {
49 |             compressed_size,
50 |             data,
51 |             origin: Origin::Remote,
52 |         }))
53 |     }
54 | }
55 | 
56 | impl Archive for Tarball {
57 |     fn compressed_size(&self) -> u64 {
58 |         self.compressed_size
59 |     }
60 |     fn unpack(
61 |         self: Box<Self>,
62 |         dest: &Path,
63 |         progress: &mut dyn FnMut(&(), usize),
64 |     ) -> Result<(), ArchiveError> {
65 |         let decoded = GzDecoder::new(ProgressRead::new(self.data, (), progress));
66 |         let mut tarball = tar::Archive::new(decoded);
67 |         tarball.unpack(dest)?;
68 |         Ok(())
69 |     }
70 |     fn origin(&self) -> Origin {
71 |         self.origin
72 |     }
73 | }
74 | 
75 | #[cfg(test)]
76 | pub mod tests {
77 | 
78 |     use crate::tarball::Tarball;
79 |     use std::fs::File;
80 |     use std::path::PathBuf;
81 | 
82 |     fn fixture_path(fixture_dir: &str) -> PathBuf {
83 |         let mut cargo_manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
84 |         cargo_manifest_dir.push("fixtures");
85 |         cargo_manifest_dir.push(fixture_dir);
86 |         cargo_manifest_dir
87 |     }
88 | 
89 |     #[test]
90 |     fn test_load() {
91 |         let mut test_file_path = fixture_path("tarballs");
92 |         test_file_path.push("test-file.tar.gz");
93 |         let test_file = File::open(test_file_path).expect("Couldn't open test file");
94 |         let tarball = Tarball::load(test_file).expect("Failed to load tarball");
95 | 
96 |         assert_eq!(tarball.compressed_size(), 402);
97 |     }
98 | }
99 | 


--------------------------------------------------------------------------------
/crates/archive/src/zip.rs:
--------------------------------------------------------------------------------
  1 | //! Provides types and functions for fetching and unpacking a Node installation
  2 | //! zip file in Windows operating systems.
  3 | 
  4 | use std::fs::File;
  5 | use std::io::Read;
  6 | use std::path::Path;
  7 | 
  8 | use super::{content_length, ArchiveError};
  9 | use fs_utils::ensure_containing_dir_exists;
 10 | use progress_read::ProgressRead;
 11 | use tee::TeeReader;
 12 | use verbatim::PathExt;
 13 | use zip_rs::unstable::stream::ZipStreamReader;
 14 | 
 15 | use super::Archive;
 16 | use super::Origin;
 17 | 
 18 | pub struct Zip {
 19 |     compressed_size: u64,
 20 |     data: Box<dyn Read>,
 21 |     origin: Origin,
 22 | }
 23 | 
 24 | impl Zip {
 25 |     /// Loads a cached Node zip archive from the specified file.
 26 |     pub fn load(source: File) -> Result<Box<dyn Archive>, ArchiveError> {
 27 |         let compressed_size = source.metadata()?.len();
 28 | 
 29 |         Ok(Box::new(Zip {
 30 |             compressed_size,
 31 |             data: Box::new(source),
 32 |             origin: Origin::Local,
 33 |         }))
 34 |     }
 35 | 
 36 |     /// Initiate fetching of a Node zip archive from the given URL, returning
 37 |     /// a `Remote` data source.
 38 |     pub fn fetch(url: &str, cache_file: &Path) -> Result<Box<dyn Archive>, ArchiveError> {
 39 |         let (status, headers, response) = attohttpc::get(url).send()?.split();
 40 | 
 41 |         if !status.is_success() {
 42 |             return Err(ArchiveError::HttpError(status));
 43 |         }
 44 | 
 45 |         let compressed_size = content_length(&headers)?;
 46 | 
 47 |         ensure_containing_dir_exists(&cache_file)?;
 48 |         let file = File::create(cache_file)?;
 49 |         let data = Box::new(TeeReader::new(response, file));
 50 | 
 51 |         Ok(Box::new(Zip {
 52 |             compressed_size,
 53 |             data,
 54 |             origin: Origin::Remote,
 55 |         }))
 56 |     }
 57 | }
 58 | 
 59 | impl Archive for Zip {
 60 |     fn compressed_size(&self) -> u64 {
 61 |         self.compressed_size
 62 |     }
 63 |     fn unpack(
 64 |         self: Box<Self>,
 65 |         dest: &Path,
 66 |         progress: &mut dyn FnMut(&(), usize),
 67 |     ) -> Result<(), ArchiveError> {
 68 |         // Use a verbatim path to avoid the legacy Windows 260 byte path limit.
 69 |         let dest: &Path = &dest.to_verbatim();
 70 |         let zip = ZipStreamReader::new(ProgressRead::new(self.data, (), progress));
 71 |         zip.extract(dest)?;
 72 |         Ok(())
 73 |     }
 74 |     fn origin(&self) -> Origin {
 75 |         self.origin
 76 |     }
 77 | }
 78 | 
 79 | #[cfg(test)]
 80 | pub mod tests {
 81 | 
 82 |     use crate::zip::Zip;
 83 |     use std::fs::File;
 84 |     use std::path::PathBuf;
 85 | 
 86 |     fn fixture_path(fixture_dir: &str) -> PathBuf {
 87 |         let mut cargo_manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
 88 |         cargo_manifest_dir.push("fixtures");
 89 |         cargo_manifest_dir.push(fixture_dir);
 90 |         cargo_manifest_dir
 91 |     }
 92 | 
 93 |     #[test]
 94 |     fn test_load() {
 95 |         let mut test_file_path = fixture_path("zips");
 96 |         test_file_path.push("test-file.zip");
 97 |         let test_file = File::open(test_file_path).expect("Couldn't open test file");
 98 |         let zip = Zip::load(test_file).expect("Failed to load zip file");
 99 | 
100 |         assert_eq!(zip.compressed_size(), 214);
101 |     }
102 | }
103 | 


--------------------------------------------------------------------------------
/crates/fs-utils/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "fs-utils"
3 | version = "0.1.0"
4 | authors = ["Michael Stewart <mikrostew@gmail.com>"]
5 | edition = "2021"
6 | 
7 | [dependencies]
8 | 


--------------------------------------------------------------------------------
/crates/fs-utils/src/lib.rs:
--------------------------------------------------------------------------------
 1 | //! This crate provides utilities for operating on the filesystem.
 2 | 
 3 | use std::fs;
 4 | use std::io;
 5 | use std::path::Path;
 6 | 
 7 | /// This creates the parent directory of the input path, assuming the input path is a file.
 8 | pub fn ensure_containing_dir_exists<P: AsRef<Path>>(path: &P) -> io::Result<()> {
 9 |     path.as_ref()
10 |         .parent()
11 |         .ok_or_else(|| {
12 |             io::Error::new(
13 |                 io::ErrorKind::NotFound,
14 |                 format!(
15 |                     "Could not determine directory information for {}",
16 |                     path.as_ref().display()
17 |                 ),
18 |             )
19 |         })
20 |         .and_then(fs::create_dir_all)
21 | }
22 | 


--------------------------------------------------------------------------------
/crates/progress-read/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "progress-read"
3 | version = "0.1.0"
4 | authors = ["David Herman <david.herman@gmail.com>"]
5 | edition = "2021"
6 | 
7 | [dependencies]
8 | 


--------------------------------------------------------------------------------
/crates/progress-read/src/lib.rs:
--------------------------------------------------------------------------------
 1 | //! This crate provides an adapter for the `std::io::Read` trait to
 2 | //! allow reporting incremental progress to a callback function.
 3 | 
 4 | use std::io::{self, Read, Seek, SeekFrom};
 5 | 
 6 | /// A reader that reports incremental progress while reading.
 7 | pub struct ProgressRead<R: Read, T, F: FnMut(&T, usize) -> T> {
 8 |     source: R,
 9 |     accumulator: T,
10 |     progress: F,
11 | }
12 | 
13 | impl<R: Read, T, F: FnMut(&T, usize) -> T> Read for ProgressRead<R, T, F> {
14 |     /// Read some bytes from the underlying reader into the specified buffer,
15 |     /// and report progress to the progress callback. The progress callback is
16 |     /// passed the current value of the accumulator as its first argument and
17 |     /// the number of bytes read as its second argument. The result of the
18 |     /// progress callback is stored as the updated value of the accumulator,
19 |     /// to be passed to the next invocation of the callback.
20 |     fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
21 |         let len = self.source.read(buf)?;
22 |         let new_accumulator = {
23 |             let progress = &mut self.progress;
24 |             progress(&self.accumulator, len)
25 |         };
26 |         self.accumulator = new_accumulator;
27 |         Ok(len)
28 |     }
29 | }
30 | 
31 | impl<R: Read, T, F: FnMut(&T, usize) -> T> ProgressRead<R, T, F> {
32 |     /// Construct a new progress reader with the specified underlying reader,
33 |     /// initial value for an accumulator, and progress callback.
34 |     pub fn new(source: R, init: T, progress: F) -> ProgressRead<R, T, F> {
35 |         ProgressRead {
36 |             source,
37 |             accumulator: init,
38 |             progress,
39 |         }
40 |     }
41 | }
42 | 
43 | impl<R: Read + Seek, T, F: FnMut(&T, usize) -> T> Seek for ProgressRead<R, T, F> {
44 |     fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
45 |         self.source.seek(pos)
46 |     }
47 | }
48 | 


--------------------------------------------------------------------------------
/crates/test-support/Cargo.toml:
--------------------------------------------------------------------------------
 1 | [package]
 2 | name = "test-support"
 3 | version = "0.1.0"
 4 | authors = ["David Herman <david.herman@gmail.com>"]
 5 | edition = "2021"
 6 | 
 7 | [dependencies]
 8 | hamcrest2 = "0.3.0"
 9 | serde_json = { version = "1.0.135" }
10 | thiserror = "2.0.9"
11 | 


--------------------------------------------------------------------------------
/crates/test-support/src/lib.rs:
--------------------------------------------------------------------------------
 1 | //! Utilities to use with acceptance tests in Volta.
 2 | 
 3 | #[macro_export]
 4 | macro_rules! ok_or_panic {
 5 |     { $e:expr } => {
 6 |         match $e {
 7 |             Ok(x) => x,
 8 |             Err(err) => panic!("{} failed with {}", stringify!($e), err),
 9 |         }
10 |     };
11 | }
12 | 
13 | pub mod matchers;
14 | pub mod paths;
15 | pub mod process;
16 | 


--------------------------------------------------------------------------------
/crates/validate-npm-package-name/Cargo.toml:
--------------------------------------------------------------------------------
 1 | [package]
 2 | name = "validate-npm-package-name"
 3 | version = "0.1.0"
 4 | authors = ["Chris Krycho <hello@chriskrycho.com>"]
 5 | edition = "2021"
 6 | 
 7 | [lib]
 8 | 
 9 | [dependencies]
10 | once_cell = "1.19.0"
11 | percent-encoding = "2.1.0"
12 | regex = "1.1.6"
13 | 


--------------------------------------------------------------------------------
/crates/volta-core/Cargo.toml:
--------------------------------------------------------------------------------
 1 | [package]
 2 | name = "volta-core"
 3 | version = "0.1.0"
 4 | authors = ["David Herman <david.herman@gmail.com>"]
 5 | edition = "2021"
 6 | 
 7 | [features]
 8 | mock-network = ["mockito"]
 9 | # The `cross-platform-docs` feature flag is used for generating API docs for
10 | # multiple platforms in one build.
11 | # See ci/publish-docs.yml for an example of how it's enabled.
12 | # See volta-core::path for an example of where it's used.
13 | cross-platform-docs = []
14 | 
15 | [dependencies]
16 | terminal_size = "0.4.1"
17 | indicatif = "0.17.9"
18 | console = ">=0.11.3, <1.0.0"
19 | readext = "0.1.0"
20 | serde_json = { version = "1.0.135", features = ["preserve_order"] }
21 | serde = { version = "1.0.217", features = ["derive"] }
22 | archive = { path = "../archive" }
23 | node-semver = "2"
24 | cmdline_words_parser = "0.2.1"
25 | fs-utils = { path = "../fs-utils" }
26 | cfg-if = "1.0"
27 | tempfile = "3.14.0"
28 | os_info = "3.9.2"
29 | detect-indent = "0.1"
30 | envoy = "0.1.3"
31 | mockito = { version = "0.31.1", optional = true }
32 | regex = "1.11.1"
33 | dirs = "6.0.0"
34 | # We manually configure the feature list here because 0.4.16 includes the
35 | # `oldtime` feature by default to avoid a breaking change. Additionally, using
36 | # the feature list explicitly lets us drop the `wasmbind` feature, which we do
37 | # not need.
38 | chrono = { version = "0.4.39", default-features = false, features = ["alloc", "std", "clock"] }
39 | validate-npm-package-name = { path = "../validate-npm-package-name" }
40 | textwrap = "0.16.1"
41 | log = { version = "0.4", features = ["std"] }
42 | ctrlc = "3.4.5"
43 | walkdir = "2.5.0"
44 | volta-layout = { path = "../volta-layout" }
45 | once_cell = "1.19.0"
46 | dunce = "1.0.5"
47 | ci_info = "0.14.14"
48 | httpdate = "1"
49 | headers = "0.4"
50 | attohttpc = { version = "0.28", default-features = false, features = ["json", "compress", "tls-rustls-native-roots"] }
51 | chain-map = "0.1.0"
52 | indexmap = "2.7.0"
53 | retry = "2"
54 | fs2 = "0.4.3"
55 | which = "7.0.1"
56 | 
57 | [target.'cfg(windows)'.dependencies]
58 | winreg = "0.55.0"
59 | junction = "1.2.0"
60 | 


--------------------------------------------------------------------------------
/crates/volta-core/fixtures/basic/node_modules/.bin/rsvp:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | 
3 | // for testing
4 | console.log("running the local rsvp binary...");
5 | 
6 | 


--------------------------------------------------------------------------------
/crates/volta-core/fixtures/basic/node_modules/@namespace/some-dep/package.json:
--------------------------------------------------------------------------------
1 | {
2 |   "name": "some-dep",
3 |   "version": "1.1.1",
4 |   "description": "Dependency that doesn't have the 'bin' field",
5 |   "license": "BSD",
6 |   "dependencies": {},
7 |   "devDependencies": {}
8 | }
9 | 


--------------------------------------------------------------------------------
/crates/volta-core/fixtures/basic/node_modules/@namespaced/something-else/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "basic-project",
 3 |   "version": "0.0.7",
 4 |   "description": "Testing that manifest pulls things out of this correctly",
 5 |   "license": "To Kill",
 6 |   "dependencies": {
 7 |     "rsvp": "^3.5.0"
 8 |   },
 9 |   "devDependencies": {
10 |     "eslint": "~4.8.0"
11 |   },
12 |   "bin": {
13 |     "bin-1": "./lib/cli.js",
14 |     "bin-2": "./lib/cli.js"
15 |   }
16 | }
17 | 


--------------------------------------------------------------------------------
/crates/volta-core/fixtures/basic/node_modules/eslint/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "eslint-project",
 3 |   "version": "4.8.0",
 4 |   "description": "Mock eslint",
 5 |   "license": "To Ill",
 6 |   "bin": {
 7 |     "eslint": "./bin/eslint.js"
 8 |   },
 9 |   "dependencies": {},
10 |   "devDependencies": {}
11 | }
12 | 


--------------------------------------------------------------------------------
/crates/volta-core/fixtures/basic/node_modules/rsvp/package.json:
--------------------------------------------------------------------------------
1 | {
2 |   "name": "rsvp",
3 |   "version": "3.5.0",
4 |   "description": "Mock rsvp",
5 |   "license": "MIT",
6 |   "bin": "./bin/rsvp.js",
7 |   "dependencies": {}
8 | }
9 | 


--------------------------------------------------------------------------------
/crates/volta-core/fixtures/basic/node_modules/typescript/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "typescript",
 3 |   "author": "Microsoft Corp.",
 4 |   "homepage": "http://typescriptlang.org/",
 5 |   "version": "2.8.3",
 6 |   "license": "Apache-2.0",
 7 |   "description": "Mock Typescript package",
 8 |   "bin": {
 9 |     "tsc": "./bin/tsc",
10 |     "tsserver": "./bin/tsserver"
11 |   },
12 |   "dependencies": {},
13 |   "devDependencies": {}
14 | }
15 | 


--------------------------------------------------------------------------------
/crates/volta-core/fixtures/basic/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "basic-project",
 3 |   "version": "0.0.7",
 4 |   "description": "Testing that manifest pulls things out of this correctly",
 5 |   "license": "To Kill",
 6 |   "dependencies": {
 7 |     "@namespace/some-dep": "0.2.4",
 8 |     "rsvp": "^3.5.0"
 9 |   },
10 |   "devDependencies": {
11 |     "@namespaced/something-else": "^6.3.7",
12 |     "eslint": "~4.8.0"
13 |   },
14 |   "volta": {
15 |     "node": "6.11.1",
16 |     "npm": "3.10.10",
17 |     "yarn": "1.2.0"
18 |   }
19 | }
20 | 


--------------------------------------------------------------------------------
/crates/volta-core/fixtures/basic/rsvp:
--------------------------------------------------------------------------------
1 | ../../../../target/debug/launchbin


--------------------------------------------------------------------------------
/crates/volta-core/fixtures/basic/subdir/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/volta-cli/volta/a7384fa4fc7a0eca961032da4d962d94218b5868/crates/volta-core/fixtures/basic/subdir/.gitkeep


--------------------------------------------------------------------------------
/crates/volta-core/fixtures/cycle-1/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "cycle-1-project",
 3 |   "version": "0.0.1",
 4 |   "description": "Testing that project correctly detects a cycle between this and volta.json",
 5 |   "license": "To Kill",
 6 |   "volta": {
 7 |     "extends": "./volta.json"
 8 |   }
 9 | }
10 | 


--------------------------------------------------------------------------------
/crates/volta-core/fixtures/cycle-1/volta.json:
--------------------------------------------------------------------------------
1 | {
2 |   "volta": {
3 |     "extends": "./package.json"
4 |   }
5 | }
6 | 


--------------------------------------------------------------------------------
/crates/volta-core/fixtures/cycle-2/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "cycle-2-project",
 3 |   "version": "0.0.1",
 4 |   "description": "Testing that project correctly detects a cycle between workspace-1.json and workspace-2.json",
 5 |   "license": "To Kill",
 6 |   "volta": {
 7 |     "extends": "./workspace-1.json"
 8 |   }
 9 | }
10 | 


--------------------------------------------------------------------------------
/crates/volta-core/fixtures/cycle-2/workspace-1.json:
--------------------------------------------------------------------------------
1 | {
2 |   "volta": {
3 |     "extends": "./workspace-2.json"
4 |   }
5 | }
6 | 


--------------------------------------------------------------------------------
/crates/volta-core/fixtures/cycle-2/workspace-2.json:
--------------------------------------------------------------------------------
1 | {
2 |   "volta": {
3 |     "extends": "./workspace-1.json"
4 |   }
5 | }
6 | 


--------------------------------------------------------------------------------
/crates/volta-core/fixtures/hooks/bins.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "node": {
 3 |     "distro": {
 4 |       "bin": "/some/bin/for/node/distro"
 5 |     },
 6 |     "latest": {
 7 |       "bin": "/some/bin/for/node/latest"
 8 |     },
 9 |     "index": {
10 |       "bin": "/some/bin/for/node/index"
11 |     }
12 |   },
13 |   "pnpm": {
14 |     "distro": {
15 |       "bin": "/bin/to/pnpm/distro"
16 |     },
17 |     "latest": {
18 |       "bin": "/bin/to/pnpm/latest"
19 |     },
20 |     "index": {
21 |       "bin": "/bin/to/pnpm/index"
22 |     }
23 |   },
24 |   "yarn": {
25 |     "distro": {
26 |       "bin": "/bin/to/yarn/distro"
27 |     },
28 |     "latest": {
29 |       "bin": "/bin/to/yarn/latest"
30 |     },
31 |     "index": {
32 |       "bin": "/bin/to/yarn/index"
33 |     }
34 |   },
35 |   "events": {
36 |     "publish": {
37 |       "bin": "/events/bin"
38 |     }
39 |   }
40 | }
41 | 


--------------------------------------------------------------------------------
/crates/volta-core/fixtures/hooks/event_url.json:
--------------------------------------------------------------------------------
1 | {
2 |   "events": {
3 |     "publish": {
4 |       "url": "https://google.com"
5 |     }
6 |   }
7 | }
8 | 


--------------------------------------------------------------------------------
/crates/volta-core/fixtures/hooks/format_github.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "node": {
 3 |     "index": {
 4 |       "prefix": "http://localhost/node/index/",
 5 |       "format": "github"
 6 |     }
 7 |   },
 8 |   "npm": {
 9 |     "index": {
10 |       "prefix": "http://localhost/npm/index/",
11 |       "format": "github"
12 |     }
13 |   },
14 |   "pnpm": {
15 |     "index": {
16 |       "prefix": "http://localhost/pnpm/index/",
17 |       "format": "github"
18 |     }
19 |   },
20 |   "yarn": {
21 |     "index": {
22 |       "prefix": "http://localhost/yarn/index/",
23 |       "format": "github"
24 |     }
25 |   }
26 | }
27 | 


--------------------------------------------------------------------------------
/crates/volta-core/fixtures/hooks/format_npm.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "node": {
 3 |     "index": {
 4 |       "prefix": "http://localhost/node/index/",
 5 |       "format": "npm"
 6 |     }
 7 |   },
 8 |   "npm": {
 9 |     "index": {
10 |       "prefix": "http://localhost/npm/index/",
11 |       "format": "npm"
12 |     }
13 |   },
14 |   "pnpm": {
15 |     "index": {
16 |       "prefix": "http://localhost/pnpm/index/",
17 |       "format": "npm"
18 |     }
19 |   },
20 |   "yarn": {
21 |     "index": {
22 |       "prefix": "http://localhost/yarn/index/",
23 |       "format": "npm"
24 |     }
25 |   }
26 | }
27 | 


--------------------------------------------------------------------------------
/crates/volta-core/fixtures/hooks/prefixes.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "node": {
 3 |     "distro": {
 4 |       "prefix": "http://localhost/node/distro/"
 5 |     },
 6 |     "latest": {
 7 |       "prefix": "http://localhost/node/latest/"
 8 |     },
 9 |     "index": {
10 |       "prefix": "http://localhost/node/index/"
11 |     }
12 |   },
13 |   "pnpm": {
14 |     "distro": {
15 |       "prefix": "http://localhost/pnpm/distro/"
16 |     },
17 |     "latest": {
18 |       "prefix": "http://localhost/pnpm/latest/"
19 |     },
20 |     "index": {
21 |       "prefix": "http://localhost/pnpm/index/"
22 |     }
23 |   },
24 |   "yarn": {
25 |     "distro": {
26 |       "prefix": "http://localhost/yarn/distro/"
27 |     },
28 |     "latest": {
29 |       "prefix": "http://localhost/yarn/latest/"
30 |     },
31 |     "index": {
32 |       "prefix": "http://localhost/yarn/index/"
33 |     }
34 |   }
35 | }
36 | 


--------------------------------------------------------------------------------
/crates/volta-core/fixtures/hooks/project/.volta/hooks.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "node": {
 3 |     "distro": {
 4 |       "bin": "/some/bin/for/node/distro"
 5 |     },
 6 |     "latest": {
 7 |       "bin": "/some/bin/for/node/latest"
 8 |     },
 9 |     "index": {
10 |       "bin": "/some/bin/for/node/index"
11 |     }
12 |   },
13 |   "events": {
14 |     "publish": {
15 |       "bin": "/events/bin"
16 |     }
17 |   }
18 | }
19 | 


--------------------------------------------------------------------------------
/crates/volta-core/fixtures/hooks/project/package.json:
--------------------------------------------------------------------------------
1 | {
2 |   "this file": "causes this directory to be recognized as a project"
3 | }
4 | 


--------------------------------------------------------------------------------
/crates/volta-core/fixtures/hooks/templates.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "node": {
 3 |     "distro": {
 4 |       "template": "http://localhost/node/distro/{{version}}/"
 5 |     },
 6 |     "latest": {
 7 |       "template": "http://localhost/node/latest/{{version}}/"
 8 |     },
 9 |     "index": {
10 |       "template": "http://localhost/node/index/{{version}}/"
11 |     }
12 |   },
13 |   "pnpm": {
14 |     "distro": {
15 |       "template": "http://localhost/pnpm/distro/{{version}}/"
16 |     },
17 |     "latest": {
18 |       "template": "http://localhost/pnpm/latest/{{version}}/"
19 |     },
20 |     "index": {
21 |       "template": "http://localhost/pnpm/index/{{version}}/"
22 |     }
23 |   },
24 |   "yarn": {
25 |     "distro": {
26 |       "template": "http://localhost/yarn/distro/{{version}}/"
27 |     },
28 |     "latest": {
29 |       "template": "http://localhost/yarn/latest/{{version}}/"
30 |     },
31 |     "index": {
32 |       "template": "http://localhost/yarn/index/{{version}}/"
33 |     }
34 |   }
35 | }
36 | 


--------------------------------------------------------------------------------
/crates/volta-core/fixtures/nested/node_modules/.bin/eslint:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | 
3 | console.log("Running eslint");
4 | 


--------------------------------------------------------------------------------
/crates/volta-core/fixtures/nested/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "nested-project",
 3 |   "version": "0.0.1",
 4 |   "description": "Testing that project correctly detects a nested workspace",
 5 |   "license": "To Kill",
 6 |   "dependencies": {
 7 |     "lodash": "*"
 8 |   },
 9 |   "devDependencies": {
10 |     "eslint": "*"
11 |   },
12 |   "volta": {
13 |     "yarn": "1.11.0",
14 |     "npm": "6.12.1",
15 |     "node": "12.14.0"
16 |   }
17 | }
18 | 


--------------------------------------------------------------------------------
/crates/volta-core/fixtures/nested/subproject/inner_project/node_modules/.bin/tsc:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | 
3 | console.log("Running Typescript");
4 | 


--------------------------------------------------------------------------------
/crates/volta-core/fixtures/nested/subproject/inner_project/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "inner-project",
 3 |   "version": "0.0.1",
 4 |   "description": "Testing that project correctly detects a nested workspace",
 5 |   "license": "To Kill",
 6 |   "dependencies": {
 7 |     "express": "*"
 8 |   },
 9 |   "devDependencies": {
10 |     "typescript": "*"
11 |   },
12 |   "volta": {
13 |     "yarn": "1.22.4",
14 |     "extends": "../package.json"
15 |   }
16 | }
17 | 


--------------------------------------------------------------------------------
/crates/volta-core/fixtures/nested/subproject/node_modules/.bin/rsvp:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | 
3 | console.log("Running rsvp");
4 | 


--------------------------------------------------------------------------------
/crates/volta-core/fixtures/nested/subproject/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "subproject",
 3 |   "version": "0.0.1",
 4 |   "description": "Testing that project correctly detects a nested workspace",
 5 |   "license": "To Kill",
 6 |   "dependencies": {
 7 |     "rsvp": "*"
 8 |   },
 9 |   "devDependencies": {
10 |     "glob": "*"
11 |   },
12 |   "volta": {
13 |     "yarn": "1.17.0",
14 |     "npm": "6.9.0",
15 |     "extends": "../package.json"
16 |   }
17 | }
18 | 


--------------------------------------------------------------------------------
/crates/volta-core/fixtures/no_toolchain/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "basic-project",
 3 |   "version": "0.0.7",
 4 |   "description": "Testing that manifest pulls things out of this correctly",
 5 |   "license": "To Kill",
 6 |   "dependencies": {
 7 |     "@namespace/some-dep": "0.2.4",
 8 |     "rsvp": "^3.5.0"
 9 |   },
10 |   "devDependencies": {
11 |     "@namespaced/something-else": "^6.3.7",
12 |     "eslint": "~4.8.0"
13 |   }
14 | }
15 | 


--------------------------------------------------------------------------------
/crates/volta-core/fixtures/yarn/pnp-cjs/.pnp.cjs:
--------------------------------------------------------------------------------
1 | // plug and play
2 | 


--------------------------------------------------------------------------------
/crates/volta-core/fixtures/yarn/pnp-cjs/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "plug-n-play-cjs",
 3 |   "version": "2.0.0",
 4 |   "description": "Testing that Plug-n-Play things work",
 5 |   "license": "To Ill",
 6 |   "volta": {
 7 |     "node": "6.11.1",
 8 |     "npm": "3.10.10",
 9 |     "yarn": "3.2.0"
10 |   }
11 | }
12 | 


--------------------------------------------------------------------------------
/crates/volta-core/fixtures/yarn/pnp-js/.pnp.js:
--------------------------------------------------------------------------------
1 | // plug and play
2 | 


--------------------------------------------------------------------------------
/crates/volta-core/fixtures/yarn/pnp-js/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "plug-n-play-js",
 3 |   "version": "2.0.0",
 4 |   "description": "Testing that Plug-n-Play things work",
 5 |   "license": "To Ill",
 6 |   "volta": {
 7 |     "node": "6.11.1",
 8 |     "npm": "3.10.10",
 9 |     "yarn": "2.4.0"
10 |   }
11 | }
12 | 


--------------------------------------------------------------------------------
/crates/volta-core/fixtures/yarn/yarnrc-yml/.yarnrc.yml:
--------------------------------------------------------------------------------
1 | yarnPath: .yarn/releases/yarn-3.3.0.cjs


--------------------------------------------------------------------------------
/crates/volta-core/fixtures/yarn/yarnrc-yml/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "yarnrc-yml",
 3 |   "version": "2.0.0",
 4 |   "description": "Testing that Yarn berry things work",
 5 |   "license": "To Ill",
 6 |   "volta": {
 7 |     "node": "6.11.1",
 8 |     "npm": "3.10.10",
 9 |     "yarn": "3.3.0"
10 |   }
11 | }
12 | 


--------------------------------------------------------------------------------
/crates/volta-core/src/command.rs:
--------------------------------------------------------------------------------
 1 | use std::ffi::OsStr;
 2 | use std::process::Command;
 3 | 
 4 | use cfg_if::cfg_if;
 5 | 
 6 | cfg_if! {
 7 |     if #[cfg(windows)] {
 8 |         pub fn create_command<E>(exe: E) -> Command
 9 |         where
10 |             E: AsRef<OsStr>
11 |         {
12 |             // Several of the node utilities are implemented as `.bat` or `.cmd` files
13 |             // When executing those files with `Command`, we need to call them with:
14 |             //    cmd.exe /C <COMMAND> <ARGUMENTS>
15 |             // Instead of: <COMMAND> <ARGUMENTS>
16 |             // See: https://github.com/rust-lang/rust/issues/42791 For a longer discussion
17 |             let mut command = Command::new("cmd.exe");
18 |             command.arg("/C");
19 |             command.arg(exe);
20 |             command
21 |         }
22 |     } else {
23 |         pub fn create_command<E>(exe: E) -> Command
24 |         where
25 |             E: AsRef<OsStr>
26 |         {
27 |             Command::new(exe)
28 |         }
29 |     }
30 | }
31 | 


--------------------------------------------------------------------------------
/crates/volta-core/src/error/mod.rs:
--------------------------------------------------------------------------------
  1 | use std::error::Error;
  2 | use std::fmt;
  3 | use std::process::exit;
  4 | 
  5 | mod kind;
  6 | mod reporter;
  7 | 
  8 | pub use kind::ErrorKind;
  9 | pub use reporter::report_error;
 10 | 
 11 | pub type Fallible<T> = Result<T, VoltaError>;
 12 | 
 13 | /// Error type for Volta
 14 | #[derive(Debug)]
 15 | pub struct VoltaError {
 16 |     inner: Box<Inner>,
 17 | }
 18 | 
 19 | #[derive(Debug)]
 20 | struct Inner {
 21 |     kind: ErrorKind,
 22 |     source: Option<Box<dyn Error>>,
 23 | }
 24 | 
 25 | impl VoltaError {
 26 |     /// The exit code Volta should use when this error stops execution
 27 |     pub fn exit_code(&self) -> ExitCode {
 28 |         self.inner.kind.exit_code()
 29 |     }
 30 | 
 31 |     /// Create a new VoltaError instance including a source error
 32 |     pub fn from_source<E>(source: E, kind: ErrorKind) -> Self
 33 |     where
 34 |         E: Into<Box<dyn Error>>,
 35 |     {
 36 |         VoltaError {
 37 |             inner: Box::new(Inner {
 38 |                 kind,
 39 |                 source: Some(source.into()),
 40 |             }),
 41 |         }
 42 |     }
 43 | 
 44 |     /// Get a reference to the ErrorKind for this error
 45 |     pub fn kind(&self) -> &ErrorKind {
 46 |         &self.inner.kind
 47 |     }
 48 | }
 49 | 
 50 | impl fmt::Display for VoltaError {
 51 |     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 52 |         self.inner.kind.fmt(f)
 53 |     }
 54 | }
 55 | 
 56 | impl Error for VoltaError {
 57 |     fn source(&self) -> Option<&(dyn Error + 'static)> {
 58 |         self.inner.source.as_ref().map(|b| b.as_ref())
 59 |     }
 60 | }
 61 | 
 62 | impl From<ErrorKind> for VoltaError {
 63 |     fn from(kind: ErrorKind) -> Self {
 64 |         VoltaError {
 65 |             inner: Box::new(Inner { kind, source: None }),
 66 |         }
 67 |     }
 68 | }
 69 | 
 70 | /// Trait providing the with_context method to easily convert any Result error into a VoltaError
 71 | pub trait Context<T> {
 72 |     fn with_context<F>(self, f: F) -> Fallible<T>
 73 |     where
 74 |         F: FnOnce() -> ErrorKind;
 75 | }
 76 | 
 77 | impl<T, E> Context<T> for Result<T, E>
 78 | where
 79 |     E: Error + 'static,
 80 | {
 81 |     fn with_context<F>(self, f: F) -> Fallible<T>
 82 |     where
 83 |         F: FnOnce() -> ErrorKind,
 84 |     {
 85 |         self.map_err(|e| VoltaError::from_source(e, f()))
 86 |     }
 87 | }
 88 | 
 89 | /// Exit codes supported by Volta Errors
 90 | #[derive(Copy, Clone, Debug)]
 91 | pub enum ExitCode {
 92 |     /// No error occurred.
 93 |     Success = 0,
 94 | 
 95 |     /// An unknown error occurred.
 96 |     UnknownError = 1,
 97 | 
 98 |     /// An invalid combination of command-line arguments was supplied.
 99 |     InvalidArguments = 3,
100 | 
101 |     /// No match could be found for the requested version string.
102 |     NoVersionMatch = 4,
103 | 
104 |     /// A network error occurred.
105 |     NetworkError = 5,
106 | 
107 |     /// A required environment variable was unset or invalid.
108 |     EnvironmentError = 6,
109 | 
110 |     /// A file could not be read or written.
111 |     FileSystemError = 7,
112 | 
113 |     /// Package configuration is missing or incorrect.
114 |     ConfigurationError = 8,
115 | 
116 |     /// The command or feature is not yet implemented.
117 |     NotYetImplemented = 9,
118 | 
119 |     /// The requested executable could not be run.
120 |     ExecutionFailure = 126,
121 | 
122 |     /// The requested executable is not available.
123 |     ExecutableNotFound = 127,
124 | }
125 | 
126 | impl ExitCode {
127 |     pub fn exit(self) -> ! {
128 |         exit(self as i32);
129 |     }
130 | }
131 | 


--------------------------------------------------------------------------------
/crates/volta-core/src/error/reporter.rs:
--------------------------------------------------------------------------------
  1 | use std::env::args_os;
  2 | use std::error::Error;
  3 | use std::fs::File;
  4 | use std::io::Write;
  5 | use std::path::PathBuf;
  6 | 
  7 | use super::VoltaError;
  8 | use crate::layout::volta_home;
  9 | use crate::style::format_error_cause;
 10 | use chrono::Local;
 11 | use ci_info::is_ci;
 12 | use console::strip_ansi_codes;
 13 | use fs_utils::ensure_containing_dir_exists;
 14 | use log::{debug, error};
 15 | 
 16 | /// Report an error, both to the console and to error logs
 17 | pub fn report_error(volta_version: &str, err: &VoltaError) {
 18 |     let message = err.to_string();
 19 |     error!("{}", message);
 20 | 
 21 |     if let Some(details) = compose_error_details(err) {
 22 |         if is_ci() {
 23 |             // In CI, we write the error details to the log so that they are available in the CI logs
 24 |             // A log file may not even exist by the time the user is reviewing a failure
 25 |             error!("{}", details);
 26 |         } else {
 27 |             // Outside of CI, we write the error details as Debug (Verbose) information
 28 |             // And we write an actual error log that the user can review
 29 |             debug!("{}", details);
 30 | 
 31 |             // Note: Writing the error log info directly to stderr as it is a message for the user
 32 |             // Any custom logs will have all of the details already, so showing a message about writing
 33 |             // the error log would be redundant
 34 |             match write_error_log(volta_version, message, details) {
 35 |                 Ok(log_file) => {
 36 |                     eprintln!("Error details written to {}", log_file.to_string_lossy());
 37 |                 }
 38 |                 Err(_) => {
 39 |                     eprintln!("Unable to write error log!");
 40 |                 }
 41 |             }
 42 |         }
 43 |     }
 44 | }
 45 | 
 46 | /// Write an error log with all details about the error
 47 | fn write_error_log(
 48 |     volta_version: &str,
 49 |     message: String,
 50 |     details: String,
 51 | ) -> Result<PathBuf, Box<dyn Error>> {
 52 |     let file_name = Local::now()
 53 |         .format("volta-error-%Y-%m-%d_%H_%M_%S%.3f.log")
 54 |         .to_string();
 55 |     let log_file_path = volta_home()?.log_dir().join(file_name);
 56 | 
 57 |     ensure_containing_dir_exists(&log_file_path)?;
 58 |     let mut log_file = File::create(&log_file_path)?;
 59 | 
 60 |     writeln!(log_file, "{}", collect_arguments())?;
 61 |     writeln!(log_file, "Volta v{}", volta_version)?;
 62 |     writeln!(log_file)?;
 63 |     writeln!(log_file, "{}", strip_ansi_codes(&message))?;
 64 |     writeln!(log_file)?;
 65 |     writeln!(log_file, "{}", strip_ansi_codes(&details))?;
 66 | 
 67 |     Ok(log_file_path)
 68 | }
 69 | 
 70 | fn compose_error_details(err: &VoltaError) -> Option<String> {
 71 |     // Only compose details if there is an underlying cause for the error
 72 |     let mut current = err.source()?;
 73 |     let mut details = String::new();
 74 | 
 75 |     // Walk up the tree of causes and include all of them
 76 |     loop {
 77 |         details.push_str(&format_error_cause(current));
 78 | 
 79 |         match current.source() {
 80 |             Some(cause) => {
 81 |                 details.push_str("\n\n");
 82 |                 current = cause;
 83 |             }
 84 |             None => {
 85 |                 break;
 86 |             }
 87 |         };
 88 |     }
 89 | 
 90 |     Some(details)
 91 | }
 92 | 
 93 | /// Combines all the arguments into a single String
 94 | fn collect_arguments() -> String {
 95 |     // The Debug formatter for OsString properly quotes and escapes each value
 96 |     args_os()
 97 |         .map(|arg| format!("{:?}", arg))
 98 |         .collect::<Vec<String>>()
 99 |         .join(" ")
100 | }
101 | 


--------------------------------------------------------------------------------
/crates/volta-core/src/inventory.rs:
--------------------------------------------------------------------------------
  1 | //! Provides types for working with Volta's _inventory_, the local repository
  2 | //! of available tool versions.
  3 | 
  4 | use std::collections::BTreeSet;
  5 | use std::ffi::OsStr;
  6 | use std::path::Path;
  7 | 
  8 | use crate::error::{Context, ErrorKind, Fallible};
  9 | use crate::fs::read_dir_eager;
 10 | use crate::layout::volta_home;
 11 | use crate::tool::PackageConfig;
 12 | use crate::version::parse_version;
 13 | use log::debug;
 14 | use node_semver::Version;
 15 | use walkdir::WalkDir;
 16 | 
 17 | /// Checks if a given Node version image is available on the local machine
 18 | pub fn node_available(version: &Version) -> Fallible<bool> {
 19 |     volta_home().map(|home| {
 20 |         home.node_image_root_dir()
 21 |             .join(version.to_string())
 22 |             .exists()
 23 |     })
 24 | }
 25 | 
 26 | /// Collects a set of all Node versions fetched on the local machine
 27 | pub fn node_versions() -> Fallible<BTreeSet<Version>> {
 28 |     volta_home().and_then(|home| read_versions(home.node_image_root_dir()))
 29 | }
 30 | 
 31 | /// Checks if a given npm version image is available on the local machine
 32 | pub fn npm_available(version: &Version) -> Fallible<bool> {
 33 |     volta_home().map(|home| home.npm_image_dir(&version.to_string()).exists())
 34 | }
 35 | 
 36 | /// Collects a set of all npm versions fetched on the local machine
 37 | pub fn npm_versions() -> Fallible<BTreeSet<Version>> {
 38 |     volta_home().and_then(|home| read_versions(home.npm_image_root_dir()))
 39 | }
 40 | 
 41 | /// Checks if a given pnpm version image is available on the local machine
 42 | pub fn pnpm_available(version: &Version) -> Fallible<bool> {
 43 |     volta_home().map(|home| home.pnpm_image_dir(&version.to_string()).exists())
 44 | }
 45 | 
 46 | /// Collects a set of all pnpm versions fetched on the local machine
 47 | pub fn pnpm_versions() -> Fallible<BTreeSet<Version>> {
 48 |     volta_home().and_then(|home| read_versions(home.pnpm_image_root_dir()))
 49 | }
 50 | 
 51 | /// Checks if a given Yarn version image is available on the local machine
 52 | pub fn yarn_available(version: &Version) -> Fallible<bool> {
 53 |     volta_home().map(|home| home.yarn_image_dir(&version.to_string()).exists())
 54 | }
 55 | 
 56 | /// Collects a set of all Yarn versions fetched on the local machine
 57 | pub fn yarn_versions() -> Fallible<BTreeSet<Version>> {
 58 |     volta_home().and_then(|home| read_versions(home.yarn_image_root_dir()))
 59 | }
 60 | 
 61 | /// Collects a set of all Package Configs on the local machine
 62 | pub fn package_configs() -> Fallible<BTreeSet<PackageConfig>> {
 63 |     let package_dir = volta_home()?.default_package_dir();
 64 | 
 65 |     WalkDir::new(package_dir)
 66 |         .max_depth(2)
 67 |         .into_iter()
 68 |         // Ignore any items which didn't resolve as `DirEntry` correctly.
 69 |         // There is no point trying to do anything with those, and no error
 70 |         // we can report to the user in any case. Log the failure in the
 71 |         // debug output, though
 72 |         .filter_map(|entry| match entry {
 73 |             Ok(dir_entry) => {
 74 |                 // Ignore directory entries and any files that don't have a .json extension.
 75 |                 // This will prevent us from trying to parse OS-generated files as package
 76 |                 // configs (e.g. `.DS_Store` on macOS)
 77 |                 let extension = dir_entry.path().extension().and_then(OsStr::to_str);
 78 |                 match (dir_entry.file_type().is_file(), extension) {
 79 |                     (true, Some(ext)) if ext.eq_ignore_ascii_case("json") => {
 80 |                         Some(dir_entry.into_path())
 81 |                     }
 82 |                     _ => None,
 83 |                 }
 84 |             }
 85 |             Err(e) => {
 86 |                 debug!("{}", e);
 87 |                 None
 88 |             }
 89 |         })
 90 |         .map(PackageConfig::from_file)
 91 |         .collect()
 92 | }
 93 | 
 94 | /// Reads the contents of a directory and returns the set of all versions found
 95 | /// in the directory's listing by parsing the directory names as semantic versions
 96 | fn read_versions(dir: &Path) -> Fallible<BTreeSet<Version>> {
 97 |     let contents = read_dir_eager(dir).with_context(|| ErrorKind::ReadDirError {
 98 |         dir: dir.to_owned(),
 99 |     })?;
100 | 
101 |     Ok(contents
102 |         .filter(|(_, metadata)| metadata.is_dir())
103 |         .filter_map(|(entry, _)| parse_version(entry.file_name().to_string_lossy()).ok())
104 |         .collect())
105 | }
106 | 


--------------------------------------------------------------------------------
/crates/volta-core/src/layout/mod.rs:
--------------------------------------------------------------------------------
 1 | use std::env;
 2 | use std::path::PathBuf;
 3 | 
 4 | use crate::error::{Context, ErrorKind, Fallible};
 5 | use cfg_if::cfg_if;
 6 | use dunce::canonicalize;
 7 | use once_cell::sync::OnceCell;
 8 | use volta_layout::v4::{VoltaHome, VoltaInstall};
 9 | 
10 | cfg_if! {
11 |     if #[cfg(unix)] {
12 |         mod unix;
13 |         pub use unix::*;
14 |     } else if #[cfg(windows)] {
15 |         mod windows;
16 |         pub use windows::*;
17 |     }
18 | }
19 | 
20 | static VOLTA_HOME: OnceCell<VoltaHome> = OnceCell::new();
21 | static VOLTA_INSTALL: OnceCell<VoltaInstall> = OnceCell::new();
22 | 
23 | pub fn volta_home<'a>() -> Fallible<&'a VoltaHome> {
24 |     VOLTA_HOME.get_or_try_init(|| {
25 |         let home_dir = match env::var_os("VOLTA_HOME") {
26 |             Some(home) => PathBuf::from(home),
27 |             None => default_home_dir()?,
28 |         };
29 | 
30 |         Ok(VoltaHome::new(home_dir))
31 |     })
32 | }
33 | 
34 | pub fn volta_install<'a>() -> Fallible<&'a VoltaInstall> {
35 |     VOLTA_INSTALL.get_or_try_init(|| {
36 |         let install_dir = match env::var_os("VOLTA_INSTALL_DIR") {
37 |             Some(install) => PathBuf::from(install),
38 |             None => default_install_dir()?,
39 |         };
40 | 
41 |         Ok(VoltaInstall::new(install_dir))
42 |     })
43 | }
44 | 
45 | /// Determine the binary install directory from the currently running executable
46 | ///
47 | /// The volta-shim and volta binaries will be installed in the same location, so we can use the
48 | /// currently running executable to find the binary install directory. Note that we need to
49 | /// canonicalize the path we get from current_exe to make sure we resolve symlinks and find the
50 | /// actual binary files
51 | fn default_install_dir() -> Fallible<PathBuf> {
52 |     env::current_exe()
53 |         .and_then(canonicalize)
54 |         .map(|mut path| {
55 |             path.pop(); // Remove the executable name from the path
56 |             path
57 |         })
58 |         .with_context(|| ErrorKind::NoInstallDir)
59 | }
60 | 


--------------------------------------------------------------------------------
/crates/volta-core/src/layout/unix.rs:
--------------------------------------------------------------------------------
 1 | use std::path::PathBuf;
 2 | 
 3 | use super::volta_home;
 4 | use crate::error::{ErrorKind, Fallible};
 5 | 
 6 | pub(super) fn default_home_dir() -> Fallible<PathBuf> {
 7 |     let mut home = dirs::home_dir().ok_or(ErrorKind::NoHomeEnvironmentVar)?;
 8 |     home.push(".volta");
 9 |     Ok(home)
10 | }
11 | 
12 | pub fn env_paths() -> Fallible<Vec<PathBuf>> {
13 |     let home = volta_home()?;
14 |     Ok(vec![home.shim_dir().to_owned()])
15 | }
16 | 


--------------------------------------------------------------------------------
/crates/volta-core/src/layout/windows.rs:
--------------------------------------------------------------------------------
 1 | use std::path::PathBuf;
 2 | 
 3 | use super::{volta_home, volta_install};
 4 | use crate::error::{ErrorKind, Fallible};
 5 | 
 6 | pub(super) fn default_home_dir() -> Fallible<PathBuf> {
 7 |     let mut home = dirs::data_local_dir().ok_or(ErrorKind::NoLocalDataDir)?;
 8 |     home.push("Volta");
 9 |     Ok(home)
10 | }
11 | 
12 | pub fn env_paths() -> Fallible<Vec<PathBuf>> {
13 |     let home = volta_home()?;
14 |     let install = volta_install()?;
15 | 
16 |     Ok(vec![home.shim_dir().to_owned(), install.root().to_owned()])
17 | }
18 | 


--------------------------------------------------------------------------------
/crates/volta-core/src/lib.rs:
--------------------------------------------------------------------------------
 1 | //! The main implementation crate for the core of Volta.
 2 | 
 3 | mod command;
 4 | pub mod error;
 5 | pub mod event;
 6 | pub mod fs;
 7 | mod hook;
 8 | pub mod inventory;
 9 | pub mod layout;
10 | pub mod log;
11 | pub mod monitor;
12 | pub mod platform;
13 | pub mod project;
14 | pub mod run;
15 | pub mod session;
16 | pub mod shim;
17 | pub mod signal;
18 | pub mod style;
19 | pub mod sync;
20 | pub mod tool;
21 | pub mod toolchain;
22 | pub mod version;
23 | 
24 | const VOLTA_FEATURE_PNPM: &str = "VOLTA_FEATURE_PNPM";
25 | 


--------------------------------------------------------------------------------
/crates/volta-core/src/monitor.rs:
--------------------------------------------------------------------------------
 1 | use std::env;
 2 | use std::io::Write;
 3 | use std::path::PathBuf;
 4 | use std::process::{Child, Stdio};
 5 | 
 6 | use log::debug;
 7 | use tempfile::NamedTempFile;
 8 | 
 9 | use crate::command::create_command;
10 | use crate::event::Event;
11 | 
12 | /// Send event to the spawned command process
13 | // if hook command is not configured, this is not called
14 | pub fn send_events(command: &str, events: &[Event]) {
15 |     match serde_json::to_string_pretty(&events) {
16 |         Ok(events_json) => {
17 |             let tempfile_path = env::var_os("VOLTA_WRITE_EVENTS_FILE")
18 |                 .and_then(|_| write_events_file(events_json.clone()));
19 |             if let Some(ref mut child_process) = spawn_process(command, tempfile_path) {
20 |                 if let Some(ref mut p_stdin) = child_process.stdin.as_mut() {
21 |                     if let Err(error) = writeln!(p_stdin, "{}", events_json) {
22 |                         debug!("Could not write events to executable stdin: {:?}", error);
23 |                     }
24 |                 }
25 |             }
26 |         }
27 |         Err(error) => {
28 |             debug!("Could not serialize events data to JSON: {:?}", error);
29 |         }
30 |     }
31 | }
32 | 
33 | // Write the events JSON to a file in the temporary directory
34 | fn write_events_file(events_json: String) -> Option<PathBuf> {
35 |     match NamedTempFile::new() {
36 |         Ok(mut events_file) => {
37 |             match events_file.write_all(events_json.as_bytes()) {
38 |                 Ok(()) => {
39 |                     let path = events_file.into_temp_path();
40 |                     // if it's not persisted, the temp file will be automatically deleted
41 |                     // (and the executable won't be able to read it)
42 |                     match path.keep() {
43 |                         Ok(tempfile_path) => Some(tempfile_path),
44 |                         Err(error) => {
45 |                             debug!("Failed to persist temp file for events data: {:?}", error);
46 |                             None
47 |                         }
48 |                     }
49 |                 }
50 |                 Err(error) => {
51 |                     debug!("Failed to write events to the temp file: {:?}", error);
52 |                     None
53 |                 }
54 |             }
55 |         }
56 |         Err(error) => {
57 |             debug!("Failed to create a temp file for events data: {:?}", error);
58 |             None
59 |         }
60 |     }
61 | }
62 | 
63 | // Spawn a child process to receive the events data, setting the path to the events file as an env var
64 | fn spawn_process(command: &str, tempfile_path: Option<PathBuf>) -> Option<Child> {
65 |     command.split(' ').take(1).next().and_then(|executable| {
66 |         let mut child = create_command(executable);
67 |         child.args(command.split(' ').skip(1));
68 |         child.stdin(Stdio::piped());
69 |         if let Some(events_file) = tempfile_path {
70 |             child.env("EVENTS_FILE", events_file);
71 |         }
72 | 
73 |         #[cfg(not(debug_assertions))]
74 |         // Hide stdout and stderr of spawned process in release mode
75 |         child.stdout(Stdio::null()).stderr(Stdio::null());
76 | 
77 |         match child.spawn() {
78 |             Err(err) => {
79 |                 debug!("Unable to run executable command: '{}'\n{}", command, err);
80 |                 None
81 |             }
82 |             Ok(c) => Some(c),
83 |         }
84 |     })
85 | }
86 | 


--------------------------------------------------------------------------------
/crates/volta-core/src/platform/image.rs:
--------------------------------------------------------------------------------
 1 | use std::ffi::OsString;
 2 | use std::path::PathBuf;
 3 | 
 4 | use super::{build_path_error, Sourced};
 5 | use crate::error::{Context, Fallible};
 6 | use crate::layout::volta_home;
 7 | use crate::tool::load_default_npm_version;
 8 | use node_semver::Version;
 9 | 
10 | /// A platform image.
11 | pub struct Image {
12 |     /// The pinned version of Node.
13 |     pub node: Sourced<Version>,
14 |     /// The custom version of npm, if any. `None` represents using the npm that is bundled with Node
15 |     pub npm: Option<Sourced<Version>>,
16 |     /// The pinned version of pnpm, if any.
17 |     pub pnpm: Option<Sourced<Version>>,
18 |     /// The pinned version of Yarn, if any.
19 |     pub yarn: Option<Sourced<Version>>,
20 | }
21 | 
22 | impl Image {
23 |     fn bins(&self) -> Fallible<Vec<PathBuf>> {
24 |         let home = volta_home()?;
25 |         let mut bins = Vec::with_capacity(3);
26 | 
27 |         if let Some(npm) = &self.npm {
28 |             let npm_str = npm.value.to_string();
29 |             bins.push(home.npm_image_bin_dir(&npm_str));
30 |         }
31 | 
32 |         if let Some(pnpm) = &self.pnpm {
33 |             let pnpm_str = pnpm.value.to_string();
34 |             bins.push(home.pnpm_image_bin_dir(&pnpm_str));
35 |         }
36 | 
37 |         if let Some(yarn) = &self.yarn {
38 |             let yarn_str = yarn.value.to_string();
39 |             bins.push(home.yarn_image_bin_dir(&yarn_str));
40 |         }
41 | 
42 |         // Add Node path to the bins last, so that any custom version of npm will be earlier in the PATH
43 |         let node_str = self.node.value.to_string();
44 |         bins.push(home.node_image_bin_dir(&node_str));
45 |         Ok(bins)
46 |     }
47 | 
48 |     /// Produces a modified version of the current `PATH` environment variable that
49 |     /// will find toolchain executables (Node, npm, pnpm, Yarn) in the installation directories
50 |     /// for the given versions instead of in the Volta shim directory.
51 |     pub fn path(&self) -> Fallible<OsString> {
52 |         let old_path = envoy::path().unwrap_or_else(|| envoy::Var::from(""));
53 | 
54 |         old_path
55 |             .split()
56 |             .prefix(self.bins()?)
57 |             .join()
58 |             .with_context(build_path_error)
59 |     }
60 | 
61 |     /// Determines the sourced version of npm that will be available, resolving the version bundled with Node, if needed
62 |     pub fn resolve_npm(&self) -> Fallible<Sourced<Version>> {
63 |         match &self.npm {
64 |             Some(npm) => Ok(npm.clone()),
65 |             None => load_default_npm_version(&self.node.value).map(|npm| Sourced {
66 |                 value: npm,
67 |                 source: self.node.source,
68 |             }),
69 |         }
70 |     }
71 | }
72 | 


--------------------------------------------------------------------------------
/crates/volta-core/src/platform/system.rs:
--------------------------------------------------------------------------------
 1 | use std::ffi::OsString;
 2 | 
 3 | use super::build_path_error;
 4 | use crate::error::{Context, Fallible};
 5 | use crate::layout::env_paths;
 6 | 
 7 | /// A lightweight namespace type representing the system environment, i.e. the environment
 8 | /// with Volta removed.
 9 | pub struct System;
10 | 
11 | impl System {
12 |     /// Produces a modified version of the current `PATH` environment variable that
13 |     /// removes the Volta shims and binaries, to use for running system node and
14 |     /// executables.
15 |     pub fn path() -> Fallible<OsString> {
16 |         let old_path = envoy::path().unwrap_or_else(|| envoy::Var::from(""));
17 |         let mut new_path = old_path.split();
18 | 
19 |         for remove_path in env_paths()? {
20 |             new_path = new_path.remove(remove_path);
21 |         }
22 | 
23 |         new_path.join().with_context(build_path_error)
24 |     }
25 | }
26 | 


--------------------------------------------------------------------------------
/crates/volta-core/src/run/node.rs:
--------------------------------------------------------------------------------
 1 | use std::env;
 2 | use std::ffi::OsString;
 3 | 
 4 | use super::executor::{Executor, ToolCommand, ToolKind};
 5 | use super::{debug_active_image, debug_no_platform, RECURSION_ENV_VAR};
 6 | use crate::error::{ErrorKind, Fallible};
 7 | use crate::platform::{Platform, System};
 8 | use crate::session::{ActivityKind, Session};
 9 | 
10 | /// Build a `ToolCommand` for Node
11 | pub(super) fn command(args: &[OsString], session: &mut Session) -> Fallible<Executor> {
12 |     session.add_event_start(ActivityKind::Node);
13 |     // Don't re-evaluate the platform if this is a recursive call
14 |     let platform = match env::var_os(RECURSION_ENV_VAR) {
15 |         Some(_) => None,
16 |         None => Platform::current(session)?,
17 |     };
18 | 
19 |     Ok(ToolCommand::new("node", args, platform, ToolKind::Node).into())
20 | }
21 | 
22 | /// Determine the execution context (PATH and failure error message) for Node
23 | pub(super) fn execution_context(
24 |     platform: Option<Platform>,
25 |     session: &mut Session,
26 | ) -> Fallible<(OsString, ErrorKind)> {
27 |     match platform {
28 |         Some(plat) => {
29 |             let image = plat.checkout(session)?;
30 |             let path = image.path()?;
31 |             debug_active_image(&image);
32 | 
33 |             Ok((path, ErrorKind::BinaryExecError))
34 |         }
35 |         None => {
36 |             let path = System::path()?;
37 |             debug_no_platform();
38 |             Ok((path, ErrorKind::NoPlatform))
39 |         }
40 |     }
41 | }
42 | 


--------------------------------------------------------------------------------
/crates/volta-core/src/run/npm.rs:
--------------------------------------------------------------------------------
 1 | use std::env;
 2 | use std::ffi::OsString;
 3 | use std::fs::File;
 4 | 
 5 | use super::executor::{Executor, ToolCommand, ToolKind, UninstallCommand};
 6 | use super::parser::{CommandArg, InterceptedCommand};
 7 | use super::{debug_active_image, debug_no_platform, RECURSION_ENV_VAR};
 8 | use crate::error::{ErrorKind, Fallible};
 9 | use crate::platform::{Platform, System};
10 | use crate::session::{ActivityKind, Session};
11 | use crate::tool::{PackageManifest, Spec};
12 | use crate::version::VersionSpec;
13 | 
14 | /// Build an `Executor` for npm
15 | ///
16 | /// If the command is a global install or uninstall and we have a default platform available, then
17 | /// we will use custom logic to ensure that the package is correctly installed / uninstalled in the
18 | /// Volta directory.
19 | ///
20 | /// If the command is _not_ a global install / uninstall or we don't have a default platform, then
21 | /// we will allow npm to execute the command as usual.
22 | pub(super) fn command(args: &[OsString], session: &mut Session) -> Fallible<Executor> {
23 |     session.add_event_start(ActivityKind::Npm);
24 |     // Don't re-evaluate the context or global install interception if this is a recursive call
25 |     let platform = match env::var_os(RECURSION_ENV_VAR) {
26 |         Some(_) => None,
27 |         None => {
28 |             match CommandArg::for_npm(args) {
29 |                 CommandArg::Global(cmd) => {
30 |                     // For globals, only intercept if the default platform exists
31 |                     if let Some(default_platform) = session.default_platform()? {
32 |                         return cmd.executor(default_platform);
33 |                     }
34 |                 }
35 |                 CommandArg::Intercepted(InterceptedCommand::Link(link)) => {
36 |                     // For link commands, only intercept if a platform exists
37 |                     if let Some(platform) = Platform::current(session)? {
38 |                         return link.executor(platform, current_project_name(session));
39 |                     }
40 |                 }
41 |                 CommandArg::Intercepted(InterceptedCommand::Unlink) => {
42 |                     // For unlink, attempt to find the current project name. If successful, treat
43 |                     // this as a global uninstall of the current project.
44 |                     if let Some(name) = current_project_name(session) {
45 |                         // Same as for link, only intercept if a platform exists
46 |                         if Platform::current(session)?.is_some() {
47 |                             return Ok(UninstallCommand::new(Spec::Package(
48 |                                 name,
49 |                                 VersionSpec::None,
50 |                             ))
51 |                             .into());
52 |                         }
53 |                     }
54 |                 }
55 |                 _ => {}
56 |             }
57 | 
58 |             Platform::current(session)?
59 |         }
60 |     };
61 | 
62 |     Ok(ToolCommand::new("npm", args, platform, ToolKind::Npm).into())
63 | }
64 | 
65 | /// Determine the execution context (PATH and failure error message) for npm
66 | pub(super) fn execution_context(
67 |     platform: Option<Platform>,
68 |     session: &mut Session,
69 | ) -> Fallible<(OsString, ErrorKind)> {
70 |     match platform {
71 |         Some(plat) => {
72 |             let image = plat.checkout(session)?;
73 |             let path = image.path()?;
74 |             debug_active_image(&image);
75 | 
76 |             Ok((path, ErrorKind::BinaryExecError))
77 |         }
78 |         None => {
79 |             let path = System::path()?;
80 |             debug_no_platform();
81 |             Ok((path, ErrorKind::NoPlatform))
82 |         }
83 |     }
84 | }
85 | 
86 | /// Determine the name of the current project, if possible
87 | fn current_project_name(session: &mut Session) -> Option<String> {
88 |     let project = session.project().ok()??;
89 |     let manifest_file = File::open(project.manifest_file()).ok()?;
90 |     let manifest: PackageManifest = serde_json::de::from_reader(manifest_file).ok()?;
91 | 
92 |     Some(manifest.name)
93 | }
94 | 


--------------------------------------------------------------------------------
/crates/volta-core/src/run/npx.rs:
--------------------------------------------------------------------------------
 1 | use std::env;
 2 | use std::ffi::OsString;
 3 | 
 4 | use super::executor::{Executor, ToolCommand, ToolKind};
 5 | use super::{debug_active_image, debug_no_platform, RECURSION_ENV_VAR};
 6 | use crate::error::{ErrorKind, Fallible};
 7 | use crate::platform::{Platform, System};
 8 | use crate::session::{ActivityKind, Session};
 9 | use node_semver::Version;
10 | use once_cell::sync::Lazy;
11 | 
12 | static REQUIRED_NPM_VERSION: Lazy<Version> = Lazy::new(|| Version {
13 |     major: 5,
14 |     minor: 2,
15 |     patch: 0,
16 |     build: vec![],
17 |     pre_release: vec![],
18 | });
19 | 
20 | /// Build a `ToolCommand` for npx
21 | pub(super) fn command(args: &[OsString], session: &mut Session) -> Fallible<Executor> {
22 |     session.add_event_start(ActivityKind::Npx);
23 |     // Don't re-evaluate the context if this is a recursive call
24 |     let platform = match env::var_os(RECURSION_ENV_VAR) {
25 |         Some(_) => None,
26 |         None => Platform::current(session)?,
27 |     };
28 | 
29 |     Ok(ToolCommand::new("npx", args, platform, ToolKind::Npx).into())
30 | }
31 | 
32 | /// Determine the execution context (PATH and failure error message) for npx
33 | pub(super) fn execution_context(
34 |     platform: Option<Platform>,
35 |     session: &mut Session,
36 | ) -> Fallible<(OsString, ErrorKind)> {
37 |     match platform {
38 |         Some(plat) => {
39 |             let image = plat.checkout(session)?;
40 | 
41 |             // If the npm version is lower than the minimum required, we can show a helpful error
42 |             // message instead of a 'command not found' error.
43 |             let active_npm = image.resolve_npm()?;
44 |             if active_npm.value < *REQUIRED_NPM_VERSION {
45 |                 return Err(ErrorKind::NpxNotAvailable {
46 |                     version: active_npm.value.to_string(),
47 |                 }
48 |                 .into());
49 |             }
50 | 
51 |             let path = image.path()?;
52 |             debug_active_image(&image);
53 | 
54 |             Ok((path, ErrorKind::BinaryExecError))
55 |         }
56 |         None => {
57 |             let path = System::path()?;
58 |             debug_no_platform();
59 |             Ok((path, ErrorKind::NoPlatform))
60 |         }
61 |     }
62 | }
63 | 


--------------------------------------------------------------------------------
/crates/volta-core/src/run/pnpm.rs:
--------------------------------------------------------------------------------
 1 | use std::env;
 2 | use std::ffi::OsString;
 3 | 
 4 | use super::executor::{Executor, ToolCommand, ToolKind};
 5 | use super::{debug_active_image, debug_no_platform, RECURSION_ENV_VAR};
 6 | use crate::error::{ErrorKind, Fallible};
 7 | use crate::platform::{Platform, Source, System};
 8 | use crate::session::{ActivityKind, Session};
 9 | 
10 | pub(super) fn command(args: &[OsString], session: &mut Session) -> Fallible<Executor> {
11 |     session.add_event_start(ActivityKind::Pnpm);
12 |     // Don't re-evaluate the context or global install interception if this is a recursive call
13 |     let platform = match env::var_os(RECURSION_ENV_VAR) {
14 |         Some(_) => None,
15 |         None => {
16 |             // FIXME: Figure out how to intercept pnpm global commands properly.
17 |             // This guard prevents all global commands from running, it should
18 |             // be removed when we fully implement global command interception.
19 |             let is_global = args.iter().any(|f| f == "--global" || f == "-g");
20 |             if is_global {
21 |                 return Err(ErrorKind::Unimplemented {
22 |                     feature: "pnpm global commands".into(),
23 |                 }
24 |                 .into());
25 |             }
26 | 
27 |             Platform::current(session)?
28 |         }
29 |     };
30 | 
31 |     Ok(ToolCommand::new("pnpm", args, platform, ToolKind::Pnpm).into())
32 | }
33 | 
34 | /// Determine the execution context (PATH and failure error message) for pnpm
35 | pub(super) fn execution_context(
36 |     platform: Option<Platform>,
37 |     session: &mut Session,
38 | ) -> Fallible<(OsString, ErrorKind)> {
39 |     match platform {
40 |         Some(plat) => {
41 |             validate_platform_pnpm(&plat)?;
42 | 
43 |             let image = plat.checkout(session)?;
44 |             let path = image.path()?;
45 |             debug_active_image(&image);
46 | 
47 |             Ok((path, ErrorKind::BinaryExecError))
48 |         }
49 |         None => {
50 |             let path = System::path()?;
51 |             debug_no_platform();
52 |             Ok((path, ErrorKind::NoPlatform))
53 |         }
54 |     }
55 | }
56 | 
57 | fn validate_platform_pnpm(platform: &Platform) -> Fallible<()> {
58 |     match &platform.pnpm {
59 |         Some(_) => Ok(()),
60 |         None => match platform.node.source {
61 |             Source::Project => Err(ErrorKind::NoProjectPnpm.into()),
62 |             Source::Default | Source::Binary => Err(ErrorKind::NoDefaultPnpm.into()),
63 |             Source::CommandLine => Err(ErrorKind::NoCommandLinePnpm.into()),
64 |         },
65 |     }
66 | }
67 | 


--------------------------------------------------------------------------------
/crates/volta-core/src/run/yarn.rs:
--------------------------------------------------------------------------------
 1 | use std::env;
 2 | use std::ffi::OsString;
 3 | 
 4 | use super::executor::{Executor, ToolCommand, ToolKind};
 5 | use super::parser::CommandArg;
 6 | use super::{debug_active_image, debug_no_platform, RECURSION_ENV_VAR};
 7 | use crate::error::{ErrorKind, Fallible};
 8 | use crate::platform::{Platform, Source, System};
 9 | use crate::session::{ActivityKind, Session};
10 | 
11 | /// Build an `Executor` for Yarn
12 | ///
13 | /// If the command is a global add or remove and we have a default platform available, then we will
14 | /// use custom logic to ensure that the package is correctly installed / uninstalled in the Volta
15 | /// directory.
16 | ///
17 | /// If the command is _not_ a global add / remove or we don't have a default platform, then
18 | /// we will allow Yarn to execute the command as usual.
19 | pub(super) fn command(args: &[OsString], session: &mut Session) -> Fallible<Executor> {
20 |     session.add_event_start(ActivityKind::Yarn);
21 |     // Don't re-evaluate the context or global install interception if this is a recursive call
22 |     let platform = match env::var_os(RECURSION_ENV_VAR) {
23 |         Some(_) => None,
24 |         None => {
25 |             if let CommandArg::Global(cmd) = CommandArg::for_yarn(args) {
26 |                 // For globals, only intercept if the default platform exists
27 |                 if let Some(default_platform) = session.default_platform()? {
28 |                     return cmd.executor(default_platform);
29 |                 }
30 |             }
31 | 
32 |             Platform::current(session)?
33 |         }
34 |     };
35 | 
36 |     Ok(ToolCommand::new("yarn", args, platform, ToolKind::Yarn).into())
37 | }
38 | 
39 | /// Determine the execution context (PATH and failure error message) for Yarn
40 | pub(super) fn execution_context(
41 |     platform: Option<Platform>,
42 |     session: &mut Session,
43 | ) -> Fallible<(OsString, ErrorKind)> {
44 |     match platform {
45 |         Some(plat) => {
46 |             validate_platform_yarn(&plat)?;
47 | 
48 |             let image = plat.checkout(session)?;
49 |             let path = image.path()?;
50 |             debug_active_image(&image);
51 | 
52 |             Ok((path, ErrorKind::BinaryExecError))
53 |         }
54 |         None => {
55 |             let path = System::path()?;
56 |             debug_no_platform();
57 |             Ok((path, ErrorKind::NoPlatform))
58 |         }
59 |     }
60 | }
61 | 
62 | fn validate_platform_yarn(platform: &Platform) -> Fallible<()> {
63 |     match &platform.yarn {
64 |         Some(_) => Ok(()),
65 |         None => match platform.node.source {
66 |             Source::Project => Err(ErrorKind::NoProjectYarn.into()),
67 |             Source::Default | Source::Binary => Err(ErrorKind::NoDefaultYarn.into()),
68 |             Source::CommandLine => Err(ErrorKind::NoCommandLineYarn.into()),
69 |         },
70 |     }
71 | }
72 | 


--------------------------------------------------------------------------------
/crates/volta-core/src/signal.rs:
--------------------------------------------------------------------------------
 1 | use std::process::exit;
 2 | use std::sync::atomic::{AtomicBool, Ordering};
 3 | 
 4 | use log::debug;
 5 | 
 6 | static SHIM_HAS_CONTROL: AtomicBool = AtomicBool::new(false);
 7 | const INTERRUPTED_EXIT_CODE: i32 = 130;
 8 | 
 9 | pub fn pass_control_to_shim() {
10 |     SHIM_HAS_CONTROL.store(true, Ordering::SeqCst);
11 | }
12 | 
13 | pub fn setup_signal_handler() {
14 |     let result = ctrlc::set_handler(|| {
15 |         if !SHIM_HAS_CONTROL.load(Ordering::SeqCst) {
16 |             exit(INTERRUPTED_EXIT_CODE);
17 |         }
18 |     });
19 | 
20 |     if result.is_err() {
21 |         debug!("Unable to set Ctrl+C handler, SIGINT will not be handled correctly");
22 |     }
23 | }
24 | 


--------------------------------------------------------------------------------
/crates/volta-core/src/tool/node/metadata.rs:
--------------------------------------------------------------------------------
 1 | use std::collections::HashSet;
 2 | 
 3 | use super::NODE_DISTRO_IDENTIFIER;
 4 | #[cfg(any(
 5 |     all(target_os = "macos", target_arch = "aarch64"),
 6 |     all(target_os = "windows", target_arch = "aarch64")
 7 | ))]
 8 | use super::NODE_DISTRO_IDENTIFIER_FALLBACK;
 9 | use crate::version::{option_version_serde, version_serde};
10 | use node_semver::Version;
11 | use serde::{Deserialize, Deserializer};
12 | 
13 | /// The index of the public Node server.
14 | pub struct NodeIndex {
15 |     pub(super) entries: Vec<NodeEntry>,
16 | }
17 | 
18 | #[derive(Debug)]
19 | pub struct NodeEntry {
20 |     pub version: Version,
21 |     pub lts: bool,
22 | }
23 | 
24 | #[derive(Deserialize)]
25 | pub struct RawNodeIndex(Vec<RawNodeEntry>);
26 | 
27 | #[derive(Deserialize)]
28 | pub struct RawNodeEntry {
29 |     #[serde(with = "version_serde")]
30 |     version: Version,
31 |     #[serde(default)] // handles Option
32 |     #[serde(with = "option_version_serde")]
33 |     npm: Option<Version>,
34 |     files: HashSet<String>,
35 |     #[serde(deserialize_with = "lts_version_serde")]
36 |     lts: bool,
37 | }
38 | 
39 | impl From<RawNodeIndex> for NodeIndex {
40 |     fn from(raw: RawNodeIndex) -> NodeIndex {
41 |         let entries = raw
42 |             .0
43 |             .into_iter()
44 |             .filter_map(|entry| {
45 |                 #[cfg(not(any(
46 |                     all(target_os = "macos", target_arch = "aarch64"),
47 |                     all(target_os = "windows", target_arch = "aarch64")
48 |                 )))]
49 |                 if entry.npm.is_some() && entry.files.contains(NODE_DISTRO_IDENTIFIER) {
50 |                     Some(NodeEntry {
51 |                         version: entry.version,
52 |                         lts: entry.lts,
53 |                     })
54 |                 } else {
55 |                     None
56 |                 }
57 | 
58 |                 #[cfg(any(
59 |                     all(target_os = "macos", target_arch = "aarch64"),
60 |                     all(target_os = "windows", target_arch = "aarch64")
61 |                 ))]
62 |                 if entry.npm.is_some()
63 |                     && (entry.files.contains(NODE_DISTRO_IDENTIFIER)
64 |                         || entry.files.contains(NODE_DISTRO_IDENTIFIER_FALLBACK))
65 |                 {
66 |                     Some(NodeEntry {
67 |                         version: entry.version,
68 |                         lts: entry.lts,
69 |                     })
70 |                 } else {
71 |                     None
72 |                 }
73 |             })
74 |             .collect();
75 | 
76 |         NodeIndex { entries }
77 |     }
78 | }
79 | 
80 | #[allow(clippy::unnecessary_wraps)] // Needs to match the API expected by Serde
81 | fn lts_version_serde<'de, D>(deserializer: D) -> Result<bool, D::Error>
82 | where
83 |     D: Deserializer<'de>,
84 | {
85 |     match String::deserialize(deserializer) {
86 |         Ok(_) => Ok(true),
87 |         Err(_) => Ok(false),
88 |     }
89 | }
90 | 


--------------------------------------------------------------------------------
/crates/volta-core/src/tool/npm/resolve.rs:
--------------------------------------------------------------------------------
 1 | //! Provides resolution of npm Version requirements into specific versions
 2 | 
 3 | use super::super::registry::{
 4 |     fetch_npm_registry, public_registry_index, PackageDetails, PackageIndex,
 5 | };
 6 | use crate::error::{ErrorKind, Fallible};
 7 | use crate::hook::ToolHooks;
 8 | use crate::session::Session;
 9 | use crate::tool::Npm;
10 | use crate::version::{VersionSpec, VersionTag};
11 | use log::debug;
12 | use node_semver::{Range, Version};
13 | 
14 | pub fn resolve(matching: VersionSpec, session: &mut Session) -> Fallible<Option<Version>> {
15 |     let hooks = session.hooks()?.npm();
16 |     match matching {
17 |         VersionSpec::Semver(requirement) => resolve_semver(requirement, hooks).map(Some),
18 |         VersionSpec::Exact(version) => Ok(Some(version)),
19 |         VersionSpec::None | VersionSpec::Tag(VersionTag::Latest) => {
20 |             resolve_tag("latest", hooks).map(Some)
21 |         }
22 |         VersionSpec::Tag(VersionTag::Custom(tag)) if tag == "bundled" => Ok(None),
23 |         VersionSpec::Tag(tag) => resolve_tag(&tag.to_string(), hooks).map(Some),
24 |     }
25 | }
26 | 
27 | fn fetch_npm_index(hooks: Option<&ToolHooks<Npm>>) -> Fallible<(String, PackageIndex)> {
28 |     let url = match hooks {
29 |         Some(&ToolHooks {
30 |             index: Some(ref hook),
31 |             ..
32 |         }) => {
33 |             debug!("Using npm.index hook to determine npm index URL");
34 |             hook.resolve("npm")?
35 |         }
36 |         _ => public_registry_index("npm"),
37 |     };
38 | 
39 |     fetch_npm_registry(url, "npm")
40 | }
41 | 
42 | fn resolve_tag(tag: &str, hooks: Option<&ToolHooks<Npm>>) -> Fallible<Version> {
43 |     let (url, mut index) = fetch_npm_index(hooks)?;
44 | 
45 |     match index.tags.remove(tag) {
46 |         Some(version) => {
47 |             debug!("Found npm@{} matching tag '{}' from {}", version, tag, url);
48 |             Ok(version)
49 |         }
50 |         None => Err(ErrorKind::NpmVersionNotFound {
51 |             matching: tag.into(),
52 |         }
53 |         .into()),
54 |     }
55 | }
56 | 
57 | fn resolve_semver(matching: Range, hooks: Option<&ToolHooks<Npm>>) -> Fallible<Version> {
58 |     let (url, index) = fetch_npm_index(hooks)?;
59 | 
60 |     let details_opt = index
61 |         .entries
62 |         .into_iter()
63 |         .find(|PackageDetails { version, .. }| matching.satisfies(version));
64 | 
65 |     match details_opt {
66 |         Some(details) => {
67 |             debug!(
68 |                 "Found npm@{} matching requirement '{}' from {}",
69 |                 details.version, matching, url
70 |             );
71 |             Ok(details.version)
72 |         }
73 |         None => Err(ErrorKind::NpmVersionNotFound {
74 |             matching: matching.to_string(),
75 |         }
76 |         .into()),
77 |     }
78 | }
79 | 


--------------------------------------------------------------------------------
/crates/volta-core/src/tool/package/configure.rs:
--------------------------------------------------------------------------------
 1 | use std::path::PathBuf;
 2 | 
 3 | use super::manager::PackageManager;
 4 | use super::metadata::{BinConfig, PackageConfig, PackageManifest};
 5 | use crate::error::{ErrorKind, Fallible};
 6 | use crate::layout::volta_home;
 7 | use crate::platform::{Image, PlatformSpec};
 8 | use crate::shim;
 9 | use crate::tool::check_shim_reachable;
10 | 
11 | /// Read the manifest for the package being installed
12 | pub(super) fn parse_manifest(
13 |     package_name: &str,
14 |     staging_dir: PathBuf,
15 |     manager: PackageManager,
16 | ) -> Fallible<PackageManifest> {
17 |     let mut package_dir = manager.source_dir(staging_dir);
18 |     package_dir.push(package_name);
19 | 
20 |     PackageManifest::for_dir(package_name, &package_dir)
21 | }
22 | 
23 | /// Generate configuration files and shims for the package and each of its bins
24 | pub(super) fn write_config_and_shims(
25 |     name: &str,
26 |     manifest: &PackageManifest,
27 |     image: &Image,
28 |     manager: PackageManager,
29 | ) -> Fallible<()> {
30 |     validate_bins(name, manifest)?;
31 | 
32 |     let platform = PlatformSpec {
33 |         node: image.node.value.clone(),
34 |         npm: image.npm.clone().map(|s| s.value),
35 |         pnpm: image.pnpm.clone().map(|s| s.value),
36 |         yarn: image.yarn.clone().map(|s| s.value),
37 |     };
38 | 
39 |     // Generate the shims and bin configs for each bin provided by the package
40 |     for bin_name in &manifest.bin {
41 |         shim::create(bin_name)?;
42 |         check_shim_reachable(bin_name);
43 | 
44 |         BinConfig {
45 |             name: bin_name.clone(),
46 |             package: name.into(),
47 |             version: manifest.version.clone(),
48 |             platform: platform.clone(),
49 |             manager,
50 |         }
51 |         .write()?;
52 |     }
53 | 
54 |     // Write the config for the package
55 |     PackageConfig {
56 |         name: name.into(),
57 |         version: manifest.version.clone(),
58 |         platform,
59 |         bins: manifest.bin.clone(),
60 |         manager,
61 |     }
62 |     .write()?;
63 | 
64 |     Ok(())
65 | }
66 | 
67 | /// Validate that we aren't attempting to install a bin that is already installed by
68 | /// another package.
69 | fn validate_bins(package_name: &str, manifest: &PackageManifest) -> Fallible<()> {
70 |     let home = volta_home()?;
71 |     for bin_name in &manifest.bin {
72 |         // Check for name conflicts with already-installed bins
73 |         // Some packages may install bins with the same name
74 |         if let Ok(config) = BinConfig::from_file(home.default_tool_bin_config(bin_name)) {
75 |             // The file exists, so there is a bin with this name
76 |             // That is okay iff it came from the package that is currently being installed
77 |             if package_name != config.package {
78 |                 return Err(ErrorKind::BinaryAlreadyInstalled {
79 |                     bin_name: bin_name.into(),
80 |                     existing_package: config.package,
81 |                     new_package: package_name.into(),
82 |                 }
83 |                 .into());
84 |             }
85 |         }
86 |     }
87 | 
88 |     Ok(())
89 | }
90 | 


--------------------------------------------------------------------------------
/crates/volta-core/src/tool/package/install.rs:
--------------------------------------------------------------------------------
 1 | use std::path::PathBuf;
 2 | 
 3 | use super::manager::PackageManager;
 4 | use crate::command::create_command;
 5 | use crate::error::{Context, ErrorKind, Fallible};
 6 | use crate::platform::Image;
 7 | use crate::style::progress_spinner;
 8 | use log::debug;
 9 | 
10 | /// Use `npm install --global` to install the package
11 | ///
12 | /// Sets the environment variable `npm_config_prefix` to redirect the install to the Volta
13 | /// data directory, taking advantage of the standard global install behavior with a custom
14 | /// location
15 | pub(super) fn run_global_install(
16 |     package: String,
17 |     staging_dir: PathBuf,
18 |     platform_image: &Image,
19 | ) -> Fallible<()> {
20 |     let mut command = create_command("npm");
21 |     command.args([
22 |         "install",
23 |         "--global",
24 |         "--loglevel=warn",
25 |         "--no-update-notifier",
26 |         "--no-audit",
27 |     ]);
28 |     command.arg(&package);
29 |     command.env("PATH", platform_image.path()?);
30 |     PackageManager::Npm.setup_global_command(&mut command, staging_dir);
31 | 
32 |     debug!("Installing {} with command: {:?}", package, command);
33 |     let spinner = progress_spinner(format!("Installing {}", package));
34 |     let output_result = command
35 |         .output()
36 |         .with_context(|| ErrorKind::PackageInstallFailed {
37 |             package: package.clone(),
38 |         });
39 |     spinner.finish_and_clear();
40 |     let output = output_result?;
41 | 
42 |     let stderr = String::from_utf8_lossy(&output.stderr);
43 |     debug!("[install stderr]\n{}", stderr);
44 |     debug!(
45 |         "[install stdout]\n{}",
46 |         String::from_utf8_lossy(&output.stdout)
47 |     );
48 | 
49 |     if output.status.success() {
50 |         Ok(())
51 |     } else if stderr.contains("code E404") {
52 |         // npm outputs "code E404" as part of the error output when a package couldn't be found
53 |         // Detect that and show a nicer error message (since we likely know the problem in that case)
54 |         Err(ErrorKind::PackageNotFound { package }.into())
55 |     } else {
56 |         Err(ErrorKind::PackageInstallFailed { package }.into())
57 |     }
58 | }
59 | 


--------------------------------------------------------------------------------
/crates/volta-core/src/tool/package/uninstall.rs:
--------------------------------------------------------------------------------
  1 | use super::metadata::{BinConfig, PackageConfig};
  2 | use crate::error::{Context, ErrorKind, Fallible};
  3 | use crate::fs::{
  4 |     dir_entry_match, ok_if_not_found, read_dir_eager, remove_dir_if_exists, remove_file_if_exists,
  5 | };
  6 | use crate::layout::volta_home;
  7 | use crate::shim;
  8 | use crate::style::success_prefix;
  9 | use crate::sync::VoltaLock;
 10 | use log::{info, warn};
 11 | 
 12 | /// Uninstalls the specified package.
 13 | ///
 14 | /// This removes:
 15 | ///
 16 | /// - The JSON configuration files for both the package and its bins
 17 | /// - The shims for the package bins
 18 | /// - The package directory itself
 19 | pub fn uninstall(name: &str) -> Fallible<()> {
 20 |     let home = volta_home()?;
 21 |     // Acquire a lock on the Volta directory, if possible, to prevent concurrent changes
 22 |     let _lock = VoltaLock::acquire();
 23 | 
 24 |     // If the package config file exists, use that to remove any installed bins and shims
 25 |     let package_config_file = home.default_package_config_file(name);
 26 | 
 27 |     let package_found = match PackageConfig::from_file_if_exists(&package_config_file)? {
 28 |         None => {
 29 |             // there is no package config - check for orphaned binaries
 30 |             let package_binary_list = binaries_from_package(name)?;
 31 |             if !package_binary_list.is_empty() {
 32 |                 for bin_name in package_binary_list {
 33 |                     remove_config_and_shim(&bin_name, name)?;
 34 |                 }
 35 |                 true
 36 |             } else {
 37 |                 false
 38 |             }
 39 |         }
 40 |         Some(package_config) => {
 41 |             for bin_name in package_config.bins {
 42 |                 remove_config_and_shim(&bin_name, name)?;
 43 |             }
 44 | 
 45 |             remove_file_if_exists(package_config_file)?;
 46 |             true
 47 |         }
 48 |     };
 49 | 
 50 |     remove_shared_link_dir(name)?;
 51 | 
 52 |     // Remove the package directory itself
 53 |     let package_image_dir = home.package_image_dir(name);
 54 |     remove_dir_if_exists(package_image_dir)?;
 55 | 
 56 |     if package_found {
 57 |         info!("{} package '{}' uninstalled", success_prefix(), name);
 58 |     } else {
 59 |         warn!("No package '{}' found to uninstall", name);
 60 |     }
 61 | 
 62 |     Ok(())
 63 | }
 64 | 
 65 | /// Remove a shim and its associated configuration file
 66 | fn remove_config_and_shim(bin_name: &str, pkg_name: &str) -> Fallible<()> {
 67 |     shim::delete(bin_name)?;
 68 |     let config_file = volta_home()?.default_tool_bin_config(bin_name);
 69 |     remove_file_if_exists(config_file)?;
 70 |     info!(
 71 |         "Removed executable '{}' installed by '{}'",
 72 |         bin_name, pkg_name
 73 |     );
 74 |     Ok(())
 75 | }
 76 | 
 77 | /// Reads the contents of a directory and returns a Vec containing the names of
 78 | /// all the binaries installed by the given package.
 79 | fn binaries_from_package(package: &str) -> Fallible<Vec<String>> {
 80 |     let bin_config_dir = volta_home()?.default_bin_dir();
 81 | 
 82 |     dir_entry_match(bin_config_dir, |entry| {
 83 |         let path = entry.path();
 84 |         if let Ok(config) = BinConfig::from_file(path) {
 85 |             if config.package == package {
 86 |                 return Some(config.name);
 87 |             }
 88 |         }
 89 |         None
 90 |     })
 91 |     .or_else(ok_if_not_found)
 92 |     .with_context(|| ErrorKind::ReadBinConfigDirError {
 93 |         dir: bin_config_dir.to_owned(),
 94 |     })
 95 | }
 96 | 
 97 | /// Remove the link to the package in the shared lib directory
 98 | ///
 99 | /// For scoped packages, if the scope directory is now empty, it will also be removed
100 | fn remove_shared_link_dir(name: &str) -> Fallible<()> {
101 |     // Remove the link in the shared package directory, if it exists
102 |     let mut shared_lib_dir = volta_home()?.shared_lib_dir(name);
103 |     remove_dir_if_exists(&shared_lib_dir)?;
104 | 
105 |     // For scoped packages, clean up the scope directory if it is now empty
106 |     if name.starts_with('@') {
107 |         shared_lib_dir.pop();
108 | 
109 |         if let Ok(mut entries) = read_dir_eager(&shared_lib_dir) {
110 |             if entries.next().is_none() {
111 |                 remove_dir_if_exists(&shared_lib_dir)?;
112 |             }
113 |         }
114 |     }
115 | 
116 |     Ok(())
117 | }
118 | 


--------------------------------------------------------------------------------
/crates/volta-core/src/tool/pnpm/mod.rs:
--------------------------------------------------------------------------------
  1 | use node_semver::Version;
  2 | use std::fmt::{self, Display};
  3 | 
  4 | use crate::error::{ErrorKind, Fallible};
  5 | use crate::inventory::pnpm_available;
  6 | use crate::session::Session;
  7 | use crate::style::tool_version;
  8 | use crate::sync::VoltaLock;
  9 | 
 10 | use super::{
 11 |     check_fetched, check_shim_reachable, debug_already_fetched, info_fetched, info_installed,
 12 |     info_pinned, info_project_version, FetchStatus, Tool,
 13 | };
 14 | 
 15 | mod fetch;
 16 | mod resolve;
 17 | 
 18 | pub use resolve::resolve;
 19 | 
 20 | /// The Tool implementation for fetching and installing pnpm
 21 | pub struct Pnpm {
 22 |     pub(super) version: Version,
 23 | }
 24 | 
 25 | impl Pnpm {
 26 |     pub fn new(version: Version) -> Self {
 27 |         Pnpm { version }
 28 |     }
 29 | 
 30 |     pub fn archive_basename(version: &str) -> String {
 31 |         format!("pnpm-{}", version)
 32 |     }
 33 | 
 34 |     pub fn archive_filename(version: &str) -> String {
 35 |         format!("{}.tgz", Pnpm::archive_basename(version))
 36 |     }
 37 | 
 38 |     pub(crate) fn ensure_fetched(&self, session: &mut Session) -> Fallible<()> {
 39 |         match check_fetched(|| pnpm_available(&self.version))? {
 40 |             FetchStatus::AlreadyFetched => {
 41 |                 debug_already_fetched(self);
 42 |                 Ok(())
 43 |             }
 44 |             FetchStatus::FetchNeeded(_lock) => fetch::fetch(&self.version, session.hooks()?.pnpm()),
 45 |         }
 46 |     }
 47 | }
 48 | 
 49 | impl Tool for Pnpm {
 50 |     fn fetch(self: Box<Self>, session: &mut Session) -> Fallible<()> {
 51 |         self.ensure_fetched(session)?;
 52 | 
 53 |         info_fetched(self);
 54 |         Ok(())
 55 |     }
 56 | 
 57 |     fn install(self: Box<Self>, session: &mut Session) -> Fallible<()> {
 58 |         // Acquire a lock on the Volta directory, if possible, to prevent concurrent changes
 59 |         let _lock = VoltaLock::acquire();
 60 |         self.ensure_fetched(session)?;
 61 | 
 62 |         session
 63 |             .toolchain_mut()?
 64 |             .set_active_pnpm(Some(self.version.clone()))?;
 65 | 
 66 |         info_installed(&self);
 67 |         check_shim_reachable("pnpm");
 68 | 
 69 |         if let Ok(Some(project)) = session.project_platform() {
 70 |             if let Some(pnpm) = &project.pnpm {
 71 |                 info_project_version(tool_version("pnpm", pnpm), &self);
 72 |             }
 73 |         }
 74 |         Ok(())
 75 |     }
 76 | 
 77 |     fn pin(self: Box<Self>, session: &mut Session) -> Fallible<()> {
 78 |         if session.project()?.is_some() {
 79 |             self.ensure_fetched(session)?;
 80 | 
 81 |             // Note: We know this will succeed, since we checked above
 82 |             let project = session.project_mut()?.unwrap();
 83 |             project.pin_pnpm(Some(self.version.clone()))?;
 84 | 
 85 |             info_pinned(self);
 86 |             Ok(())
 87 |         } else {
 88 |             Err(ErrorKind::NotInPackage.into())
 89 |         }
 90 |     }
 91 | }
 92 | 
 93 | impl Display for Pnpm {
 94 |     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 95 |         f.write_str(&tool_version("pnpm", &self.version))
 96 |     }
 97 | }
 98 | 
 99 | #[cfg(test)]
100 | mod tests {
101 |     use super::*;
102 | 
103 |     #[test]
104 |     fn test_pnpm_archive_basename() {
105 |         assert_eq!(Pnpm::archive_basename("1.2.3"), "pnpm-1.2.3");
106 |     }
107 | 
108 |     #[test]
109 |     fn test_pnpm_archive_filename() {
110 |         assert_eq!(Pnpm::archive_filename("1.2.3"), "pnpm-1.2.3.tgz");
111 |     }
112 | }
113 | 


--------------------------------------------------------------------------------
/crates/volta-core/src/tool/pnpm/resolve.rs:
--------------------------------------------------------------------------------
 1 | use log::debug;
 2 | use node_semver::{Range, Version};
 3 | 
 4 | use crate::error::{ErrorKind, Fallible};
 5 | use crate::hook::ToolHooks;
 6 | use crate::session::Session;
 7 | use crate::tool::registry::{fetch_npm_registry, public_registry_index, PackageIndex};
 8 | use crate::tool::{PackageDetails, Pnpm};
 9 | use crate::version::{VersionSpec, VersionTag};
10 | 
11 | pub fn resolve(matching: VersionSpec, session: &mut Session) -> Fallible<Version> {
12 |     let hooks = session.hooks()?.pnpm();
13 |     match matching {
14 |         VersionSpec::Semver(requirement) => resolve_semver(requirement, hooks),
15 |         VersionSpec::Exact(version) => Ok(version),
16 |         VersionSpec::None | VersionSpec::Tag(VersionTag::Latest) => resolve_tag("latest", hooks),
17 |         VersionSpec::Tag(tag) => resolve_tag(&tag.to_string(), hooks),
18 |     }
19 | }
20 | 
21 | fn resolve_tag(tag: &str, hooks: Option<&ToolHooks<Pnpm>>) -> Fallible<Version> {
22 |     let (url, mut index) = fetch_pnpm_index(hooks)?;
23 | 
24 |     match index.tags.remove(tag) {
25 |         Some(version) => {
26 |             debug!("Found pnpm@{} matching tag '{}' from {}", version, tag, url);
27 |             Ok(version)
28 |         }
29 |         None => Err(ErrorKind::PnpmVersionNotFound {
30 |             matching: tag.into(),
31 |         }
32 |         .into()),
33 |     }
34 | }
35 | 
36 | fn resolve_semver(matching: Range, hooks: Option<&ToolHooks<Pnpm>>) -> Fallible<Version> {
37 |     let (url, index) = fetch_pnpm_index(hooks)?;
38 | 
39 |     let details_opt = index
40 |         .entries
41 |         .into_iter()
42 |         .find(|PackageDetails { version, .. }| matching.satisfies(version));
43 | 
44 |     match details_opt {
45 |         Some(details) => {
46 |             debug!(
47 |                 "Found pnpm@{} matching requirement '{}' from {}",
48 |                 details.version, matching, url
49 |             );
50 |             Ok(details.version)
51 |         }
52 |         None => Err(ErrorKind::PnpmVersionNotFound {
53 |             matching: matching.to_string(),
54 |         }
55 |         .into()),
56 |     }
57 | }
58 | 
59 | /// Fetch the index of available pnpm versions from the npm registry
60 | fn fetch_pnpm_index(hooks: Option<&ToolHooks<Pnpm>>) -> Fallible<(String, PackageIndex)> {
61 |     let url = match hooks {
62 |         Some(&ToolHooks {
63 |             index: Some(ref hook),
64 |             ..
65 |         }) => {
66 |             debug!("Using pnpm.index hook to determine pnpm index URL");
67 |             hook.resolve("pnpm")?
68 |         }
69 |         _ => public_registry_index("pnpm"),
70 |     };
71 | 
72 |     fetch_npm_registry(url, "pnpm")
73 | }
74 | 


--------------------------------------------------------------------------------
/crates/volta-core/src/tool/yarn/metadata.rs:
--------------------------------------------------------------------------------
 1 | use std::collections::BTreeSet;
 2 | 
 3 | use crate::version::version_serde;
 4 | use node_semver::Version;
 5 | use serde::Deserialize;
 6 | 
 7 | /// The public Yarn index.
 8 | pub struct YarnIndex {
 9 |     pub(super) entries: BTreeSet<Version>,
10 | }
11 | 
12 | #[derive(Deserialize)]
13 | pub struct RawYarnIndex(Vec<RawYarnEntry>);
14 | 
15 | #[derive(Deserialize)]
16 | pub struct RawYarnEntry {
17 |     /// Yarn releases are given a tag name of the form "v$version" where $version
18 |     /// is the release's version string.
19 |     #[serde(with = "version_serde")]
20 |     pub tag_name: Version,
21 | 
22 |     /// The GitHub API provides a list of assets. Some Yarn releases don't include
23 |     /// a tarball, so we don't support them and remove them from the set of available
24 |     /// Yarn versions.
25 |     pub assets: Vec<RawYarnAsset>,
26 | }
27 | 
28 | impl RawYarnEntry {
29 |     /// Is this entry a full release, i.e., does this entry's asset list include a
30 |     /// proper release tarball?
31 |     fn is_full_release(&self) -> bool {
32 |         let release_filename = &format!("yarn-v{}.tar.gz", self.tag_name)[..];
33 |         self.assets
34 |             .iter()
35 |             .any(|raw_asset| raw_asset.name == release_filename)
36 |     }
37 | }
38 | 
39 | #[derive(Deserialize)]
40 | pub struct RawYarnAsset {
41 |     /// The filename of an asset included in a Yarn GitHub release.
42 |     pub name: String,
43 | }
44 | 
45 | impl From<RawYarnIndex> for YarnIndex {
46 |     fn from(raw: RawYarnIndex) -> YarnIndex {
47 |         let mut entries = BTreeSet::new();
48 |         for entry in raw.0 {
49 |             if entry.is_full_release() {
50 |                 entries.insert(entry.tag_name);
51 |             }
52 |         }
53 |         YarnIndex { entries }
54 |     }
55 | }
56 | 


--------------------------------------------------------------------------------
/crates/volta-core/src/tool/yarn/mod.rs:
--------------------------------------------------------------------------------
  1 | use std::fmt::{self, Display};
  2 | 
  3 | use super::{
  4 |     check_fetched, check_shim_reachable, debug_already_fetched, info_fetched, info_installed,
  5 |     info_pinned, info_project_version, FetchStatus, Tool,
  6 | };
  7 | use crate::error::{ErrorKind, Fallible};
  8 | use crate::inventory::yarn_available;
  9 | use crate::session::Session;
 10 | use crate::style::tool_version;
 11 | use crate::sync::VoltaLock;
 12 | use node_semver::Version;
 13 | 
 14 | mod fetch;
 15 | mod metadata;
 16 | mod resolve;
 17 | 
 18 | pub use resolve::resolve;
 19 | 
 20 | /// The Tool implementation for fetching and installing Yarn
 21 | pub struct Yarn {
 22 |     pub(super) version: Version,
 23 | }
 24 | 
 25 | impl Yarn {
 26 |     pub fn new(version: Version) -> Self {
 27 |         Yarn { version }
 28 |     }
 29 | 
 30 |     pub fn archive_basename(version: &str) -> String {
 31 |         format!("yarn-v{}", version)
 32 |     }
 33 | 
 34 |     pub fn archive_filename(version: &str) -> String {
 35 |         format!("{}.tar.gz", Yarn::archive_basename(version))
 36 |     }
 37 | 
 38 |     pub(crate) fn ensure_fetched(&self, session: &mut Session) -> Fallible<()> {
 39 |         match check_fetched(|| yarn_available(&self.version))? {
 40 |             FetchStatus::AlreadyFetched => {
 41 |                 debug_already_fetched(self);
 42 |                 Ok(())
 43 |             }
 44 |             FetchStatus::FetchNeeded(_lock) => fetch::fetch(&self.version, session.hooks()?.yarn()),
 45 |         }
 46 |     }
 47 | }
 48 | 
 49 | impl Tool for Yarn {
 50 |     fn fetch(self: Box<Self>, session: &mut Session) -> Fallible<()> {
 51 |         self.ensure_fetched(session)?;
 52 | 
 53 |         info_fetched(self);
 54 |         Ok(())
 55 |     }
 56 |     fn install(self: Box<Self>, session: &mut Session) -> Fallible<()> {
 57 |         // Acquire a lock on the Volta directory, if possible, to prevent concurrent changes
 58 |         let _lock = VoltaLock::acquire();
 59 |         self.ensure_fetched(session)?;
 60 | 
 61 |         session
 62 |             .toolchain_mut()?
 63 |             .set_active_yarn(Some(self.version.clone()))?;
 64 | 
 65 |         info_installed(&self);
 66 |         check_shim_reachable("yarn");
 67 | 
 68 |         if let Ok(Some(project)) = session.project_platform() {
 69 |             if let Some(yarn) = &project.yarn {
 70 |                 info_project_version(tool_version("yarn", yarn), &self);
 71 |             }
 72 |         }
 73 |         Ok(())
 74 |     }
 75 |     fn pin(self: Box<Self>, session: &mut Session) -> Fallible<()> {
 76 |         if session.project()?.is_some() {
 77 |             self.ensure_fetched(session)?;
 78 | 
 79 |             // Note: We know this will succeed, since we checked above
 80 |             let project = session.project_mut()?.unwrap();
 81 |             project.pin_yarn(Some(self.version.clone()))?;
 82 | 
 83 |             info_pinned(self);
 84 |             Ok(())
 85 |         } else {
 86 |             Err(ErrorKind::NotInPackage.into())
 87 |         }
 88 |     }
 89 | }
 90 | 
 91 | impl Display for Yarn {
 92 |     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 93 |         f.write_str(&tool_version("yarn", &self.version))
 94 |     }
 95 | }
 96 | 
 97 | #[cfg(test)]
 98 | mod tests {
 99 |     use super::*;
100 | 
101 |     #[test]
102 |     fn test_yarn_archive_basename() {
103 |         assert_eq!(Yarn::archive_basename("1.2.3"), "yarn-v1.2.3");
104 |     }
105 | 
106 |     #[test]
107 |     fn test_yarn_archive_filename() {
108 |         assert_eq!(Yarn::archive_filename("1.2.3"), "yarn-v1.2.3.tar.gz");
109 |     }
110 | }
111 | 


--------------------------------------------------------------------------------
/crates/volta-core/src/version/serial.rs:
--------------------------------------------------------------------------------
 1 | use node_semver::{Range, SemverError};
 2 | 
 3 | // NOTE: using `parse_compat` here because the semver crate defaults to
 4 | // parsing in a cargo-compatible way. This is normally fine, except for
 5 | // 2 cases (that I know about):
 6 | //  * "1.2.3" parses as `^1.2.3` for cargo, but `=1.2.3` for Node
 7 | //  * `>1.2.3 <2.0.0` serializes to ">1.2.3, <2.0.0" for cargo (with the
 8 | //    comma), but ">1.2.3 <2.0.0" for Node (no comma, because Node parses
 9 | //    commas differently)
10 | //
11 | // Because we are parsing the version requirements from the command line,
12 | // then serializing them to pass to `npm view`, they need to be handled in
13 | // a Node-compatible way (or we get the wrong version info returned).
14 | pub fn parse_requirements(src: &str) -> Result<Range, SemverError> {
15 |     let src = src.trim().trim_start_matches('v');
16 | 
17 |     Range::parse(src)
18 | }
19 | 
20 | #[cfg(test)]
21 | pub mod tests {
22 | 
23 |     use crate::version::serial::parse_requirements;
24 |     use node_semver::Range;
25 | 
26 |     #[test]
27 |     fn test_parse_requirements() {
28 |         assert_eq!(
29 |             parse_requirements("1.2.3").unwrap(),
30 |             Range::parse("=1.2.3").unwrap()
31 |         );
32 |         assert_eq!(
33 |             parse_requirements("v1.5").unwrap(),
34 |             Range::parse("=1.5").unwrap()
35 |         );
36 |         assert_eq!(
37 |             parse_requirements("=1.2.3").unwrap(),
38 |             Range::parse("=1.2.3").unwrap()
39 |         );
40 |         assert_eq!(
41 |             parse_requirements("^1.2").unwrap(),
42 |             Range::parse("^1.2").unwrap()
43 |         );
44 |         assert_eq!(
45 |             parse_requirements(">=1.4").unwrap(),
46 |             Range::parse(">=1.4").unwrap()
47 |         );
48 |         assert_eq!(
49 |             parse_requirements("8.11 - 8.17 || 10.* || >= 12").unwrap(),
50 |             Range::parse("8.11 - 8.17 || 10.* || >= 12").unwrap()
51 |         );
52 |     }
53 | }
54 | 


--------------------------------------------------------------------------------
/crates/volta-layout-macro/Cargo.toml:
--------------------------------------------------------------------------------
 1 | [package]
 2 | name = "volta-layout-macro"
 3 | version = "0.1.0"
 4 | authors = ["David Herman <david.herman@gmail.com>"]
 5 | edition = "2021"
 6 | 
 7 | [lib]
 8 | proc-macro = true
 9 | 
10 | [dependencies]
11 | syn = "1.0.5"
12 | quote = "1.0.2"
13 | proc-macro2 = "1.0.2"
14 | 


--------------------------------------------------------------------------------
/crates/volta-layout-macro/src/lib.rs:
--------------------------------------------------------------------------------
 1 | #![recursion_limit = "128"]
 2 | 
 3 | extern crate proc_macro;
 4 | 
 5 | mod ast;
 6 | mod ir;
 7 | 
 8 | use crate::ast::Ast;
 9 | use proc_macro::TokenStream;
10 | use syn::parse_macro_input;
11 | 
12 | /// A macro for defining Volta directory layout hierarchies.
13 | ///
14 | /// The syntax of `layout!` takes the form:
15 | ///
16 | /// ```text,no_run
17 | /// layout! {
18 | ///     LayoutStruct*
19 | /// }
20 | /// ```
21 | ///
22 | /// The syntax of a `LayoutStruct` takes the form:
23 | ///
24 | /// ```text,no_run
25 | /// Attribute* Visibility "struct" Ident Directory
26 | /// ```
27 | ///
28 | /// The syntax of a `Directory` takes the form:
29 | ///
30 | /// ```text,no_run
31 | /// {
32 | ///     (FieldPrefix)FieldContents*
33 | /// }
34 | /// ```
35 | ///
36 | /// The syntax of a `FieldPrefix` takes the form:
37 | ///
38 | /// ```text,no_run
39 | /// LitStr ":" Ident
40 | /// ```
41 | ///
42 | /// The syntax of a `FieldContents` is either:
43 | ///
44 | /// ```text,no_run
45 | /// ";"
46 | /// ```
47 | ///
48 | /// or:
49 | ///
50 | /// ```text,no_run
51 | /// Directory
52 | /// ```
53 | #[proc_macro]
54 | pub fn layout(input: TokenStream) -> TokenStream {
55 |     let ast = parse_macro_input!(input as Ast);
56 |     let expanded = ast.compile();
57 |     TokenStream::from(expanded)
58 | }
59 | 


--------------------------------------------------------------------------------
/crates/volta-layout/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "volta-layout"
3 | version = "0.1.1"
4 | authors = ["Chuck Pierce <cpierce.grad@gmail.com>"]
5 | edition = "2021"
6 | 
7 | [dependencies]
8 | volta-layout-macro = { path = "../volta-layout-macro" }
9 | 


--------------------------------------------------------------------------------
/crates/volta-layout/src/lib.rs:
--------------------------------------------------------------------------------
 1 | #[macro_use]
 2 | mod macros;
 3 | 
 4 | pub mod v0;
 5 | pub mod v1;
 6 | pub mod v2;
 7 | pub mod v3;
 8 | pub mod v4;
 9 | 
10 | fn executable(name: &str) -> String {
11 |     format!("{}{}", name, std::env::consts::EXE_SUFFIX)
12 | }
13 | 


--------------------------------------------------------------------------------
/crates/volta-layout/src/macros.rs:
--------------------------------------------------------------------------------
 1 | macro_rules! path_buf {
 2 |     ($base:expr, $( $x:expr ), *) => {
 3 |         {
 4 |             let mut temp = $base;
 5 |             $(
 6 |                 temp.push($x);
 7 |             )*
 8 |             temp
 9 |         }
10 |     }
11 | }
12 | 


--------------------------------------------------------------------------------
/crates/volta-layout/src/v0.rs:
--------------------------------------------------------------------------------
  1 | use std::path::PathBuf;
  2 | 
  3 | use super::executable;
  4 | use volta_layout_macro::layout;
  5 | 
  6 | layout! {
  7 |     pub struct VoltaInstall {
  8 |         "shim[.exe]": shim_executable;
  9 |     }
 10 | 
 11 |     pub struct VoltaHome {
 12 |         "cache": cache_dir {
 13 |             "node": node_cache_dir {
 14 |                 "index.json": node_index_file;
 15 |                 "index.json.expires": node_index_expiry_file;
 16 |             }
 17 |         }
 18 |         "bin": shim_dir {}
 19 |         "log": log_dir {}
 20 |         "tools": tools_dir {
 21 |             "inventory": inventory_dir {
 22 |                 "node": node_inventory_dir {}
 23 |                 "packages": package_inventory_dir {}
 24 |                 "yarn": yarn_inventory_dir {}
 25 |             }
 26 |             "image": image_dir {
 27 |                 "node": node_image_root_dir {}
 28 |                 "yarn": yarn_image_root_dir {}
 29 |                 "packages": package_image_root_dir {}
 30 |             }
 31 |             "user": default_toolchain_dir {
 32 |                 "bins": default_bin_dir {}
 33 |                 "packages": default_package_dir {}
 34 |                 "platform.json": default_platform_file;
 35 |             }
 36 |         }
 37 |         "tmp": tmp_dir {}
 38 |         "hooks.json": default_hooks_file;
 39 |     }
 40 | }
 41 | 
 42 | impl VoltaHome {
 43 |     pub fn package_distro_file(&self, name: &str, version: &str) -> PathBuf {
 44 |         path_buf!(
 45 |             self.package_inventory_dir.clone(),
 46 |             format!("{}-{}.tgz", name, version)
 47 |         )
 48 |     }
 49 | 
 50 |     pub fn package_distro_shasum(&self, name: &str, version: &str) -> PathBuf {
 51 |         path_buf!(
 52 |             self.package_inventory_dir.clone(),
 53 |             format!("{}-{}.shasum", name, version)
 54 |         )
 55 |     }
 56 | 
 57 |     pub fn node_image_dir(&self, node: &str, npm: &str) -> PathBuf {
 58 |         path_buf!(self.node_image_root_dir.clone(), node, npm)
 59 |     }
 60 | 
 61 |     pub fn yarn_image_dir(&self, version: &str) -> PathBuf {
 62 |         path_buf!(self.yarn_image_root_dir.clone(), version)
 63 |     }
 64 | 
 65 |     pub fn yarn_image_bin_dir(&self, version: &str) -> PathBuf {
 66 |         path_buf!(self.yarn_image_dir(version), "bin")
 67 |     }
 68 | 
 69 |     pub fn package_image_dir(&self, name: &str, version: &str) -> PathBuf {
 70 |         path_buf!(self.package_image_root_dir.clone(), name, version)
 71 |     }
 72 | 
 73 |     pub fn default_package_config_file(&self, package_name: &str) -> PathBuf {
 74 |         path_buf!(
 75 |             self.default_package_dir.clone(),
 76 |             format!("{}.json", package_name)
 77 |         )
 78 |     }
 79 | 
 80 |     pub fn default_tool_bin_config(&self, bin_name: &str) -> PathBuf {
 81 |         path_buf!(self.default_bin_dir.clone(), format!("{}.json", bin_name))
 82 |     }
 83 | 
 84 |     pub fn node_npm_version_file(&self, version: &str) -> PathBuf {
 85 |         path_buf!(
 86 |             self.node_inventory_dir.clone(),
 87 |             format!("node-v{}-npm", version)
 88 |         )
 89 |     }
 90 | 
 91 |     pub fn shim_file(&self, toolname: &str) -> PathBuf {
 92 |         path_buf!(self.shim_dir.clone(), executable(toolname))
 93 |     }
 94 | }
 95 | 
 96 | #[cfg(windows)]
 97 | impl VoltaHome {
 98 |     pub fn shim_git_bash_script_file(&self, toolname: &str) -> PathBuf {
 99 |         path_buf!(self.shim_dir.clone(), toolname)
100 |     }
101 | 
102 |     pub fn node_image_bin_dir(&self, node: &str, npm: &str) -> PathBuf {
103 |         self.node_image_dir(node, npm)
104 |     }
105 | }
106 | 
107 | #[cfg(windows)]
108 | impl VoltaInstall {
109 |     pub fn bin_dir(&self) -> PathBuf {
110 |         path_buf!(self.root.clone(), "bin")
111 |     }
112 | }
113 | 
114 | #[cfg(unix)]
115 | impl VoltaHome {
116 |     pub fn node_image_bin_dir(&self, node: &str, npm: &str) -> PathBuf {
117 |         path_buf!(self.node_image_dir(node, npm), "bin")
118 |     }
119 | }
120 | 


--------------------------------------------------------------------------------
/crates/volta-layout/src/v1.rs:
--------------------------------------------------------------------------------
  1 | use std::path::PathBuf;
  2 | 
  3 | use super::executable;
  4 | use volta_layout_macro::layout;
  5 | 
  6 | layout! {
  7 |     pub struct VoltaInstall {
  8 |         "volta-shim[.exe]": shim_executable;
  9 |         "volta[.exe]": main_executable;
 10 |         "volta-migrate[.exe]": migrate_executable;
 11 |     }
 12 | 
 13 |     pub struct VoltaHome {
 14 |         "cache": cache_dir {
 15 |             "node": node_cache_dir {
 16 |                 "index.json": node_index_file;
 17 |                 "index.json.expires": node_index_expiry_file;
 18 |             }
 19 |         }
 20 |         "bin": shim_dir {}
 21 |         "log": log_dir {}
 22 |         "tools": tools_dir {
 23 |             "inventory": inventory_dir {
 24 |                 "node": node_inventory_dir {}
 25 |                 "packages": package_inventory_dir {}
 26 |                 "yarn": yarn_inventory_dir {}
 27 |             }
 28 |             "image": image_dir {
 29 |                 "node": node_image_root_dir {}
 30 |                 "yarn": yarn_image_root_dir {}
 31 |                 "packages": package_image_root_dir {}
 32 |             }
 33 |             "user": default_toolchain_dir {
 34 |                 "bins": default_bin_dir {}
 35 |                 "packages": default_package_dir {}
 36 |                 "platform.json": default_platform_file;
 37 |             }
 38 |         }
 39 |         "tmp": tmp_dir {}
 40 |         "hooks.json": default_hooks_file;
 41 |         "layout.v1": layout_file;
 42 |     }
 43 | }
 44 | 
 45 | impl VoltaHome {
 46 |     pub fn package_distro_file(&self, name: &str, version: &str) -> PathBuf {
 47 |         path_buf!(
 48 |             self.package_inventory_dir.clone(),
 49 |             format!("{}-{}.tgz", name, version)
 50 |         )
 51 |     }
 52 | 
 53 |     pub fn package_distro_shasum(&self, name: &str, version: &str) -> PathBuf {
 54 |         path_buf!(
 55 |             self.package_inventory_dir.clone(),
 56 |             format!("{}-{}.shasum", name, version)
 57 |         )
 58 |     }
 59 | 
 60 |     pub fn node_image_dir(&self, node: &str, npm: &str) -> PathBuf {
 61 |         path_buf!(self.node_image_root_dir.clone(), node, npm)
 62 |     }
 63 | 
 64 |     pub fn yarn_image_dir(&self, version: &str) -> PathBuf {
 65 |         path_buf!(self.yarn_image_root_dir.clone(), version)
 66 |     }
 67 | 
 68 |     pub fn yarn_image_bin_dir(&self, version: &str) -> PathBuf {
 69 |         path_buf!(self.yarn_image_dir(version), "bin")
 70 |     }
 71 | 
 72 |     pub fn package_image_dir(&self, name: &str, version: &str) -> PathBuf {
 73 |         path_buf!(self.package_image_root_dir.clone(), name, version)
 74 |     }
 75 | 
 76 |     pub fn default_package_config_file(&self, package_name: &str) -> PathBuf {
 77 |         path_buf!(
 78 |             self.default_package_dir.clone(),
 79 |             format!("{}.json", package_name)
 80 |         )
 81 |     }
 82 | 
 83 |     pub fn default_tool_bin_config(&self, bin_name: &str) -> PathBuf {
 84 |         path_buf!(self.default_bin_dir.clone(), format!("{}.json", bin_name))
 85 |     }
 86 | 
 87 |     pub fn node_npm_version_file(&self, version: &str) -> PathBuf {
 88 |         path_buf!(
 89 |             self.node_inventory_dir.clone(),
 90 |             format!("node-v{}-npm", version)
 91 |         )
 92 |     }
 93 | 
 94 |     pub fn shim_file(&self, toolname: &str) -> PathBuf {
 95 |         path_buf!(self.shim_dir.clone(), executable(toolname))
 96 |     }
 97 | }
 98 | 
 99 | #[cfg(windows)]
100 | impl VoltaHome {
101 |     pub fn shim_git_bash_script_file(&self, toolname: &str) -> PathBuf {
102 |         path_buf!(self.shim_dir.clone(), toolname)
103 |     }
104 | 
105 |     pub fn node_image_bin_dir(&self, node: &str, npm: &str) -> PathBuf {
106 |         self.node_image_dir(node, npm)
107 |     }
108 | }
109 | 
110 | #[cfg(unix)]
111 | impl VoltaHome {
112 |     pub fn node_image_bin_dir(&self, node: &str, npm: &str) -> PathBuf {
113 |         path_buf!(self.node_image_dir(node, npm), "bin")
114 |     }
115 | }
116 | 


--------------------------------------------------------------------------------
/crates/volta-layout/src/v2.rs:
--------------------------------------------------------------------------------
  1 | use std::path::PathBuf;
  2 | 
  3 | use super::executable;
  4 | use volta_layout_macro::layout;
  5 | 
  6 | pub use crate::v1::VoltaInstall;
  7 | 
  8 | layout! {
  9 |     pub struct VoltaHome {
 10 |         "cache": cache_dir {
 11 |             "node": node_cache_dir {
 12 |                 "index.json": node_index_file;
 13 |                 "index.json.expires": node_index_expiry_file;
 14 |             }
 15 |         }
 16 |         "bin": shim_dir {}
 17 |         "log": log_dir {}
 18 |         "tools": tools_dir {
 19 |             "inventory": inventory_dir {
 20 |                 "node": node_inventory_dir {}
 21 |                 "npm": npm_inventory_dir {}
 22 |                 "packages": package_inventory_dir {}
 23 |                 "yarn": yarn_inventory_dir {}
 24 |             }
 25 |             "image": image_dir {
 26 |                 "node": node_image_root_dir {}
 27 |                 "npm": npm_image_root_dir {}
 28 |                 "yarn": yarn_image_root_dir {}
 29 |                 "packages": package_image_root_dir {}
 30 |             }
 31 |             "user": default_toolchain_dir {
 32 |                 "bins": default_bin_dir {}
 33 |                 "packages": default_package_dir {}
 34 |                 "platform.json": default_platform_file;
 35 |             }
 36 |         }
 37 |         "tmp": tmp_dir {}
 38 |         "hooks.json": default_hooks_file;
 39 |         "layout.v2": layout_file;
 40 |     }
 41 | }
 42 | 
 43 | impl VoltaHome {
 44 |     pub fn package_distro_file(&self, name: &str, version: &str) -> PathBuf {
 45 |         path_buf!(
 46 |             self.package_inventory_dir.clone(),
 47 |             format!("{}-{}.tgz", name, version)
 48 |         )
 49 |     }
 50 | 
 51 |     pub fn package_distro_shasum(&self, name: &str, version: &str) -> PathBuf {
 52 |         path_buf!(
 53 |             self.package_inventory_dir.clone(),
 54 |             format!("{}-{}.shasum", name, version)
 55 |         )
 56 |     }
 57 | 
 58 |     pub fn node_image_dir(&self, node: &str) -> PathBuf {
 59 |         path_buf!(self.node_image_root_dir.clone(), node)
 60 |     }
 61 | 
 62 |     pub fn npm_image_dir(&self, npm: &str) -> PathBuf {
 63 |         path_buf!(self.npm_image_root_dir.clone(), npm)
 64 |     }
 65 | 
 66 |     pub fn npm_image_bin_dir(&self, npm: &str) -> PathBuf {
 67 |         path_buf!(self.npm_image_dir(npm), "bin")
 68 |     }
 69 | 
 70 |     pub fn yarn_image_dir(&self, version: &str) -> PathBuf {
 71 |         path_buf!(self.yarn_image_root_dir.clone(), version)
 72 |     }
 73 | 
 74 |     pub fn yarn_image_bin_dir(&self, version: &str) -> PathBuf {
 75 |         path_buf!(self.yarn_image_dir(version), "bin")
 76 |     }
 77 | 
 78 |     pub fn package_image_dir(&self, name: &str, version: &str) -> PathBuf {
 79 |         path_buf!(self.package_image_root_dir.clone(), name, version)
 80 |     }
 81 | 
 82 |     pub fn default_package_config_file(&self, package_name: &str) -> PathBuf {
 83 |         path_buf!(
 84 |             self.default_package_dir.clone(),
 85 |             format!("{}.json", package_name)
 86 |         )
 87 |     }
 88 | 
 89 |     pub fn default_tool_bin_config(&self, bin_name: &str) -> PathBuf {
 90 |         path_buf!(self.default_bin_dir.clone(), format!("{}.json", bin_name))
 91 |     }
 92 | 
 93 |     pub fn node_npm_version_file(&self, version: &str) -> PathBuf {
 94 |         path_buf!(
 95 |             self.node_inventory_dir.clone(),
 96 |             format!("node-v{}-npm", version)
 97 |         )
 98 |     }
 99 | 
100 |     pub fn shim_file(&self, toolname: &str) -> PathBuf {
101 |         path_buf!(self.shim_dir.clone(), executable(toolname))
102 |     }
103 | }
104 | 
105 | #[cfg(windows)]
106 | impl VoltaHome {
107 |     pub fn shim_git_bash_script_file(&self, toolname: &str) -> PathBuf {
108 |         path_buf!(self.shim_dir.clone(), toolname)
109 |     }
110 | 
111 |     pub fn node_image_bin_dir(&self, node: &str) -> PathBuf {
112 |         self.node_image_dir(node)
113 |     }
114 | }
115 | 
116 | #[cfg(unix)]
117 | impl VoltaHome {
118 |     pub fn node_image_bin_dir(&self, node: &str) -> PathBuf {
119 |         path_buf!(self.node_image_dir(node), "bin")
120 |     }
121 | }
122 | 


--------------------------------------------------------------------------------
/crates/volta-layout/src/v3.rs:
--------------------------------------------------------------------------------
  1 | use std::path::PathBuf;
  2 | 
  3 | use super::executable;
  4 | use volta_layout_macro::layout;
  5 | 
  6 | pub use crate::v1::VoltaInstall;
  7 | 
  8 | layout! {
  9 |     pub struct VoltaHome {
 10 |         "cache": cache_dir {
 11 |             "node": node_cache_dir {
 12 |                 "index.json": node_index_file;
 13 |                 "index.json.expires": node_index_expiry_file;
 14 |             }
 15 |         }
 16 |         "bin": shim_dir {}
 17 |         "log": log_dir {}
 18 |         "tools": tools_dir {
 19 |             "inventory": inventory_dir {
 20 |                 "node": node_inventory_dir {}
 21 |                 "npm": npm_inventory_dir {}
 22 |                 "pnpm": pnpm_inventory_dir {}
 23 |                 "yarn": yarn_inventory_dir {}
 24 |             }
 25 |             "image": image_dir {
 26 |                 "node": node_image_root_dir {}
 27 |                 "npm": npm_image_root_dir {}
 28 |                 "pnpm": pnpm_image_root_dir {}
 29 |                 "yarn": yarn_image_root_dir {}
 30 |                 "packages": package_image_root_dir {}
 31 |             }
 32 |             "shared": shared_lib_root {}
 33 |             "user": default_toolchain_dir {
 34 |                 "bins": default_bin_dir {}
 35 |                 "packages": default_package_dir {}
 36 |                 "platform.json": default_platform_file;
 37 |             }
 38 |         }
 39 |         "tmp": tmp_dir {}
 40 |         "hooks.json": default_hooks_file;
 41 |         "layout.v3": layout_file;
 42 |     }
 43 | }
 44 | 
 45 | impl VoltaHome {
 46 |     pub fn node_image_dir(&self, node: &str) -> PathBuf {
 47 |         path_buf!(self.node_image_root_dir.clone(), node)
 48 |     }
 49 | 
 50 |     pub fn npm_image_dir(&self, npm: &str) -> PathBuf {
 51 |         path_buf!(self.npm_image_root_dir.clone(), npm)
 52 |     }
 53 | 
 54 |     pub fn npm_image_bin_dir(&self, npm: &str) -> PathBuf {
 55 |         path_buf!(self.npm_image_dir(npm), "bin")
 56 |     }
 57 | 
 58 |     pub fn pnpm_image_dir(&self, version: &str) -> PathBuf {
 59 |         path_buf!(self.pnpm_image_root_dir.clone(), version)
 60 |     }
 61 | 
 62 |     pub fn pnpm_image_bin_dir(&self, version: &str) -> PathBuf {
 63 |         path_buf!(self.pnpm_image_dir(version), "bin")
 64 |     }
 65 | 
 66 |     pub fn yarn_image_dir(&self, version: &str) -> PathBuf {
 67 |         path_buf!(self.yarn_image_root_dir.clone(), version)
 68 |     }
 69 | 
 70 |     pub fn yarn_image_bin_dir(&self, version: &str) -> PathBuf {
 71 |         path_buf!(self.yarn_image_dir(version), "bin")
 72 |     }
 73 | 
 74 |     pub fn package_image_dir(&self, name: &str) -> PathBuf {
 75 |         path_buf!(self.package_image_root_dir.clone(), name)
 76 |     }
 77 | 
 78 |     pub fn default_package_config_file(&self, package_name: &str) -> PathBuf {
 79 |         path_buf!(
 80 |             self.default_package_dir.clone(),
 81 |             format!("{}.json", package_name)
 82 |         )
 83 |     }
 84 | 
 85 |     pub fn default_tool_bin_config(&self, bin_name: &str) -> PathBuf {
 86 |         path_buf!(self.default_bin_dir.clone(), format!("{}.json", bin_name))
 87 |     }
 88 | 
 89 |     pub fn node_npm_version_file(&self, version: &str) -> PathBuf {
 90 |         path_buf!(
 91 |             self.node_inventory_dir.clone(),
 92 |             format!("node-v{}-npm", version)
 93 |         )
 94 |     }
 95 | 
 96 |     pub fn shim_file(&self, toolname: &str) -> PathBuf {
 97 |         path_buf!(self.shim_dir.clone(), executable(toolname))
 98 |     }
 99 | 
100 |     pub fn shared_lib_dir(&self, library: &str) -> PathBuf {
101 |         path_buf!(self.shared_lib_root.clone(), library)
102 |     }
103 | }
104 | 
105 | #[cfg(windows)]
106 | impl VoltaHome {
107 |     pub fn shim_git_bash_script_file(&self, toolname: &str) -> PathBuf {
108 |         path_buf!(self.shim_dir.clone(), toolname)
109 |     }
110 | 
111 |     pub fn node_image_bin_dir(&self, node: &str) -> PathBuf {
112 |         self.node_image_dir(node)
113 |     }
114 | }
115 | 
116 | #[cfg(unix)]
117 | impl VoltaHome {
118 |     pub fn node_image_bin_dir(&self, node: &str) -> PathBuf {
119 |         path_buf!(self.node_image_dir(node), "bin")
120 |     }
121 | }
122 | 


--------------------------------------------------------------------------------
/crates/volta-layout/src/v4.rs:
--------------------------------------------------------------------------------
  1 | use std::path::PathBuf;
  2 | 
  3 | use volta_layout_macro::layout;
  4 | 
  5 | pub use crate::v1::VoltaInstall;
  6 | 
  7 | layout! {
  8 |     pub struct VoltaHome {
  9 |         "cache": cache_dir {
 10 |             "node": node_cache_dir {
 11 |                 "index.json": node_index_file;
 12 |                 "index.json.expires": node_index_expiry_file;
 13 |             }
 14 |         }
 15 |         "bin": shim_dir {}
 16 |         "log": log_dir {}
 17 |         "tools": tools_dir {
 18 |             "inventory": inventory_dir {
 19 |                 "node": node_inventory_dir {}
 20 |                 "npm": npm_inventory_dir {}
 21 |                 "pnpm": pnpm_inventory_dir {}
 22 |                 "yarn": yarn_inventory_dir {}
 23 |             }
 24 |             "image": image_dir {
 25 |                 "node": node_image_root_dir {}
 26 |                 "npm": npm_image_root_dir {}
 27 |                 "pnpm": pnpm_image_root_dir {}
 28 |                 "yarn": yarn_image_root_dir {}
 29 |                 "packages": package_image_root_dir {}
 30 |             }
 31 |             "shared": shared_lib_root {}
 32 |             "user": default_toolchain_dir {
 33 |                 "bins": default_bin_dir {}
 34 |                 "packages": default_package_dir {}
 35 |                 "platform.json": default_platform_file;
 36 |             }
 37 |         }
 38 |         "tmp": tmp_dir {}
 39 |         "hooks.json": default_hooks_file;
 40 |         "layout.v4": layout_file;
 41 |     }
 42 | }
 43 | 
 44 | impl VoltaHome {
 45 |     pub fn node_image_dir(&self, node: &str) -> PathBuf {
 46 |         path_buf!(self.node_image_root_dir.clone(), node)
 47 |     }
 48 | 
 49 |     pub fn npm_image_dir(&self, npm: &str) -> PathBuf {
 50 |         path_buf!(self.npm_image_root_dir.clone(), npm)
 51 |     }
 52 | 
 53 |     pub fn npm_image_bin_dir(&self, npm: &str) -> PathBuf {
 54 |         path_buf!(self.npm_image_dir(npm), "bin")
 55 |     }
 56 | 
 57 |     pub fn pnpm_image_dir(&self, version: &str) -> PathBuf {
 58 |         path_buf!(self.pnpm_image_root_dir.clone(), version)
 59 |     }
 60 | 
 61 |     pub fn pnpm_image_bin_dir(&self, version: &str) -> PathBuf {
 62 |         path_buf!(self.pnpm_image_dir(version), "bin")
 63 |     }
 64 | 
 65 |     pub fn yarn_image_dir(&self, version: &str) -> PathBuf {
 66 |         path_buf!(self.yarn_image_root_dir.clone(), version)
 67 |     }
 68 | 
 69 |     pub fn yarn_image_bin_dir(&self, version: &str) -> PathBuf {
 70 |         path_buf!(self.yarn_image_dir(version), "bin")
 71 |     }
 72 | 
 73 |     pub fn package_image_dir(&self, name: &str) -> PathBuf {
 74 |         path_buf!(self.package_image_root_dir.clone(), name)
 75 |     }
 76 | 
 77 |     pub fn default_package_config_file(&self, package_name: &str) -> PathBuf {
 78 |         path_buf!(
 79 |             self.default_package_dir.clone(),
 80 |             format!("{}.json", package_name)
 81 |         )
 82 |     }
 83 | 
 84 |     pub fn default_tool_bin_config(&self, bin_name: &str) -> PathBuf {
 85 |         path_buf!(self.default_bin_dir.clone(), format!("{}.json", bin_name))
 86 |     }
 87 | 
 88 |     pub fn node_npm_version_file(&self, version: &str) -> PathBuf {
 89 |         path_buf!(
 90 |             self.node_inventory_dir.clone(),
 91 |             format!("node-v{}-npm", version)
 92 |         )
 93 |     }
 94 | 
 95 |     pub fn shim_file(&self, toolname: &str) -> PathBuf {
 96 |         // On Windows, shims are created as `<name>.cmd` since they
 97 |         // are thin scripts that use `volta run` to execute the command
 98 |         #[cfg(windows)]
 99 |         let toolname = format!("{}{}", toolname, ".cmd");
100 | 
101 |         path_buf!(self.shim_dir.clone(), toolname)
102 |     }
103 | 
104 |     pub fn shared_lib_dir(&self, library: &str) -> PathBuf {
105 |         path_buf!(self.shared_lib_root.clone(), library)
106 |     }
107 | }
108 | 
109 | #[cfg(windows)]
110 | impl VoltaHome {
111 |     pub fn shim_git_bash_script_file(&self, toolname: &str) -> PathBuf {
112 |         path_buf!(self.shim_dir.clone(), toolname)
113 |     }
114 | 
115 |     pub fn node_image_bin_dir(&self, node: &str) -> PathBuf {
116 |         self.node_image_dir(node)
117 |     }
118 | }
119 | 
120 | #[cfg(unix)]
121 | impl VoltaHome {
122 |     pub fn node_image_bin_dir(&self, node: &str) -> PathBuf {
123 |         path_buf!(self.node_image_dir(node), "bin")
124 |     }
125 | }
126 | 


--------------------------------------------------------------------------------
/crates/volta-migrate/Cargo.toml:
--------------------------------------------------------------------------------
 1 | [package]
 2 | name = "volta-migrate"
 3 | version = "0.1.0"
 4 | authors = ["Charles Pierce <cpierce.grad@gmail.com>"]
 5 | edition = "2021"
 6 | 
 7 | [dependencies]
 8 | volta-core = { path = "../volta-core" }
 9 | volta-layout = { path = "../volta-layout" }
10 | log = { version = "0.4", features = ["std"] }
11 | tempfile = "3.14.0"
12 | node-semver = "2"
13 | serde_json = { version = "1.0.135", features = ["preserve_order"] }
14 | serde = { version = "1.0.217", features = ["derive"] }
15 | walkdir = "2.5.0"
16 | 


--------------------------------------------------------------------------------
/crates/volta-migrate/src/empty.rs:
--------------------------------------------------------------------------------
 1 | use std::path::PathBuf;
 2 | 
 3 | /// Represents an Empty (or uninitialized) Volta layout, one that has never been used by any prior version
 4 | ///
 5 | /// This is the easiest to migrate from, as we simply need to create the current layout within the .volta
 6 | /// directory
 7 | pub struct Empty {
 8 |     pub home: PathBuf,
 9 | }
10 | 
11 | impl Empty {
12 |     pub fn new(home: PathBuf) -> Self {
13 |         Empty { home }
14 |     }
15 | }
16 | 


--------------------------------------------------------------------------------
/crates/volta-migrate/src/v0.rs:
--------------------------------------------------------------------------------
 1 | use std::path::PathBuf;
 2 | 
 3 | use volta_layout::v0::VoltaHome;
 4 | 
 5 | /// Represents a V0 Volta layout (from before v0.7.0)
 6 | ///
 7 | /// This needs some migration work to move up to V1, so we keep a reference to the V0 layout
 8 | /// struct to allow for easy comparison between versions
 9 | pub struct V0 {
10 |     pub home: VoltaHome,
11 | }
12 | 
13 | impl V0 {
14 |     pub fn new(home: PathBuf) -> Self {
15 |         V0 {
16 |             home: VoltaHome::new(home),
17 |         }
18 |     }
19 | }
20 | 


--------------------------------------------------------------------------------
/crates/volta-migrate/src/v1.rs:
--------------------------------------------------------------------------------
 1 | #[cfg(unix)]
 2 | use std::fs::remove_file;
 3 | use std::fs::File;
 4 | use std::path::PathBuf;
 5 | 
 6 | use super::empty::Empty;
 7 | use super::v0::V0;
 8 | use log::debug;
 9 | use volta_core::error::{Context, ErrorKind, Fallible, VoltaError};
10 | #[cfg(unix)]
11 | use volta_core::fs::{read_dir_eager, remove_file_if_exists};
12 | use volta_layout::v1;
13 | 
14 | /// Represents a V1 Volta Layout (used by Volta v0.7.0 - v0.7.2)
15 | ///
16 | /// Holds a reference to the V1 layout struct to support potential future migrations
17 | pub struct V1 {
18 |     pub home: v1::VoltaHome,
19 | }
20 | 
21 | impl V1 {
22 |     pub fn new(home: PathBuf) -> Self {
23 |         V1 {
24 |             home: v1::VoltaHome::new(home),
25 |         }
26 |     }
27 | 
28 |     /// Write the layout file to mark migration to V1 as complete
29 |     ///
30 |     /// Should only be called once all other migration steps are finished, so that we don't
31 |     /// accidentally mark an incomplete migration as completed
32 |     fn complete_migration(home: v1::VoltaHome) -> Fallible<Self> {
33 |         debug!("Writing layout marker file");
34 |         File::create(home.layout_file()).with_context(|| ErrorKind::CreateLayoutFileError {
35 |             file: home.layout_file().to_owned(),
36 |         })?;
37 | 
38 |         Ok(V1 { home })
39 |     }
40 | }
41 | 
42 | impl TryFrom<Empty> for V1 {
43 |     type Error = VoltaError;
44 | 
45 |     fn try_from(old: Empty) -> Fallible<V1> {
46 |         debug!("New Volta installation detected, creating fresh layout");
47 | 
48 |         let home = v1::VoltaHome::new(old.home);
49 |         home.create().with_context(|| ErrorKind::CreateDirError {
50 |             dir: home.root().to_owned(),
51 |         })?;
52 | 
53 |         V1::complete_migration(home)
54 |     }
55 | }
56 | 
57 | impl TryFrom<V0> for V1 {
58 |     type Error = VoltaError;
59 | 
60 |     fn try_from(old: V0) -> Fallible<V1> {
61 |         debug!("Existing Volta installation detected, migrating from V0 layout");
62 | 
63 |         let new_home = v1::VoltaHome::new(old.home.root().to_owned());
64 |         new_home
65 |             .create()
66 |             .with_context(|| ErrorKind::CreateDirError {
67 |                 dir: new_home.root().to_owned(),
68 |             })?;
69 | 
70 |         #[cfg(unix)]
71 |         {
72 |             debug!("Removing unnecessary 'load.*' files");
73 |             let root_contents =
74 |                 read_dir_eager(new_home.root()).with_context(|| ErrorKind::ReadDirError {
75 |                     dir: new_home.root().to_owned(),
76 |                 })?;
77 |             for (entry, _) in root_contents {
78 |                 let path = entry.path();
79 |                 if let Some(stem) = path.file_stem() {
80 |                     if stem == "load" && path.is_file() {
81 |                         remove_file(&path)
82 |                             .with_context(|| ErrorKind::DeleteFileError { file: path })?;
83 |                     }
84 |                 }
85 |             }
86 | 
87 |             debug!("Removing old Volta binaries");
88 | 
89 |             let old_volta_bin = new_home.root().join("volta");
90 |             remove_file_if_exists(old_volta_bin)?;
91 | 
92 |             let old_shim_bin = new_home.root().join("shim");
93 |             remove_file_if_exists(old_shim_bin)?;
94 |         }
95 | 
96 |         V1::complete_migration(new_home)
97 |     }
98 | }
99 | 


--------------------------------------------------------------------------------
/crates/volta-migrate/src/v3/config.rs:
--------------------------------------------------------------------------------
 1 | use std::fs::File;
 2 | use std::path::Path;
 3 | 
 4 | use node_semver::Version;
 5 | use volta_core::platform::PlatformSpec;
 6 | use volta_core::version::{option_version_serde, version_serde};
 7 | 
 8 | #[derive(serde::Deserialize)]
 9 | pub struct LegacyPackageConfig {
10 |     pub name: String,
11 |     #[serde(with = "version_serde")]
12 |     pub version: Version,
13 |     pub platform: LegacyPlatform,
14 |     pub bins: Vec<String>,
15 | }
16 | 
17 | #[derive(serde::Deserialize)]
18 | pub struct LegacyPlatform {
19 |     pub node: NodeVersion,
20 |     #[serde(with = "option_version_serde")]
21 |     pub yarn: Option<Version>,
22 | }
23 | 
24 | #[derive(serde::Deserialize)]
25 | pub struct NodeVersion {
26 |     #[serde(with = "version_serde")]
27 |     pub runtime: Version,
28 |     #[serde(with = "option_version_serde")]
29 |     pub npm: Option<Version>,
30 | }
31 | 
32 | impl LegacyPackageConfig {
33 |     pub fn from_file(config_file: &Path) -> Option<Self> {
34 |         let file = File::open(config_file).ok()?;
35 | 
36 |         serde_json::from_reader(file).ok()
37 |     }
38 | }
39 | 
40 | impl From<LegacyPlatform> for PlatformSpec {
41 |     fn from(config_platform: LegacyPlatform) -> Self {
42 |         PlatformSpec {
43 |             node: config_platform.node.runtime,
44 |             npm: config_platform.node.npm,
45 |             // LegacyPlatform (layout.v2) doesn't have a pnpm field
46 |             pnpm: None,
47 |             yarn: config_platform.yarn,
48 |         }
49 |     }
50 | }
51 | 


--------------------------------------------------------------------------------
/dev/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "example",
 3 |   "version": "1.0",
 4 |   "description": "an example Node project using volta",
 5 |   "author": "Dave Herman <david.herman@gmail.com>",
 6 |   "main": "lib/index.js",
 7 |   "devDependencies": {
 8 |     "ember-cli": "2.18.1"
 9 |   },
10 |   "volta": {
11 |     "node": "6.11.1",
12 |     "yarn": "1.7.0"
13 |   }
14 | }
15 | 


--------------------------------------------------------------------------------
/dev/rpm/build-rpm.sh:
--------------------------------------------------------------------------------
 1 | #!/usr/bin/env bash
 2 | # Build an RPM package for Volta
 3 | 
 4 | # using the directions from https://rpm-packaging-guide.github.io/
 5 | 
 6 | # exit on error
 7 | set -e
 8 | 
 9 | # only argument is the version number
10 | release_version="${1:?Must specify the release version, like \`build-rpm 1.2.3\`}"
11 | archive_filename="v${release_version}.tar.gz"
12 | 
13 | # make sure these packages are installed
14 | # (https://rpm-packaging-guide.github.io/#prerequisites)
15 | sudo yum install gcc rpm-build rpm-devel rpmlint make python bash coreutils diffutils patch rpmdevtools
16 | 
17 | # set up the directory layout for the RPM packaging workspace
18 | # (https://rpm-packaging-guide.github.io/#rpm-packaging-workspace)
19 | rpmdev-setuptree
20 | 
21 | # create a tarball of the repo for the specified version
22 | # using prefix because the rpmbuild process expects a 'volta-<version>' directory
23 | # (https://rpm-packaging-guide.github.io/#putting-source-code-into-tarball)
24 | git archive --format=tar.gz --output=$archive_filename --prefix="volta-${release_version}/" HEAD
25 | 
26 | # move the archive to the SOURCES dir, after cleaning it up
27 | # (https://rpm-packaging-guide.github.io/#working-with-spec-files)
28 | rm -rf "$HOME/rmpbuild/SOURCES/"*
29 | mv "$archive_filename" "$HOME/rpmbuild/SOURCES/"
30 | 
31 | # copy the .spec file to SPECS dir
32 | cp dev/rpm/volta.spec "$HOME/rpmbuild/SPECS/"
33 | 
34 | # build it!
35 | # (https://rpm-packaging-guide.github.io/#binary-rpms)
36 | rpmbuild -bb "$HOME/rpmbuild/SPECS/volta.spec"
37 | # (there will be a lot of output)
38 | 
39 | # then install it and verify everything worked...
40 | echo ""
41 | echo "Build finished!"
42 | echo ""
43 | echo "Run this to install:"
44 | echo "  \`sudo yum install ~/rpmbuild/RPMS/x86_64/volta-${release_version}-1.el7.x86_64.rpm\`"
45 | echo ""
46 | echo "Then run this to uninstall after verifying:"
47 | echo "  \`sudo yum erase volta-${release_version}-1.el7.x86_64\`"
48 | 


--------------------------------------------------------------------------------
/dev/rpm/volta.spec:
--------------------------------------------------------------------------------
 1 | Name:           volta
 2 | Version:        0.8.2
 3 | Release:        1%{?dist}
 4 | Summary:        The JavaScript Launcher ⚡
 5 | 
 6 | License:        BSD 2-CLAUSE
 7 | URL:            https://%{name}.sh
 8 | Source0:        https://github.com/volta-cli/volta/archive/v%{version}.tar.gz
 9 | 
10 | # cargo is required, but installing from RPM is failing with libcrypto dep error
11 | # so you will have to install cargo manually to build this
12 | #BuildRequires:  cargo
13 | 
14 | # because these are built with openssl
15 | Requires:       openssl
16 | 
17 | 
18 | %description
19 | Volta’s job is to manage your JavaScript command-line tools, such as node, npm, yarn, or executables shipped as part of JavaScript packages. Similar to package managers, Volta keeps track of which project (if any) you’re working on based on your current directory. The tools in your Volta toolchain automatically detect when you’re in a project that’s using a particular version of the tools, and take care of routing to the right version of the tools for you.
20 | 
21 | 
22 | %prep
23 | # this unpacks the tarball to the build root
24 | %setup -q
25 | 
26 | 
27 | %build
28 | # build the release binaries
29 | # NOTE: build expects to `cd` into a volta-<version> directory
30 | cargo build --release
31 | 
32 | 
33 | # this installs into a chroot directory resembling the user's root directory
34 | %install
35 | # BUILDROOT/usr/bin
36 | %define volta_install_dir %{buildroot}/%{_bindir}
37 | # setup the /usr/bin/volta-lib/ directory
38 | rm -rf %{buildroot}
39 | mkdir -p %{volta_install_dir}
40 | # install everything into into /usr/bin/, so it's on the PATH
41 | install -m 0755 target/release/%{name} %{volta_install_dir}/%{name}
42 | install -m 0755 target/release/volta-shim %{volta_install_dir}/volta-shim
43 | install -m 0755 target/release/volta-migrate %{volta_install_dir}/volta-migrate
44 | 
45 | 
46 | # files installed by this package
47 | %files
48 | %license LICENSE
49 | %{_bindir}/%{name}
50 | %{_bindir}/volta-shim
51 | %{_bindir}/volta-migrate
52 | 
53 | 
54 | # this runs before install
55 | %pre
56 | # make sure the /usr/bin/volta/ dir does not exist, from prev RPM installs (or this will fail)
57 | printf '\033[1;32m%12s\033[0m %s\n' "Running" "Volta pre-install..." 1>&2
58 | rm -rf %{_bindir}/%{name}
59 | 
60 | 
61 | # this runs after install, and sets up VOLTA_HOME and the shell integration
62 | %post
63 | printf '\033[1;32m%12s\033[0m %s\n' "Running" "Volta post-install setup..." 1>&2
64 | # run this as the user who invoked sudo (not as root, because we're writing to $HOME)
65 | /bin/su -c "%{_bindir}/volta setup" - $SUDO_USER
66 | 
67 | 
68 | %changelog
69 | * Tue Oct 22 2019 Charles Pierce <cpierce.grad@gmail.com> - 0.6.5-1
70 | - Update to use 'volta setup' as the postinstall script
71 | * Mon Jun 03 2019 Michael Stewart <mikrostew@gmail.com> - 0.5.3-1
72 | - First volta package
73 | 


--------------------------------------------------------------------------------
/dev/unix/SHASUMS256.txt:
--------------------------------------------------------------------------------
1 | fbdc4b8cb33fb6d19e5f07b22423265943d34e7e5c3d5a1efcecc9621854f9cb  volta-install.sh
2 | 


--------------------------------------------------------------------------------
/dev/unix/build.sh:
--------------------------------------------------------------------------------
 1 | #!/usr/bin/env bash
 2 | 
 3 | script_dir="$(dirname "$0")"
 4 | 
 5 | usage() {
 6 |   cat <<END_USAGE
 7 | build.sh: generate volta's generic unix installation script
 8 | 
 9 | usage: build.sh [target]
10 |   [target]   build artifacts to use ('release' or 'debug', defaults to 'release')
11 | 
12 | The output file is saved as $script_dir/install.sh.
13 | END_USAGE
14 | }
15 | 
16 | if [ -z "$1" ]; then
17 |   target_dir='release'
18 | elif [[ "$1" =~ (debug|release) ]]; then
19 |   target_dir="$1"
20 | else
21 |   usage
22 |   exit 1
23 | fi
24 | 
25 | encode_base64_sed_command() {
26 |   command printf "s|<PLACEHOLDER_$2_PAYLOAD>|" > $1.base64.txt
27 |   cat $3 | base64 - | tr -d '\n' >> $1.base64.txt
28 |   command printf "|\n" >> $1.base64.txt
29 | }
30 | 
31 | encode_expand_sed_command() {
32 |   # This atrocity is a combination of:
33 |   # - https://unix.stackexchange.com/questions/141387/sed-replace-string-with-file-contents
34 |   # - https://serverfault.com/questions/391360/remove-line-break-using-awk
35 |   # - https://stackoverflow.com/questions/1421478/how-do-i-use-a-new-line-replacement-in-a-bsd-sed
36 |   command printf "s|<PLACEHOLDER_$2_PAYLOAD>|$(sed 's/|/\\|/g' $3 | awk '{printf "%s\\\n",$0} END {print ""}' )\\\n|\n" > $1.expand.txt
37 | }
38 | 
39 | build_dir="$script_dir/../../target/$target_dir"
40 | shell_dir="$script_dir/../../shell"
41 | 
42 | encode_base64_sed_command volta VOLTA "$build_dir/volta"
43 | encode_base64_sed_command shim SHIM "$build_dir/shim"
44 | encode_expand_sed_command bash_launcher BASH_LAUNCHER "$shell_dir/unix/load.sh"
45 | encode_expand_sed_command fish_launcher FISH_LAUNCHER "$shell_dir/unix/load.fish"
46 | 
47 | sed -f volta.base64.txt \
48 |     -f shim.base64.txt \
49 |     -f bash_launcher.expand.txt \
50 |     -f fish_launcher.expand.txt \
51 |     < "$script_dir/install.sh.in" > "$script_dir/install.sh"
52 | 
53 | chmod 755 "$script_dir/install.sh"
54 | 
55 | rm volta.base64.txt \
56 |    shim.base64.txt \
57 |    bash_launcher.expand.txt \
58 |    fish_launcher.expand.txt
59 | 


--------------------------------------------------------------------------------
/dev/unix/release.sh:
--------------------------------------------------------------------------------
 1 | #!/usr/bin/env bash
 2 | 
 3 | # Script to build the binaries and package them up for release.
 4 | # This should be run from the top-level directory.
 5 | 
 6 | # get the directory of this script
 7 | # (from https://stackoverflow.com/a/246128)
 8 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
 9 | 
10 | # get shared functions from the volta-install.sh file
11 | source "$DIR/volta-install.sh"
12 | 
13 | usage() {
14 |   cat >&2 <<END_OF_USAGE
15 | release.sh
16 | 
17 | Compile and package a release for Volta
18 | 
19 | USAGE:
20 |     ./dev/unix/release.sh [FLAGS] [OPTIONS]
21 | 
22 | FLAGS:
23 |     -h, --help          Prints this help info
24 | 
25 | OPTIONS:
26 |         --release       Build artifacts in release mode, with optimizations (default)
27 |         --dev           Build artifacts in dev mode, without optimizations
28 | END_OF_USAGE
29 | }
30 | 
31 | 
32 | # default to compiling with '--release'
33 | build_with_release="true"
34 | 
35 | # parse input arguments
36 | case "$1" in
37 |   -h|--help)
38 |     usage
39 |     exit 0
40 |     ;;
41 |   --dev)
42 |     build_with_release="false"
43 |     ;;
44 |   ''|--release)
45 |     # not really necessary to set this again
46 |     build_with_release="true"
47 |     ;;
48 |   *)
49 |     error "Unknown argument '$1'"
50 |     usage
51 |     exit1
52 |     ;;
53 | esac
54 | 
55 | # read the current version from Cargo.toml
56 | cargo_toml_contents="$(<Cargo.toml)"
57 | VOLTA_VERSION="$(parse_cargo_version "$cargo_toml_contents")" || exit 1
58 | 
59 | # figure out the OS details
60 | os="$(uname -s)"
61 | openssl_version="$(openssl version)" || exit 1
62 | VOLTA_OS="$(parse_os_info "$os" "$openssl_version")"
63 | if [ "$?" != 0 ]; then
64 |   error "Releases for '$os' are not yet supported."
65 |   request "To support '$os', add another case to parse_os_info() in volta-install.sh."
66 |   exit 1
67 | fi
68 | 
69 | release_filename="volta-$VOLTA_VERSION-$VOLTA_OS"
70 | 
71 | # first make sure the release binaries have been built
72 | info 'Building' "Volta for $(bold "$release_filename")"
73 | if [ "$build_with_release" == "true" ]
74 | then
75 |   target_dir="target/release"
76 |   cargo build --release
77 | else
78 |   target_dir="target/debug"
79 |   cargo build
80 | fi || exit 1
81 | 
82 | # then package the binaries and shell scripts together
83 | info 'Packaging' "the compiled binaries"
84 | cd "$target_dir"
85 | # using COPYFILE_DISABLE to avoid storing extended attribute files when run on OSX
86 | # (see https://superuser.com/q/61185)
87 | COPYFILE_DISABLE=1 tar -czvf "$release_filename.tar.gz" volta volta-shim volta-migrate
88 | 
89 | info 'Completed' "release in file $target_dir/$release_filename.tar.gz"
90 | 


--------------------------------------------------------------------------------
/dev/unix/test-events:
--------------------------------------------------------------------------------
 1 | #!/usr/bin/env bash
 2 | 
 3 | # long-running script to show events and test spawning processes
 4 | 
 5 | my_pid="$"
 6 | 
 7 | echo "$my_pid called with $# args: $@"
 8 | 
 9 | # get JSON data from stdin
10 | read some_data
11 | 
12 | echo "$my_pid got:"
13 | # display nicely with jq if it is installed
14 | command -v jq >/dev/null 2>&1 && echo "$some_data" | jq '.' || echo "$some_data"
15 | 
16 | i=0
17 | while [ $i -lt 3 ]
18 | do
19 |   sleep 2s
20 |   echo "$my_pid still running!"
21 |   let i=i+1
22 | done
23 | 
24 | echo "$my_pid done!!"
25 | 
26 | 


--------------------------------------------------------------------------------
/dev/unix/tests/install-script.bats:
--------------------------------------------------------------------------------
  1 | # test the volta-install.sh script
  2 | 
  3 | # load the functions from the script
  4 | source dev/unix/volta-install.sh
  5 | 
  6 | 
  7 | # happy path test to parse the version from Cargo.toml
  8 | @test "parse_cargo_version - normal Cargo.toml" {
  9 |   input=$(cat <<'END_CARGO_TOML'
 10 | [package]
 11 | name = "volta"
 12 | version = "0.7.38"
 13 | authors = ["David Herman <david.herman@gmail.com>"]
 14 | license = "BSD-2-Clause"
 15 | END_CARGO_TOML
 16 | )
 17 | 
 18 |   expected_output="0.7.38"
 19 | 
 20 |   run parse_cargo_version "$input"
 21 |   [ "$status" -eq 0 ]
 22 |   diff <(echo "$output") <(echo "$expected_output")
 23 | }
 24 | 
 25 | # it doesn't parse the version from other dependencies
 26 | @test "parse_cargo_version - error" {
 27 |   input=$(cat <<'END_CARGO_TOML'
 28 | [dependencies]
 29 | volta-core = { path = "crates/volta-core" }
 30 | serde = { version = "1.0", features = ["derive"] }
 31 | serde_json = "1.0.37"
 32 | console = "0.6.1"
 33 | END_CARGO_TOML
 34 | )
 35 | 
 36 |   expected_output=$(echo -e "\033[1;31mError\033[0m: Could not determine the current version from Cargo.toml")
 37 | 
 38 |   run parse_cargo_version "$input"
 39 |   [ "$status" -eq 1 ]
 40 |   diff <(echo "$output") <(echo "$expected_output")
 41 | }
 42 | 
 43 | # linux
 44 | @test "parse_os_info - linux" {
 45 |   expected_output="linux"
 46 | 
 47 |   run parse_os_info "Linux"
 48 |   [ "$status" -eq 0 ]
 49 |   diff <(echo "$output") <(echo "$expected_output")
 50 | }
 51 | 
 52 | # macos
 53 | @test "parse_os_info - macos" {
 54 |   expected_output="macos"
 55 | 
 56 |   run parse_os_info "Darwin"
 57 |   [ "$status" -eq 0 ]
 58 |   diff <(echo "$output") <(echo "$expected_output")
 59 | }
 60 | 
 61 | # unsupported OS
 62 | @test "parse_os_info - unsupported OS" {
 63 |   expected_output=""
 64 | 
 65 |   run parse_os_info "DOS"
 66 |   [ "$status" -eq 1 ]
 67 |   diff <(echo "$output") <(echo "$expected_output")
 68 | }
 69 | 
 70 | # test element_in helper function
 71 | @test "element_in works correctly" {
 72 |   run element_in "foo" "foo" "bar" "baz"
 73 |   [ "$status" -eq 0 ]
 74 | 
 75 |   array=( "foo" "bar" "baz" )
 76 |   run element_in "foo" "${array[@]}"
 77 |   [ "$status" -eq 0 ]
 78 |   run element_in "bar" "${array[@]}"
 79 |   [ "$status" -eq 0 ]
 80 |   run element_in "baz" "${array[@]}"
 81 |   [ "$status" -eq 0 ]
 82 | 
 83 |   run element_in "fob" "${array[@]}"
 84 |   [ "$status" -eq 1 ]
 85 | }
 86 | 
 87 | 
 88 | # test VOLTA_HOME settings
 89 | 
 90 | @test "volta_home_is_ok - true cases" {
 91 |   # unset is fine
 92 |   unset VOLTA_HOME
 93 |   run volta_home_is_ok
 94 |   [ "$status" -eq 0 ]
 95 | 
 96 |   # empty is fine
 97 |   VOLTA_HOME=""
 98 |   run volta_home_is_ok
 99 |   [ "$status" -eq 0 ]
100 | 
101 |   # non-existing dir is fine
102 |   VOLTA_HOME="/some/dir/that/does/not/exist/anywhere"
103 |   run volta_home_is_ok
104 |   [ "$status" -eq 0 ]
105 | 
106 |   # existing dir is fine
107 |   VOLTA_HOME="$HOME"
108 |   run volta_home_is_ok
109 |   [ "$status" -eq 0 ]
110 | }
111 | 
112 | @test "volta_home_is_ok - not ok" {
113 |   # file is not ok
114 |   VOLTA_HOME="$(mktemp)"
115 |   run volta_home_is_ok
116 |   [ "$status" -eq 1 ]
117 | }
118 | 
119 | # TODO: test creating symlinks
120 | 


--------------------------------------------------------------------------------
/rust-toolchain.toml:
--------------------------------------------------------------------------------
1 | [toolchain]
2 | channel = "1.75"
3 | components = ["clippy", "rustfmt"]
4 | profile = "minimal"
5 | 


--------------------------------------------------------------------------------
/src/command/completions.rs:
--------------------------------------------------------------------------------
 1 | use std::path::PathBuf;
 2 | 
 3 | use clap::CommandFactory;
 4 | use clap_complete::Shell;
 5 | use log::info;
 6 | 
 7 | use volta_core::{
 8 |     error::{Context, ErrorKind, ExitCode, Fallible},
 9 |     session::{ActivityKind, Session},
10 |     style::{note_prefix, success_prefix},
11 | };
12 | 
13 | use crate::command::Command;
14 | 
15 | #[derive(Debug, clap::Args)]
16 | pub(crate) struct Completions {
17 |     /// Shell to generate completions for
18 |     #[arg(index = 1, ignore_case = true, required = true)]
19 |     shell: Shell,
20 | 
21 |     /// File to write generated completions to
22 |     #[arg(short, long = "output")]
23 |     out_file: Option<PathBuf>,
24 | 
25 |     /// Write over an existing file, if any.
26 |     #[arg(short, long)]
27 |     force: bool,
28 | }
29 | 
30 | impl Command for Completions {
31 |     fn run(self, session: &mut Session) -> Fallible<ExitCode> {
32 |         session.add_event_start(ActivityKind::Completions);
33 | 
34 |         let mut app = crate::cli::Volta::command();
35 |         let app_name = app.get_name().to_owned();
36 |         match self.out_file {
37 |             Some(path) => {
38 |                 if path.is_file() && !self.force {
39 |                     return Err(ErrorKind::CompletionsOutFileError { path }.into());
40 |                 }
41 | 
42 |                 // The user may have passed a path that does not yet exist. If
43 |                 // so, we create it, informing the user we have done so.
44 |                 if let Some(parent) = path.parent() {
45 |                     if !parent.is_dir() {
46 |                         info!(
47 |                             "{} {} does not exist, creating it",
48 |                             note_prefix(),
49 |                             parent.display()
50 |                         );
51 |                         std::fs::create_dir_all(parent).with_context(|| {
52 |                             ErrorKind::CreateDirError {
53 |                                 dir: parent.to_path_buf(),
54 |                             }
55 |                         })?;
56 |                     }
57 |                 }
58 | 
59 |                 let mut file = &std::fs::File::create(&path).with_context(|| {
60 |                     ErrorKind::CompletionsOutFileError {
61 |                         path: path.to_path_buf(),
62 |                     }
63 |                 })?;
64 | 
65 |                 clap_complete::generate(self.shell, &mut app, app_name, &mut file);
66 | 
67 |                 info!(
68 |                     "{} installed completions to {}",
69 |                     success_prefix(),
70 |                     path.display()
71 |                 );
72 |             }
73 |             None => clap_complete::generate(self.shell, &mut app, app_name, &mut std::io::stdout()),
74 |         };
75 | 
76 |         session.add_event_end(ActivityKind::Completions, ExitCode::Success);
77 |         Ok(ExitCode::Success)
78 |     }
79 | }
80 | 


--------------------------------------------------------------------------------
/src/command/fetch.rs:
--------------------------------------------------------------------------------
 1 | use volta_core::error::{ExitCode, Fallible};
 2 | use volta_core::session::{ActivityKind, Session};
 3 | use volta_core::tool;
 4 | 
 5 | use crate::command::Command;
 6 | 
 7 | #[derive(clap::Args)]
 8 | pub(crate) struct Fetch {
 9 |     /// Tools to fetch, like `node`, `yarn@latest` or `your-package@^14.4.3`.
10 |     #[arg(value_name = "tool[@version]", required = true)]
11 |     tools: Vec<String>,
12 | }
13 | 
14 | impl Command for Fetch {
15 |     fn run(self, session: &mut Session) -> Fallible<ExitCode> {
16 |         session.add_event_start(ActivityKind::Fetch);
17 | 
18 |         for tool in tool::Spec::from_strings(&self.tools, "fetch")? {
19 |             tool.resolve(session)?.fetch(session)?;
20 |         }
21 | 
22 |         session.add_event_end(ActivityKind::Fetch, ExitCode::Success);
23 |         Ok(ExitCode::Success)
24 |     }
25 | }
26 | 


--------------------------------------------------------------------------------
/src/command/install.rs:
--------------------------------------------------------------------------------
 1 | use volta_core::error::{ExitCode, Fallible};
 2 | use volta_core::session::{ActivityKind, Session};
 3 | use volta_core::tool::Spec;
 4 | 
 5 | use crate::command::Command;
 6 | 
 7 | #[derive(clap::Args)]
 8 | pub(crate) struct Install {
 9 |     /// Tools to install, like `node`, `yarn@latest` or `your-package@^14.4.3`.
10 |     #[arg(value_name = "tool[@version]", required = true)]
11 |     tools: Vec<String>,
12 | }
13 | 
14 | impl Command for Install {
15 |     fn run(self, session: &mut Session) -> Fallible<ExitCode> {
16 |         session.add_event_start(ActivityKind::Install);
17 | 
18 |         for tool in Spec::from_strings(&self.tools, "install")? {
19 |             tool.resolve(session)?.install(session)?;
20 |         }
21 | 
22 |         session.add_event_end(ActivityKind::Install, ExitCode::Success);
23 |         Ok(ExitCode::Success)
24 |     }
25 | }
26 | 


--------------------------------------------------------------------------------
/src/command/mod.rs:
--------------------------------------------------------------------------------
 1 | pub(crate) mod completions;
 2 | pub(crate) mod fetch;
 3 | pub(crate) mod install;
 4 | pub(crate) mod list;
 5 | pub(crate) mod pin;
 6 | pub(crate) mod run;
 7 | pub(crate) mod setup;
 8 | pub(crate) mod uninstall;
 9 | pub(crate) mod r#use;
10 | pub(crate) mod which;
11 | 
12 | pub(crate) use self::which::Which;
13 | pub(crate) use completions::Completions;
14 | pub(crate) use fetch::Fetch;
15 | pub(crate) use install::Install;
16 | pub(crate) use list::List;
17 | pub(crate) use pin::Pin;
18 | pub(crate) use r#use::Use;
19 | pub(crate) use run::Run;
20 | pub(crate) use setup::Setup;
21 | pub(crate) use uninstall::Uninstall;
22 | 
23 | use volta_core::error::{ExitCode, Fallible};
24 | use volta_core::session::Session;
25 | 
26 | /// A Volta command.
27 | pub(crate) trait Command: Sized {
28 |     /// Executes the command. Returns `Ok(true)` if the process should return 0,
29 |     /// `Ok(false)` if the process should return 1, and `Err(e)` if the process
30 |     /// should return `e.exit_code()`.
31 |     fn run(self, session: &mut Session) -> Fallible<ExitCode>;
32 | }
33 | 


--------------------------------------------------------------------------------
/src/command/pin.rs:
--------------------------------------------------------------------------------
 1 | use volta_core::error::{ExitCode, Fallible};
 2 | use volta_core::session::{ActivityKind, Session};
 3 | use volta_core::tool::Spec;
 4 | 
 5 | use crate::command::Command;
 6 | 
 7 | #[derive(clap::Args)]
 8 | pub(crate) struct Pin {
 9 |     /// Tools to pin, like `node@lts` or `yarn@^1.14`.
10 |     #[arg(value_name = "tool[@version]", required = true)]
11 |     tools: Vec<String>,
12 | }
13 | 
14 | impl Command for Pin {
15 |     fn run(self, session: &mut Session) -> Fallible<ExitCode> {
16 |         session.add_event_start(ActivityKind::Pin);
17 | 
18 |         for tool in Spec::from_strings(&self.tools, "pin")? {
19 |             tool.resolve(session)?.pin(session)?;
20 |         }
21 | 
22 |         session.add_event_end(ActivityKind::Pin, ExitCode::Success);
23 |         Ok(ExitCode::Success)
24 |     }
25 | }
26 | 


--------------------------------------------------------------------------------
/src/command/uninstall.rs:
--------------------------------------------------------------------------------
 1 | use volta_core::error::{ErrorKind, ExitCode, Fallible};
 2 | use volta_core::session::{ActivityKind, Session};
 3 | use volta_core::tool;
 4 | use volta_core::version::VersionSpec;
 5 | 
 6 | use crate::command::Command;
 7 | 
 8 | #[derive(clap::Args)]
 9 | pub(crate) struct Uninstall {
10 |     /// The tool to uninstall, like `ember-cli-update`, `typescript`, or <package>
11 |     tool: String,
12 | }
13 | 
14 | impl Command for Uninstall {
15 |     fn run(self, session: &mut Session) -> Fallible<ExitCode> {
16 |         session.add_event_start(ActivityKind::Uninstall);
17 | 
18 |         let tool = tool::Spec::try_from_str(&self.tool)?;
19 | 
20 |         // For packages, specifically report that we do not support uninstalling
21 |         // specific versions. For runtimes and package managers, we currently
22 |         // *intentionally* let this fall through to inform the user that we do
23 |         // not support uninstalling those *at all*.
24 |         if let tool::Spec::Package(_name, version) = &tool {
25 |             let VersionSpec::None = version else {
26 |                 return Err(ErrorKind::Unimplemented {
27 |                     feature: "uninstalling specific versions of tools".into(),
28 |                 }
29 |                 .into());
30 |             };
31 |         }
32 | 
33 |         tool.uninstall()?;
34 | 
35 |         session.add_event_end(ActivityKind::Uninstall, ExitCode::Success);
36 |         Ok(ExitCode::Success)
37 |     }
38 | }
39 | 


--------------------------------------------------------------------------------
/src/command/use.rs:
--------------------------------------------------------------------------------
 1 | use crate::command::Command;
 2 | use volta_core::error::{ErrorKind, ExitCode, Fallible};
 3 | use volta_core::session::{ActivityKind, Session};
 4 | 
 5 | // NOTE: These use the same text as the `long_about` in crate::cli.
 6 | //       It's hard to abstract since it's in an attribute string.
 7 | 
 8 | pub(crate) const USAGE: &str = "The subcommand `use` is deprecated.
 9 | 
10 |     To install a tool in your toolchain, use `volta install`.
11 |     To pin your project's runtime or package manager, use `volta pin`.
12 | ";
13 | 
14 | const ADVICE: &str = "
15 |     To install a tool in your toolchain, use `volta install`.
16 |     To pin your project's runtime or package manager, use `volta pin`.
17 | ";
18 | 
19 | #[derive(clap::Args)]
20 | pub(crate) struct Use {
21 |     #[allow(dead_code)]
22 |     anything: Vec<String>, // Prevent Clap argument errors when invoking e.g. `volta use node`
23 | }
24 | 
25 | impl Command for Use {
26 |     fn run(self, session: &mut Session) -> Fallible<ExitCode> {
27 |         session.add_event_start(ActivityKind::Help);
28 |         let result = Err(ErrorKind::DeprecatedCommandError {
29 |             command: "use".to_string(),
30 |             advice: ADVICE.to_string(),
31 |         }
32 |         .into());
33 |         session.add_event_end(ActivityKind::Help, ExitCode::InvalidArguments);
34 |         result
35 |     }
36 | }
37 | 


--------------------------------------------------------------------------------
/src/command/which.rs:
--------------------------------------------------------------------------------
 1 | use std::env;
 2 | use std::ffi::OsString;
 3 | 
 4 | use which::which_in;
 5 | 
 6 | use volta_core::error::{Context, ErrorKind, ExitCode, Fallible};
 7 | use volta_core::platform::{Platform, System};
 8 | use volta_core::run::binary::DefaultBinary;
 9 | use volta_core::session::{ActivityKind, Session};
10 | 
11 | use crate::command::Command;
12 | 
13 | #[derive(clap::Args)]
14 | pub(crate) struct Which {
15 |     /// The binary to find, e.g. `node` or `npm`
16 |     binary: OsString,
17 | }
18 | 
19 | impl Command for Which {
20 |     // 1. Start by checking if the user has a tool installed in the project or
21 |     //    as a user default. If so, we're done.
22 |     // 2. Otherwise, use the platform image and/or the system environment to
23 |     //    determine a lookup path to run `which` in.
24 |     fn run(self, session: &mut Session) -> Fallible<ExitCode> {
25 |         session.add_event_start(ActivityKind::Which);
26 | 
27 |         let default_tool = DefaultBinary::from_name(&self.binary, session)?;
28 |         let project_bin_path = session
29 |             .project()?
30 |             .and_then(|project| project.find_bin(&self.binary));
31 | 
32 |         let tool_path = match (default_tool, project_bin_path) {
33 |             (Some(_), Some(bin_path)) => Some(bin_path),
34 |             (Some(tool), _) => Some(tool.bin_path),
35 |             _ => None,
36 |         };
37 | 
38 |         if let Some(path) = tool_path {
39 |             println!("{}", path.to_string_lossy());
40 | 
41 |             let exit_code = ExitCode::Success;
42 |             session.add_event_end(ActivityKind::Which, exit_code);
43 |             return Ok(exit_code);
44 |         }
45 | 
46 |         // Treat any error with obtaining the current platform image as if the image doesn't exist
47 |         // However, errors in obtaining the current working directory or the System path should
48 |         // still be treated as errors.
49 |         let path = match Platform::current(session)
50 |             .unwrap_or(None)
51 |             .and_then(|platform| platform.checkout(session).ok())
52 |             .and_then(|image| image.path().ok())
53 |         {
54 |             Some(path) => path,
55 |             None => System::path()?,
56 |         };
57 | 
58 |         let cwd = env::current_dir().with_context(|| ErrorKind::CurrentDirError)?;
59 |         let exit_code = match which_in(&self.binary, Some(path), cwd) {
60 |             Ok(result) => {
61 |                 println!("{}", result.to_string_lossy());
62 |                 ExitCode::Success
63 |             }
64 |             Err(_) => {
65 |                 // `which_in` Will return an Err if it can't find the binary in the path
66 |                 // In that case, we don't want to print anything out, but we want to return
67 |                 // Exit Code 1 (ExitCode::UnknownError)
68 |                 ExitCode::UnknownError
69 |             }
70 |         };
71 | 
72 |         session.add_event_end(ActivityKind::Which, exit_code);
73 |         Ok(exit_code)
74 |     }
75 | }
76 | 


--------------------------------------------------------------------------------
/src/common.rs:
--------------------------------------------------------------------------------
 1 | use std::process::{Command, ExitStatus};
 2 | 
 3 | use volta_core::error::{Context, ErrorKind, VoltaError};
 4 | use volta_core::layout::{volta_home, volta_install};
 5 | 
 6 | pub enum Error {
 7 |     Volta(VoltaError),
 8 |     Tool(i32),
 9 | }
10 | 
11 | pub fn ensure_layout() -> Result<(), Error> {
12 |     let home = volta_home().map_err(Error::Volta)?;
13 | 
14 |     if !home.layout_file().exists() {
15 |         let install = volta_install().map_err(Error::Volta)?;
16 |         Command::new(install.migrate_executable())
17 |             .env("VOLTA_LOGLEVEL", format!("{}", log::max_level()))
18 |             .status()
19 |             .with_context(|| ErrorKind::CouldNotStartMigration)
20 |             .into_result()?;
21 |     }
22 | 
23 |     Ok(())
24 | }
25 | 
26 | pub trait IntoResult<T> {
27 |     fn into_result(self) -> Result<T, Error>;
28 | }
29 | 
30 | impl IntoResult<()> for Result<ExitStatus, VoltaError> {
31 |     fn into_result(self) -> Result<(), Error> {
32 |         match self {
33 |             Ok(status) => {
34 |                 if status.success() {
35 |                     Ok(())
36 |                 } else {
37 |                     let code = status.code().unwrap_or(1);
38 |                     Err(Error::Tool(code))
39 |                 }
40 |             }
41 |             Err(err) => Err(Error::Volta(err)),
42 |         }
43 |     }
44 | }
45 | 


--------------------------------------------------------------------------------
/src/main.rs:
--------------------------------------------------------------------------------
 1 | #[macro_use]
 2 | mod command;
 3 | mod cli;
 4 | 
 5 | use clap::Parser;
 6 | 
 7 | use volta_core::error::report_error;
 8 | use volta_core::log::{LogContext, LogVerbosity, Logger};
 9 | use volta_core::session::{ActivityKind, Session};
10 | 
11 | mod common;
12 | use common::{ensure_layout, Error};
13 | 
14 | /// The entry point for the `volta` CLI.
15 | pub fn main() {
16 |     let volta = cli::Volta::parse();
17 |     let verbosity = match (&volta.verbose, &volta.quiet) {
18 |         (false, false) => LogVerbosity::Default,
19 |         (true, false) => {
20 |             if volta.very_verbose {
21 |                 LogVerbosity::VeryVerbose
22 |             } else {
23 |                 LogVerbosity::Verbose
24 |             }
25 |         }
26 |         (false, true) => LogVerbosity::Quiet,
27 |         (true, true) => {
28 |             unreachable!("Clap should prevent the user from providing both --verbose and --quiet")
29 |         }
30 |     };
31 |     Logger::init(LogContext::Volta, verbosity).expect("Only a single logger should be initialized");
32 |     log::trace!("log level: {verbosity:?}");
33 | 
34 |     let mut session = Session::init();
35 |     session.add_event_start(ActivityKind::Volta);
36 | 
37 |     let result = ensure_layout().and_then(|()| volta.run(&mut session).map_err(Error::Volta));
38 |     match result {
39 |         Ok(exit_code) => {
40 |             session.add_event_end(ActivityKind::Volta, exit_code);
41 |             session.exit(exit_code);
42 |         }
43 |         Err(Error::Tool(code)) => {
44 |             session.add_event_tool_end(ActivityKind::Volta, code);
45 |             session.exit_tool(code);
46 |         }
47 |         Err(Error::Volta(err)) => {
48 |             report_error(env!("CARGO_PKG_VERSION"), &err);
49 |             session.add_event_error(ActivityKind::Volta, &err);
50 |             let code = err.exit_code();
51 |             session.add_event_end(ActivityKind::Volta, code);
52 |             session.exit(code);
53 |         }
54 |     }
55 | }
56 | 


--------------------------------------------------------------------------------
/src/volta-migrate.rs:
--------------------------------------------------------------------------------
 1 | use volta_core::error::{report_error, ExitCode};
 2 | use volta_core::layout::volta_home;
 3 | use volta_core::log::{LogContext, LogVerbosity, Logger};
 4 | use volta_migrate::run_migration;
 5 | 
 6 | pub fn main() {
 7 |     Logger::init(LogContext::Migration, LogVerbosity::Default)
 8 |         .expect("Only a single Logger should be initialized");
 9 | 
10 |     // In order to migrate the existing Volta directory while avoiding unconditional changes to the user's system,
11 |     // the Homebrew formula runs volta-migrate with `--no-create` flag in the post-install phase.
12 |     let no_create = matches!(std::env::args_os().nth(1), Some(flag) if flag == "--no-create");
13 |     if no_create && volta_home().map_or(true, |home| !home.root().exists()) {
14 |         ExitCode::Success.exit();
15 |     }
16 | 
17 |     let exit_code = match run_migration() {
18 |         Ok(()) => ExitCode::Success,
19 |         Err(err) => {
20 |             report_error(env!("CARGO_PKG_VERSION"), &err);
21 |             err.exit_code()
22 |         }
23 |     };
24 | 
25 |     exit_code.exit();
26 | }
27 | 


--------------------------------------------------------------------------------
/src/volta-shim.rs:
--------------------------------------------------------------------------------
 1 | mod common;
 2 | 
 3 | use common::{ensure_layout, Error, IntoResult};
 4 | use volta_core::error::{report_error, ExitCode};
 5 | use volta_core::log::{LogContext, LogVerbosity, Logger};
 6 | use volta_core::run::execute_shim;
 7 | use volta_core::session::{ActivityKind, Session};
 8 | use volta_core::signal::setup_signal_handler;
 9 | 
10 | pub fn main() {
11 |     Logger::init(LogContext::Shim, LogVerbosity::Default)
12 |         .expect("Only a single Logger should be initialized");
13 |     setup_signal_handler();
14 | 
15 |     let mut session = Session::init();
16 |     session.add_event_start(ActivityKind::Tool);
17 | 
18 |     let result = ensure_layout().and_then(|()| execute_shim(&mut session).into_result());
19 |     match result {
20 |         Ok(()) => {
21 |             session.add_event_end(ActivityKind::Tool, ExitCode::Success);
22 |             session.exit(ExitCode::Success);
23 |         }
24 |         Err(Error::Tool(code)) => {
25 |             session.add_event_tool_end(ActivityKind::Tool, code);
26 |             session.exit_tool(code);
27 |         }
28 |         Err(Error::Volta(err)) => {
29 |             report_error(env!("CARGO_PKG_VERSION"), &err);
30 |             session.add_event_error(ActivityKind::Tool, &err);
31 |             session.add_event_end(ActivityKind::Tool, err.exit_code());
32 |             session.exit(ExitCode::ExecutionFailure);
33 |         }
34 |     }
35 | }
36 | 


--------------------------------------------------------------------------------
/tests/acceptance/main.rs:
--------------------------------------------------------------------------------
 1 | use cfg_if::cfg_if;
 2 | 
 3 | cfg_if! {
 4 |     if #[cfg(feature = "mock-network")] {
 5 |         mod support;
 6 | 
 7 |         // test files
 8 |         mod corrupted_download;
 9 |         mod direct_install;
10 |         mod direct_uninstall;
11 |         mod execute_binary;
12 |         mod hooks;
13 |         mod merged_platform;
14 |         mod migrations;
15 |         mod run_shim_directly;
16 |         mod verbose_errors;
17 |         mod volta_bypass;
18 |         mod volta_install;
19 |         mod volta_pin;
20 |         mod volta_run;
21 |         mod volta_uninstall;
22 |     }
23 | }
24 | 


--------------------------------------------------------------------------------
/tests/acceptance/run_shim_directly.rs:
--------------------------------------------------------------------------------
 1 | use crate::support::sandbox::{sandbox, shim_exe};
 2 | use hamcrest2::assert_that;
 3 | use hamcrest2::prelude::*;
 4 | use test_support::matchers::execs;
 5 | 
 6 | use volta_core::error::ExitCode;
 7 | 
 8 | #[test]
 9 | fn shows_pretty_error_when_calling_shim_directly() {
10 |     let s = sandbox().build();
11 | 
12 |     assert_that!(
13 |         s.process(shim_exe()),
14 |         execs()
15 |             .with_status(ExitCode::ExecutionFailure as i32)
16 |             .with_stderr_contains("[..]should not be called directly[..]")
17 |     );
18 | }
19 | 


--------------------------------------------------------------------------------
/tests/acceptance/support/events_helpers.rs:
--------------------------------------------------------------------------------
  1 | use std::fs::File;
  2 | 
  3 | use crate::support::sandbox::Sandbox;
  4 | use hamcrest2::assert_that;
  5 | use hamcrest2::prelude::*;
  6 | 
  7 | use volta_core::event::{Event, EventKind};
  8 | 
  9 | pub enum EventKindMatcher<'a> {
 10 |     Start,
 11 |     End { exit_code: i32 },
 12 |     Error { exit_code: i32, error: &'a str },
 13 |     ToolEnd { exit_code: i32 },
 14 |     Args { argv: &'a str },
 15 | }
 16 | 
 17 | pub fn match_start() -> EventKindMatcher<'static> {
 18 |     EventKindMatcher::Start
 19 | }
 20 | 
 21 | pub fn match_error(exit_code: i32, error: &str) -> EventKindMatcher {
 22 |     EventKindMatcher::Error { exit_code, error }
 23 | }
 24 | 
 25 | pub fn match_end(exit_code: i32) -> EventKindMatcher<'static> {
 26 |     EventKindMatcher::End { exit_code }
 27 | }
 28 | 
 29 | pub fn match_tool_end(exit_code: i32) -> EventKindMatcher<'static> {
 30 |     EventKindMatcher::ToolEnd { exit_code }
 31 | }
 32 | 
 33 | pub fn match_args(argv: &str) -> EventKindMatcher {
 34 |     EventKindMatcher::Args { argv }
 35 | }
 36 | 
 37 | pub fn assert_events(sandbox: &Sandbox, matchers: Vec<(&str, EventKindMatcher)>) {
 38 |     let events_path = sandbox.root().join("events.json");
 39 |     assert_that!(&events_path, file_exists());
 40 | 
 41 |     let events_file = File::open(events_path).expect("Error reading 'events.json' file in sandbox");
 42 |     let events: Vec<Event> = serde_json::de::from_reader(events_file)
 43 |         .expect("Error parsing 'events.json' file in sandbox");
 44 |     assert_that!(events.len(), eq(matchers.len()));
 45 | 
 46 |     for (i, matcher) in matchers.iter().enumerate() {
 47 |         assert_that!(&events[i].name, eq(matcher.0));
 48 |         match matcher.1 {
 49 |             EventKindMatcher::Start => {
 50 |                 assert_that!(&events[i].event, eq(&EventKind::Start));
 51 |             }
 52 |             EventKindMatcher::End {
 53 |                 exit_code: expected_exit_code,
 54 |             } => {
 55 |                 if let EventKind::End { exit_code } = &events[i].event {
 56 |                     assert_that!(*exit_code, eq(expected_exit_code));
 57 |                 } else {
 58 |                     panic!(
 59 |                         "Expected: End {{ exit_code: {} }}, Got: {:?}",
 60 |                         expected_exit_code, events[i].event
 61 |                     );
 62 |                 }
 63 |             }
 64 |             EventKindMatcher::Error {
 65 |                 exit_code: expected_exit_code,
 66 |                 error: expected_error,
 67 |             } => {
 68 |                 if let EventKind::Error {
 69 |                     exit_code, error, ..
 70 |                 } = &events[i].event
 71 |                 {
 72 |                     assert_that!(*exit_code, eq(expected_exit_code));
 73 |                     assert_that!(error.clone(), matches_regex(expected_error));
 74 |                 } else {
 75 |                     panic!(
 76 |                         "Expected: Error {{ exit_code: {}, error: {} }}, Got: {:?}",
 77 |                         expected_exit_code, expected_error, events[i].event
 78 |                     );
 79 |                 }
 80 |             }
 81 |             EventKindMatcher::ToolEnd {
 82 |                 exit_code: expected_exit_code,
 83 |             } => {
 84 |                 if let EventKind::End { exit_code } = &events[i].event {
 85 |                     assert_that!(*exit_code, eq(expected_exit_code));
 86 |                 } else {
 87 |                     panic!(
 88 |                         "Expected: ToolEnd {{ exit_code: {} }}, Got: {:?}",
 89 |                         expected_exit_code, events[i].event
 90 |                     );
 91 |                 }
 92 |             }
 93 |             EventKindMatcher::Args {
 94 |                 argv: expected_argv,
 95 |             } => {
 96 |                 if let EventKind::Args { argv } = &events[i].event {
 97 |                     assert_that!(argv.clone(), matches_regex(expected_argv));
 98 |                 } else {
 99 |                     panic!(
100 |                         "Expected: Args {{ argv: {} }}, Got: {:?}",
101 |                         expected_argv, events[i].event
102 |                     );
103 |                 }
104 |             }
105 |         }
106 |     }
107 | }
108 | 


--------------------------------------------------------------------------------
/tests/acceptance/support/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod events_helpers;
2 | pub mod sandbox;
3 | 


--------------------------------------------------------------------------------
/tests/acceptance/volta_bypass.rs:
--------------------------------------------------------------------------------
 1 | use crate::support::sandbox::{sandbox, shim_exe};
 2 | use hamcrest2::assert_that;
 3 | use hamcrest2::prelude::*;
 4 | use test_support::matchers::execs;
 5 | 
 6 | use volta_core::error::ExitCode;
 7 | 
 8 | #[test]
 9 | fn shim_skips_platform_checks_on_bypass() {
10 |     let s = sandbox()
11 |         .env("VOLTA_BYPASS", "1")
12 |         .env(
13 |             "VOLTA_INSTALL_DIR",
14 |             &shim_exe().parent().unwrap().to_string_lossy(),
15 |         )
16 |         .build();
17 | 
18 |     #[cfg(unix)]
19 |     assert_that!(
20 |         s.process(shim_exe()),
21 |         execs()
22 |             .with_status(ExitCode::ExecutionFailure as i32)
23 |             .with_stderr_contains("VOLTA_BYPASS is enabled[..]")
24 |     );
25 | 
26 |     #[cfg(windows)]
27 |     assert_that!(
28 |         s.process(shim_exe()),
29 |         execs()
30 |             .with_status(ExitCode::UnknownError as i32)
31 |             .with_stderr_contains("[..]is not recognized as an internal or external command[..]")
32 |     );
33 | }
34 | 


--------------------------------------------------------------------------------
/tests/fixtures/cli-dist-2.4.159.tgz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/volta-cli/volta/a7384fa4fc7a0eca961032da4d962d94218b5868/tests/fixtures/cli-dist-2.4.159.tgz


--------------------------------------------------------------------------------
/tests/fixtures/cli-dist-3.12.99.tgz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/volta-cli/volta/a7384fa4fc7a0eca961032da4d962d94218b5868/tests/fixtures/cli-dist-3.12.99.tgz


--------------------------------------------------------------------------------
/tests/fixtures/cli-dist-3.2.42.tgz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/volta-cli/volta/a7384fa4fc7a0eca961032da4d962d94218b5868/tests/fixtures/cli-dist-3.2.42.tgz


--------------------------------------------------------------------------------
/tests/fixtures/cli-dist-3.7.71.tgz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/volta-cli/volta/a7384fa4fc7a0eca961032da4d962d94218b5868/tests/fixtures/cli-dist-3.7.71.tgz


--------------------------------------------------------------------------------
/tests/fixtures/node-v0.0.1-darwin-x64.tar.gz:
--------------------------------------------------------------------------------
1 | CORRUPTED
2 | 


--------------------------------------------------------------------------------
/tests/fixtures/node-v0.0.1-linux-arm64.tar.gz:
--------------------------------------------------------------------------------
1 | CORRUPTED
2 | 


--------------------------------------------------------------------------------
/tests/fixtures/node-v0.0.1-linux-x64.tar.gz:
--------------------------------------------------------------------------------
1 | CORRUPTED
2 | 


--------------------------------------------------------------------------------
/tests/fixtures/node-v0.0.1-win-x64.zip:
--------------------------------------------------------------------------------
1 | CORRUPTED
2 | 


--------------------------------------------------------------------------------
/tests/fixtures/node-v0.0.1-win-x86.zip:
--------------------------------------------------------------------------------
1 | CORRUPTED
2 | 


--------------------------------------------------------------------------------
/tests/fixtures/node-v10.99.1040-darwin-x64.tar.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/volta-cli/volta/a7384fa4fc7a0eca961032da4d962d94218b5868/tests/fixtures/node-v10.99.1040-darwin-x64.tar.gz


--------------------------------------------------------------------------------
/tests/fixtures/node-v10.99.1040-linux-arm64.tar.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/volta-cli/volta/a7384fa4fc7a0eca961032da4d962d94218b5868/tests/fixtures/node-v10.99.1040-linux-arm64.tar.gz


--------------------------------------------------------------------------------
/tests/fixtures/node-v10.99.1040-linux-x64.tar.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/volta-cli/volta/a7384fa4fc7a0eca961032da4d962d94218b5868/tests/fixtures/node-v10.99.1040-linux-x64.tar.gz


--------------------------------------------------------------------------------
/tests/fixtures/node-v10.99.1040-win-x64.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/volta-cli/volta/a7384fa4fc7a0eca961032da4d962d94218b5868/tests/fixtures/node-v10.99.1040-win-x64.zip


--------------------------------------------------------------------------------
/tests/fixtures/node-v10.99.1040-win-x86.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/volta-cli/volta/a7384fa4fc7a0eca961032da4d962d94218b5868/tests/fixtures/node-v10.99.1040-win-x86.zip


--------------------------------------------------------------------------------
/tests/fixtures/node-v6.19.62-darwin-x64.tar.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/volta-cli/volta/a7384fa4fc7a0eca961032da4d962d94218b5868/tests/fixtures/node-v6.19.62-darwin-x64.tar.gz


--------------------------------------------------------------------------------
/tests/fixtures/node-v6.19.62-linux-arm64.tar.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/volta-cli/volta/a7384fa4fc7a0eca961032da4d962d94218b5868/tests/fixtures/node-v6.19.62-linux-arm64.tar.gz


--------------------------------------------------------------------------------
/tests/fixtures/node-v6.19.62-linux-x64.tar.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/volta-cli/volta/a7384fa4fc7a0eca961032da4d962d94218b5868/tests/fixtures/node-v6.19.62-linux-x64.tar.gz


--------------------------------------------------------------------------------
/tests/fixtures/node-v6.19.62-win-x64.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/volta-cli/volta/a7384fa4fc7a0eca961032da4d962d94218b5868/tests/fixtures/node-v6.19.62-win-x64.zip


--------------------------------------------------------------------------------
/tests/fixtures/node-v6.19.62-win-x86.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/volta-cli/volta/a7384fa4fc7a0eca961032da4d962d94218b5868/tests/fixtures/node-v6.19.62-win-x86.zip


--------------------------------------------------------------------------------
/tests/fixtures/node-v8.9.10-darwin-x64.tar.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/volta-cli/volta/a7384fa4fc7a0eca961032da4d962d94218b5868/tests/fixtures/node-v8.9.10-darwin-x64.tar.gz


--------------------------------------------------------------------------------
/tests/fixtures/node-v8.9.10-linux-arm64.tar.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/volta-cli/volta/a7384fa4fc7a0eca961032da4d962d94218b5868/tests/fixtures/node-v8.9.10-linux-arm64.tar.gz


--------------------------------------------------------------------------------
/tests/fixtures/node-v8.9.10-linux-x64.tar.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/volta-cli/volta/a7384fa4fc7a0eca961032da4d962d94218b5868/tests/fixtures/node-v8.9.10-linux-x64.tar.gz


--------------------------------------------------------------------------------
/tests/fixtures/node-v8.9.10-win-x64.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/volta-cli/volta/a7384fa4fc7a0eca961032da4d962d94218b5868/tests/fixtures/node-v8.9.10-win-x64.zip


--------------------------------------------------------------------------------
/tests/fixtures/node-v8.9.10-win-x86.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/volta-cli/volta/a7384fa4fc7a0eca961032da4d962d94218b5868/tests/fixtures/node-v8.9.10-win-x86.zip


--------------------------------------------------------------------------------
/tests/fixtures/node-v9.27.6-darwin-x64.tar.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/volta-cli/volta/a7384fa4fc7a0eca961032da4d962d94218b5868/tests/fixtures/node-v9.27.6-darwin-x64.tar.gz


--------------------------------------------------------------------------------
/tests/fixtures/node-v9.27.6-linux-arm64.tar.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/volta-cli/volta/a7384fa4fc7a0eca961032da4d962d94218b5868/tests/fixtures/node-v9.27.6-linux-arm64.tar.gz


--------------------------------------------------------------------------------
/tests/fixtures/node-v9.27.6-linux-x64.tar.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/volta-cli/volta/a7384fa4fc7a0eca961032da4d962d94218b5868/tests/fixtures/node-v9.27.6-linux-x64.tar.gz


--------------------------------------------------------------------------------
/tests/fixtures/node-v9.27.6-win-x64.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/volta-cli/volta/a7384fa4fc7a0eca961032da4d962d94218b5868/tests/fixtures/node-v9.27.6-win-x64.zip


--------------------------------------------------------------------------------
/tests/fixtures/node-v9.27.6-win-x86.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/volta-cli/volta/a7384fa4fc7a0eca961032da4d962d94218b5868/tests/fixtures/node-v9.27.6-win-x86.zip


--------------------------------------------------------------------------------
/tests/fixtures/npm-1.2.3.tgz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/volta-cli/volta/a7384fa4fc7a0eca961032da4d962d94218b5868/tests/fixtures/npm-1.2.3.tgz


--------------------------------------------------------------------------------
/tests/fixtures/npm-4.5.6.tgz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/volta-cli/volta/a7384fa4fc7a0eca961032da4d962d94218b5868/tests/fixtures/npm-4.5.6.tgz


--------------------------------------------------------------------------------
/tests/fixtures/npm-8.1.5.tgz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/volta-cli/volta/a7384fa4fc7a0eca961032da4d962d94218b5868/tests/fixtures/npm-8.1.5.tgz


--------------------------------------------------------------------------------
/tests/fixtures/pnpm-0.0.1.tgz:
--------------------------------------------------------------------------------
1 | CORRUPTED
2 | 


--------------------------------------------------------------------------------
/tests/fixtures/pnpm-6.34.0.tgz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/volta-cli/volta/a7384fa4fc7a0eca961032da4d962d94218b5868/tests/fixtures/pnpm-6.34.0.tgz


--------------------------------------------------------------------------------
/tests/fixtures/pnpm-7.7.1.tgz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/volta-cli/volta/a7384fa4fc7a0eca961032da4d962d94218b5868/tests/fixtures/pnpm-7.7.1.tgz


--------------------------------------------------------------------------------
/tests/fixtures/volta-test-1.0.0.tgz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/volta-cli/volta/a7384fa4fc7a0eca961032da4d962d94218b5868/tests/fixtures/volta-test-1.0.0.tgz


--------------------------------------------------------------------------------
/tests/fixtures/yarn-0.0.1.tgz:
--------------------------------------------------------------------------------
1 | CORRUPTED
2 | 


--------------------------------------------------------------------------------
/tests/fixtures/yarn-1.12.99.tgz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/volta-cli/volta/a7384fa4fc7a0eca961032da4d962d94218b5868/tests/fixtures/yarn-1.12.99.tgz


--------------------------------------------------------------------------------
/tests/fixtures/yarn-1.2.42.tgz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/volta-cli/volta/a7384fa4fc7a0eca961032da4d962d94218b5868/tests/fixtures/yarn-1.2.42.tgz


--------------------------------------------------------------------------------
/tests/fixtures/yarn-1.4.159.tgz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/volta-cli/volta/a7384fa4fc7a0eca961032da4d962d94218b5868/tests/fixtures/yarn-1.4.159.tgz


--------------------------------------------------------------------------------
/tests/fixtures/yarn-1.7.71.tgz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/volta-cli/volta/a7384fa4fc7a0eca961032da4d962d94218b5868/tests/fixtures/yarn-1.7.71.tgz


--------------------------------------------------------------------------------
/tests/smoke/autodownload.rs:
--------------------------------------------------------------------------------
 1 | use crate::support::temp_project::temp_project;
 2 | 
 3 | use hamcrest2::assert_that;
 4 | use hamcrest2::prelude::*;
 5 | use test_support::matchers::execs;
 6 | 
 7 | static PACKAGE_JSON_WITH_PINNED_NODE: &str = r#"{
 8 |     "name": "test-package",
 9 |     "volta": {
10 |         "node": "14.15.5"
11 |     }
12 | }"#;
13 | 
14 | static PACKAGE_JSON_WITH_PINNED_NODE_NPM: &str = r#"{
15 |     "name": "test-package",
16 |     "volta": {
17 |         "node": "17.3.0",
18 |         "npm": "8.5.1"
19 |     }
20 | }"#;
21 | 
22 | static PACKAGE_JSON_WITH_PINNED_NODE_YARN_1: &str = r#"{
23 |     "name": "test-package",
24 |     "volta": {
25 |         "node": "16.11.1",
26 |         "yarn": "1.22.16"
27 |     }
28 | }"#;
29 | 
30 | static PACKAGE_JSON_WITH_PINNED_NODE_YARN_3: &str = r#"{
31 |     "name": "test-package",
32 |     "volta": {
33 |         "node": "16.14.0",
34 |         "yarn": "3.1.0"
35 |     }
36 | }"#;
37 | 
38 | #[test]
39 | fn autodownload_node() {
40 |     let p = temp_project()
41 |         .package_json(PACKAGE_JSON_WITH_PINNED_NODE)
42 |         .build();
43 | 
44 |     assert_that!(
45 |         p.node("--version"),
46 |         execs().with_status(0).with_stdout_contains("v14.15.5")
47 |     );
48 | }
49 | 
50 | #[test]
51 | fn autodownload_npm() {
52 |     let p = temp_project()
53 |         .package_json(PACKAGE_JSON_WITH_PINNED_NODE_NPM)
54 |         .build();
55 | 
56 |     assert_that!(
57 |         p.npm("--version"),
58 |         execs().with_status(0).with_stdout_contains("8.5.1")
59 |     );
60 | }
61 | 
62 | #[test]
63 | fn autodownload_yarn_1() {
64 |     let p = temp_project()
65 |         .package_json(PACKAGE_JSON_WITH_PINNED_NODE_YARN_1)
66 |         .build();
67 | 
68 |     assert_that!(
69 |         p.yarn("--version"),
70 |         execs().with_status(0).with_stdout_contains("1.22.16")
71 |     );
72 | }
73 | 
74 | #[test]
75 | fn autodownload_yarn_3() {
76 |     let p = temp_project()
77 |         .package_json(PACKAGE_JSON_WITH_PINNED_NODE_YARN_3)
78 |         .build();
79 | 
80 |     assert_that!(
81 |         p.yarn("--version"),
82 |         execs().with_status(0).with_stdout_contains("3.1.0")
83 |     );
84 | }
85 | 


--------------------------------------------------------------------------------
/tests/smoke/direct_install.rs:
--------------------------------------------------------------------------------
 1 | use crate::support::temp_project::temp_project;
 2 | use hamcrest2::assert_that;
 3 | use hamcrest2::prelude::*;
 4 | use test_support::matchers::execs;
 5 | 
 6 | #[test]
 7 | fn npm_global_install() {
 8 |     let p = temp_project().build();
 9 | 
10 |     // Have to install node to ensure npm is available
11 |     assert_that!(p.volta("install node@14.1.0"), execs().with_status(0));
12 | 
13 |     assert_that!(
14 |         p.npm("install --global typescript@3.9.4 yarn@1.16.0 ../../../../tests/fixtures/volta-test-1.0.0.tgz"),
15 |         execs().with_status(0)
16 |     );
17 | 
18 |     assert!(p.shim_exists("tsc"));
19 |     assert!(p.shim_exists("tsserver"));
20 |     assert!(p.package_is_installed("typescript"));
21 |     assert_that!(
22 |         p.exec_shim("tsc", "--version"),
23 |         execs().with_status(0).with_stdout_contains("Version 3.9.4")
24 |     );
25 | 
26 |     assert!(p.yarn_version_is_fetched("1.16.0"));
27 |     assert!(p.yarn_version_is_unpacked("1.16.0"));
28 |     p.assert_yarn_version_is_installed("1.16.0");
29 | 
30 |     assert_that!(
31 |         p.yarn("--version"),
32 |         execs().with_status(0).with_stdout_contains("1.16.0")
33 |     );
34 | 
35 |     assert!(p.shim_exists("volta-test"));
36 |     assert!(p.package_is_installed("volta-test"));
37 |     assert_that!(
38 |         p.exec_shim("volta-test", ""),
39 |         execs()
40 |             .with_status(0)
41 |             .with_stdout_contains("Volta test successful")
42 |     );
43 | }
44 | 
45 | #[test]
46 | fn yarn_global_add() {
47 |     let p = temp_project().build();
48 | 
49 |     let tarball_path = p
50 |         .root()
51 |         .join("../../../../tests/fixtures/volta-test-1.0.0.tgz")
52 |         .canonicalize()
53 |         .unwrap();
54 | 
55 |     // Have to install node and yarn first
56 |     assert_that!(
57 |         p.volta("install node@14.2.0 yarn@1.22.5"),
58 |         execs().with_status(0)
59 |     );
60 | 
61 |     assert_that!(
62 |         p.yarn(&format!(
63 |             "global add typescript@4.0.2 npm@6.4.0 file:{}",
64 |             tarball_path.display()
65 |         )),
66 |         execs().with_status(0)
67 |     );
68 | 
69 |     assert!(p.shim_exists("tsc"));
70 |     assert!(p.shim_exists("tsserver"));
71 |     assert!(p.package_is_installed("typescript"));
72 |     assert_that!(
73 |         p.exec_shim("tsc", "--version"),
74 |         execs().with_status(0).with_stdout_contains("Version 4.0.2")
75 |     );
76 | 
77 |     assert!(p.npm_version_is_fetched("6.4.0"));
78 |     assert!(p.npm_version_is_unpacked("6.4.0"));
79 |     p.assert_npm_version_is_installed("6.4.0");
80 | 
81 |     assert_that!(
82 |         p.npm("--version"),
83 |         execs().with_status(0).with_stdout_contains("6.4.0")
84 |     );
85 | 
86 |     assert!(p.shim_exists("volta-test"));
87 |     assert!(p.package_is_installed("volta-test"));
88 |     assert_that!(
89 |         p.exec_shim("volta-test", ""),
90 |         execs()
91 |             .with_status(0)
92 |             .with_stdout_contains("Volta test successful")
93 |     );
94 | }
95 | 


--------------------------------------------------------------------------------
/tests/smoke/direct_upgrade.rs:
--------------------------------------------------------------------------------
 1 | use crate::support::temp_project::temp_project;
 2 | use hamcrest2::assert_that;
 3 | use hamcrest2::prelude::*;
 4 | use test_support::matchers::execs;
 5 | use volta_core::error::ExitCode;
 6 | 
 7 | #[test]
 8 | fn npm_global_update() {
 9 |     let p = temp_project().build();
10 | 
11 |     // Install Node and typescript
12 |     assert_that!(
13 |         p.volta("install node@14.10.1 typescript@2.8.4"),
14 |         execs().with_status(0)
15 |     );
16 |     // Confirm correct version of typescript installed
17 |     assert_that!(
18 |         p.exec_shim("tsc", "--version"),
19 |         execs().with_status(0).with_stdout_contains("Version 2.8.4")
20 |     );
21 | 
22 |     // Update typescript
23 |     assert_that!(p.npm("update --global typescript"), execs().with_status(0));
24 |     // Confirm update completed successfully
25 |     assert_that!(
26 |         p.exec_shim("tsc", "--version"),
27 |         execs().with_status(0).with_stdout_contains("Version 2.9.2")
28 |     );
29 | 
30 |     // Revert typescript update
31 |     assert_that!(p.npm("i -g typescript@2.8.4"), execs().with_status(0));
32 |     // Update all packages (should include typescript)
33 |     assert_that!(p.npm("update --global"), execs().with_status(0));
34 |     // Confirm update
35 |     assert_that!(
36 |         p.exec_shim("tsc", "--version"),
37 |         execs().with_status(0).with_stdout_contains("Version 2.9.2")
38 |     );
39 | 
40 |     // Confirm that attempting to upgrade using `yarn` fails
41 |     assert_that!(
42 |         p.yarn("global upgrade typescript"),
43 |         execs()
44 |             .with_status(ExitCode::ExecutionFailure as i32)
45 |             .with_stderr_contains("[..]The package 'typescript' was installed using npm.")
46 |     );
47 | }
48 | 
49 | #[test]
50 | fn yarn_global_update() {
51 |     let p = temp_project().build();
52 | 
53 |     // Install Node and Yarn
54 |     assert_that!(
55 |         p.volta("install node@14.10.1 yarn@1.22.5"),
56 |         execs().with_status(0)
57 |     );
58 |     // Install typescript
59 |     assert_that!(
60 |         p.yarn("global add typescript@2.8.4"),
61 |         execs().with_status(0)
62 |     );
63 |     // Confirm correct version of typescript installed
64 |     assert_that!(
65 |         p.exec_shim("tsc", "--version"),
66 |         execs().with_status(0).with_stdout_contains("Version 2.8.4")
67 |     );
68 | 
69 |     // Upgrade typescript
70 |     assert_that!(
71 |         p.yarn("global upgrade typescript@2.9"),
72 |         execs().with_status(0)
73 |     );
74 |     // Confirm upgrade completed successfully
75 |     assert_that!(
76 |         p.exec_shim("tsc", "--version"),
77 |         execs().with_status(0).with_stdout_contains("Version 2.9.2")
78 |     );
79 | 
80 |     // Note: Since Yarn always installs the latest version that matches your requirements and
81 |     // 'upgrade' also gets the latest version that matches (which can change over time), an
82 |     // immediate call to 'yarn upgrade' without packages won't result in any change.
83 | 
84 |     // This is in contrast to npm, which treats your installed version as a caret specifier when
85 |     // runnin `npm update`
86 | 
87 |     // Confirm that attempting to upgrade using `npm` fails
88 |     assert_that!(
89 |         p.npm("update -g typescript"),
90 |         execs()
91 |             .with_status(ExitCode::ExecutionFailure as i32)
92 |             .with_stderr_contains("[..]The package 'typescript' was installed using Yarn.")
93 |     );
94 | }
95 | 


--------------------------------------------------------------------------------
/tests/smoke/main.rs:
--------------------------------------------------------------------------------
 1 | // Smoke tests for Volta, that will be run in CI.
 2 | //
 3 | // To run these locally:
 4 | // (CAUTION: this will destroy the Volta installation on the system where this is run)
 5 | //
 6 | // ```
 7 | // VOLTA_LOGLEVEL=debug cargo test --test smoke --features smoke-tests -- --test-threads 1
 8 | // ```
 9 | //
10 | // Also note that each test uses a different version of node and yarn. This is to prevent
11 | // false positives if the tests are not cleaned up correctly. Any new tests should use
12 | // different versions of node and yarn.
13 | 
14 | cfg_if::cfg_if! {
15 |     if #[cfg(all(unix, feature = "smoke-tests"))] {
16 |         mod autodownload;
17 |         mod direct_install;
18 |         mod direct_upgrade;
19 |         mod npm_link;
20 |         mod package_migration;
21 |         pub mod support;
22 |         mod volta_fetch;
23 |         mod volta_install;
24 |         mod volta_run;
25 |     }
26 | }
27 | 


--------------------------------------------------------------------------------
/tests/smoke/npm_link.rs:
--------------------------------------------------------------------------------
 1 | use crate::support::temp_project::temp_project;
 2 | use hamcrest2::assert_that;
 3 | use hamcrest2::prelude::*;
 4 | use test_support::matchers::execs;
 5 | 
 6 | const PACKAGE_JSON: &str = r#"
 7 | {
 8 |     "name": "my-library",
 9 |     "version": "1.0.0",
10 |     "bin": {
11 |         "mylibrary": "./index.js"
12 |     }
13 | }"#;
14 | 
15 | const INDEX_JS: &str = r#"#!/usr/bin/env node
16 | 
17 | console.log('VOLTA TEST');
18 | "#;
19 | 
20 | #[test]
21 | fn link_unlink_local_project() {
22 |     let p = temp_project()
23 |         .package_json(PACKAGE_JSON)
24 |         .project_file("index.js", INDEX_JS)
25 |         .build();
26 | 
27 |     // Install node to ensure npm is available
28 |     assert_that!(p.volta("install node@14.15.1"), execs().with_status(0));
29 | 
30 |     // Link the current project as a global
31 |     assert_that!(p.npm("link"), execs().with_status(0));
32 |     // Executable should be available
33 |     assert!(p.shim_exists("mylibrary"));
34 |     assert!(p.package_is_installed("my-library"));
35 |     assert_that!(
36 |         p.exec_shim("mylibrary", ""),
37 |         execs().with_status(0).with_stdout_contains("VOLTA TEST")
38 |     );
39 | 
40 |     // Unlink the current project
41 |     assert_that!(p.npm("unlink"), execs().with_status(0));
42 |     // Executable should no longer be available
43 |     assert!(!p.shim_exists("mylibrary"));
44 |     assert!(!p.package_is_installed("my-library"));
45 | }
46 | 
47 | #[test]
48 | fn link_global_into_current_project() {
49 |     let p = temp_project().package_json(PACKAGE_JSON).build();
50 | 
51 |     assert_that!(
52 |         p.volta("install node@14.19.0 typescript@4.1.2"),
53 |         execs().with_status(0)
54 |     );
55 | 
56 |     // Link typescript into the current project
57 |     assert_that!(p.npm("link typescript"), execs().with_status(0));
58 |     // Typescript should now be available inside the node_modules directory
59 |     assert!(p.project_path_exists("node_modules/typescript"));
60 |     assert!(p.project_path_exists("node_modules/typescript/package.json"));
61 | }
62 | 


--------------------------------------------------------------------------------
/tests/smoke/package_migration.rs:
--------------------------------------------------------------------------------
 1 | use crate::support::temp_project::temp_project;
 2 | 
 3 | use hamcrest2::assert_that;
 4 | use hamcrest2::prelude::*;
 5 | use test_support::matchers::execs;
 6 | 
 7 | const LEGACY_PACKAGE_CONFIG: &str = r#"{
 8 |   "name": "cowsay",
 9 |   "version": "1.1.7",
10 |   "platform": {
11 |     "node": {
12 |       "runtime": "14.18.2",
13 |       "npm": null
14 |     },
15 |     "yarn": null
16 |   },
17 |   "bins": [
18 |     "cowsay",
19 |     "cowthink"
20 |   ]
21 | }"#;
22 | 
23 | const LEGACY_BIN_CONFIG: &str = r#"{
24 |   "name": "cowsay",
25 |   "package": "cowsay",
26 |   "version": "1.1.7",
27 |   "path": "./cli.js",
28 |   "platform": {
29 |     "node": {
30 |       "runtime": "14.18.2",
31 |       "npm": null
32 |     },
33 |     "yarn": null
34 |   },
35 |   "loader": {
36 |     "command": "node",
37 |     "args": []
38 |   }
39 | }"#;
40 | 
41 | const COWSAY_HELLO: &str = r#" _______
42 | < hello >
43 |  -------
44 |         \   ^__^
45 |          \  (oo)\_______
46 |             (__)\       )\/\
47 |                 ||----w |
48 |                 ||     ||"#;
49 | 
50 | #[test]
51 | fn legacy_package_upgrade() {
52 |     let p = temp_project()
53 |         .volta_home_file("tools/user/packages/cowsay.json", LEGACY_PACKAGE_CONFIG)
54 |         .volta_home_file("tools/user/bins/cowsay.json", LEGACY_BIN_CONFIG)
55 |         .volta_home_file(
56 |             "tools/image/packages/cowsay/1.3.1/README.md",
57 |             "Mock of installed package",
58 |         )
59 |         .volta_home_file("layout.v2", "")
60 |         .build();
61 | 
62 |     assert_that!(p.volta("--version"), execs().with_status(0));
63 | 
64 |     assert!(p.package_is_installed("cowsay"));
65 | 
66 |     assert_that!(
67 |         p.exec_shim("cowsay", "hello"),
68 |         execs().with_status(0).with_stdout_contains(COWSAY_HELLO)
69 |     );
70 | }
71 | 


--------------------------------------------------------------------------------
/tests/smoke/support/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod temp_project;
2 | 


--------------------------------------------------------------------------------
/tests/smoke/volta_fetch.rs:
--------------------------------------------------------------------------------
 1 | use crate::support::temp_project::temp_project;
 2 | 
 3 | use hamcrest2::assert_that;
 4 | use hamcrest2::prelude::*;
 5 | use test_support::matchers::execs;
 6 | 
 7 | #[test]
 8 | fn fetch_node() {
 9 |     let p = temp_project().build();
10 | 
11 |     assert_that!(p.volta("fetch node@14.17.6"), execs().with_status(0));
12 |     assert!(p.node_version_is_fetched("14.17.6"));
13 |     assert!(p.node_version_is_unpacked("14.17.6"));
14 | }
15 | 
16 | #[test]
17 | fn fetch_yarn_1() {
18 |     let p = temp_project().build();
19 | 
20 |     assert_that!(p.volta("fetch yarn@1.22.1"), execs().with_status(0));
21 |     assert!(p.yarn_version_is_fetched("1.22.1"));
22 |     assert!(p.yarn_version_is_unpacked("1.22.1"));
23 | }
24 | 
25 | #[test]
26 | fn fetch_yarn_3() {
27 |     let p = temp_project().build();
28 | 
29 |     assert_that!(p.volta("fetch yarn@3.2.0"), execs().with_status(0));
30 |     assert!(p.yarn_version_is_fetched("3.2.0"));
31 |     assert!(p.yarn_version_is_unpacked("3.2.0"));
32 | }
33 | 
34 | #[test]
35 | fn fetch_npm() {
36 |     let p = temp_project().build();
37 | 
38 |     assert_that!(p.volta("fetch npm@8.3.1"), execs().with_status(0));
39 |     assert!(p.npm_version_is_fetched("8.3.1"));
40 |     assert!(p.npm_version_is_unpacked("8.3.1"));
41 | }
42 | 


--------------------------------------------------------------------------------
/tests/smoke/volta_run.rs:
--------------------------------------------------------------------------------
 1 | use crate::support::temp_project::temp_project;
 2 | 
 3 | use hamcrest2::assert_that;
 4 | use hamcrest2::prelude::*;
 5 | use test_support::matchers::execs;
 6 | 
 7 | // Note: Node 14.11.0 is bundled with npm 6.14.8
 8 | const PACKAGE_JSON: &str = r#"{
 9 |     "name": "test-package",
10 |     "volta": {
11 |         "node": "14.11.0",
12 |         "npm": "6.14.15",
13 |         "yarn": "1.22.10"
14 |     }
15 | }"#;
16 | 
17 | #[test]
18 | fn run_node() {
19 |     let p = temp_project().build();
20 | 
21 |     assert_that!(
22 |         p.volta("run --node 14.16.0 node --version"),
23 |         execs().with_status(0).with_stdout_contains("v14.16.0")
24 |     );
25 | }
26 | 
27 | #[test]
28 | fn run_npm() {
29 |     let p = temp_project().build();
30 | 
31 |     assert_that!(
32 |         p.volta("run --node 14.14.0 --npm 6.14.16 npm --version"),
33 |         execs().with_status(0).with_stdout_contains("6.14.16")
34 |     )
35 | }
36 | 
37 | #[test]
38 | fn run_yarn_1() {
39 |     let p = temp_project().build();
40 | 
41 |     assert_that!(
42 |         p.volta("run --node 14.16.1 --yarn 1.22.0 yarn --version"),
43 |         execs().with_status(0).with_stdout_contains("1.22.0")
44 |     );
45 | }
46 | 
47 | #[test]
48 | fn run_yarn_3() {
49 |     let p = temp_project().build();
50 | 
51 |     assert_that!(
52 |         p.volta("run --node 16.14.1 --yarn 3.1.1 yarn --version"),
53 |         execs().with_status(0).with_stdout_contains("3.1.1")
54 |     );
55 | }
56 | 
57 | #[test]
58 | fn inherits_project_platform() {
59 |     let p = temp_project().package_json(PACKAGE_JSON).build();
60 | 
61 |     assert_that!(
62 |         p.volta("run --yarn 1.21.0 yarn --version"),
63 |         execs().with_status(0).with_stdout_contains("1.21.0")
64 |     );
65 | }
66 | 
67 | #[test]
68 | fn run_environment() {
69 |     let p = temp_project().build();
70 | 
71 |     assert_that!(
72 |         p.volta("run --node 14.15.3 --env VOLTA_SMOKE_1234=hello node -e console.log(process.env.VOLTA_SMOKE_1234)"),
73 |         execs().with_status(0).with_stdout_contains("hello")
74 |     );
75 | }
76 | 


--------------------------------------------------------------------------------
/volta.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/volta-cli/volta/a7384fa4fc7a0eca961032da4d962d94218b5868/volta.png


--------------------------------------------------------------------------------
/wix/License.rtf:
--------------------------------------------------------------------------------
 1 | {\rtf1\ansi\ansicpg1252\deff0\nouicompat\deflang1033{\fonttbl{\f0\fnil\fcharset0 Calibri;}}
 2 | {\*\generator Riched20 10.0.17134}\viewkind4\uc1 
 3 | \pard\sl240\slmult1\f0\fs24\lang9 BSD 2-CLAUSE LICENSE\par
 4 | \par
 5 | Copyright (c) 2017, The Volta Contributors.\par
 6 | All rights reserved.\par
 7 | \par
 8 | This product includes:\par
 9 | \par
10 | Contributions from LinkedIn Corporation\par
11 | Copyright (c) 2017, LinkedIn Corporation.\par
12 | \par
13 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:\par
14 | \par
15 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.\par
16 | \par
17 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.\par
18 | \par
19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\par
20 | }
21 | �


--------------------------------------------------------------------------------
/wix/shim.cmd:
--------------------------------------------------------------------------------
1 | @echo off
2 | "%~dpn0.exe" %*
3 | 


--------------------------------------------------------------------------------
/wix/volta.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/volta-cli/volta/a7384fa4fc7a0eca961032da4d962d94218b5868/wix/volta.ico


--------------------------------------------------------------------------------