├── .all-contributorsrc ├── .editorconfig ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── bug_types.yml │ └── feature_request.yml ├── pull_request_template.md └── workflows │ └── workflow.yml ├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── ava.config.js ├── eslint.config.js ├── gulpfile.js ├── package-lock.json ├── package.json ├── prettier.config.js ├── src ├── copy │ ├── content.js │ ├── list.js │ ├── main.js │ ├── main.test.js │ ├── output.js │ ├── path.js │ └── write.js ├── fixtures │ ├── 10.16.0 │ │ ├── node_modules │ │ │ └── printversion │ │ │ │ └── main.js │ │ ├── npm │ │ ├── npm.cmd │ │ ├── npx │ │ ├── npx.cmd │ │ ├── printversion │ │ └── printversion.cmd │ ├── 13.1.0 │ │ ├── node_modules │ │ │ └── printversion │ │ │ │ └── main.js │ │ ├── npm │ │ ├── npm.cmd │ │ ├── npx │ │ ├── npx.cmd │ │ ├── printversion │ │ ├── printversion.cmd │ │ └── printversion.ps1 │ ├── 18.18.0 │ │ ├── node_modules │ │ │ └── printversion │ │ │ │ └── main.js │ │ ├── npm │ │ ├── npm.cmd │ │ ├── npm.ps1 │ │ ├── npx │ │ ├── npx.cmd │ │ ├── npx.ps1 │ │ ├── printversion │ │ ├── printversion.cmd │ │ └── printversion.ps1 │ ├── 22.1.0 │ │ ├── node_modules │ │ │ └── printversion │ │ │ │ └── main.js │ │ ├── npm │ │ ├── npm.cmd │ │ ├── npm.ps1 │ │ ├── npx │ │ ├── npx.cmd │ │ ├── npx.ps1 │ │ ├── printversion │ │ ├── printversion.cmd │ │ └── printversion.ps1 │ ├── not_node │ │ ├── not_node │ │ └── not_node.cmd │ ├── not_npm │ │ └── not_npm.cmd │ ├── nvmrc │ │ └── .nvmrc │ ├── package │ │ ├── bin.test.js │ │ └── package.json │ ├── package_scripts │ │ └── package.json │ ├── sibling_dir │ │ ├── sibling_dir.cmd │ │ └── sibling_dir │ │ │ └── empty │ └── unix │ │ ├── node_modules │ │ └── printversion │ │ │ └── main.js │ │ └── printversion ├── helpers │ ├── copy.test.js │ ├── copy_fork.test.js │ ├── deep.test.js │ ├── fork.test.js │ └── versions.test.js ├── main.d.ts ├── main.js ├── main.test-d.ts ├── main.test.js ├── options.js ├── options.test.js └── validate.js └── tsconfig.json /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "nvexeca", 3 | "projectOwner": "ehmicky", 4 | "repoType": "github", 5 | "repoHost": "https://github.com", 6 | "files": [ 7 | "README.md" 8 | ], 9 | "imageSize": 100, 10 | "commit": true, 11 | "linkToUsage": false, 12 | "contributors": [ 13 | { 14 | "login": "ehmicky", 15 | "name": "ehmicky", 16 | "avatar_url": "https://avatars2.githubusercontent.com/u/8136211?v=4", 17 | "profile": "https://fosstodon.org/@ehmicky", 18 | "contributions": [ 19 | "code", 20 | "design", 21 | "ideas", 22 | "doc" 23 | ] 24 | }, 25 | { 26 | "login": "nicolas-goudry", 27 | "name": "Nicolas Goudry", 28 | "avatar_url": "https://avatars.githubusercontent.com/u/8753998?v=4", 29 | "profile": "https://github.com/nicolas-goudry", 30 | "contributions": [ 31 | "doc" 32 | ] 33 | }, 34 | { 35 | "login": "papb", 36 | "name": "Pedro Augusto de Paula Barbosa", 37 | "avatar_url": "https://avatars.githubusercontent.com/u/20914054?v=4", 38 | "profile": "https://github.com/papb", 39 | "contributions": [ 40 | "question" 41 | ] 42 | } 43 | ], 44 | "contributorsPerLine": 7, 45 | "skipCi": true, 46 | "commitConvention": "none" 47 | } 48 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | max_line_length = 80 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | description: Report a bug 3 | title: Please replace with a clear and descriptive title 4 | labels: [bug] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: Thanks for reporting this bug! 9 | - type: checkboxes 10 | attributes: 11 | label: Guidelines 12 | options: 13 | - label: 14 | Please search other issues to make sure this bug has not already 15 | been reported. 16 | required: true 17 | - label: 18 | If this is related to a typo or the documentation being unclear, 19 | please click on the relevant page's `Edit` button (pencil icon) and 20 | suggest a correction instead. 21 | required: true 22 | - type: textarea 23 | attributes: 24 | label: Describe the bug 25 | placeholder: A clear and concise description of what the bug is. 26 | validations: 27 | required: true 28 | - type: textarea 29 | attributes: 30 | label: Steps to reproduce 31 | placeholder: | 32 | Step-by-step instructions on how to reproduce the behavior. 33 | Example: 34 | 1. Type the following command: [...] 35 | 2. etc. 36 | validations: 37 | required: true 38 | - type: textarea 39 | attributes: 40 | label: Configuration 41 | placeholder: Command line options and/or configuration file, if any. 42 | validations: 43 | required: true 44 | - type: textarea 45 | attributes: 46 | label: Environment 47 | description: | 48 | Enter the following command in a terminal and copy/paste its output: 49 | ```bash 50 | npx envinfo --system --binaries --browsers --npmPackages nvexeca 51 | ``` 52 | validations: 53 | required: true 54 | - type: checkboxes 55 | attributes: 56 | label: Pull request (optional) 57 | description: 58 | Pull requests are welcome! If you would like to help us fix this bug, 59 | please check our [contributions 60 | guidelines](../blob/main/CONTRIBUTING.md). 61 | options: 62 | - label: I can submit a pull request. 63 | required: false 64 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_types.yml: -------------------------------------------------------------------------------- 1 | name: Bug report (TypeScript types) 2 | description: Report a bug about TypeScript types 3 | title: Please replace with a clear and descriptive title 4 | labels: [bug] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: Thanks for reporting this bug! 9 | - type: checkboxes 10 | attributes: 11 | label: Guidelines 12 | options: 13 | - label: 14 | Please search other issues to make sure this bug has not already 15 | been reported. 16 | required: true 17 | - type: textarea 18 | attributes: 19 | label: Describe the bug 20 | placeholder: A clear and concise description of what the bug is. 21 | validations: 22 | required: true 23 | - type: textarea 24 | attributes: 25 | label: Steps to reproduce 26 | description: | 27 | Please reproduce the bug using the [TypeScript playground](https://www.typescriptlang.org/play) or [Bug workbench](https://www.typescriptlang.org/dev/bug-workbench), then paste the URL here. 28 | validations: 29 | required: true 30 | - type: textarea 31 | attributes: 32 | label: Environment 33 | description: | 34 | Enter the following command in a terminal and copy/paste its output: 35 | ```bash 36 | npx envinfo --system --binaries --browsers --npmPackages nvexeca,typescript --npmGlobalPackages typescript 37 | ``` 38 | validations: 39 | required: true 40 | - type: checkboxes 41 | attributes: 42 | label: Pull request (optional) 43 | description: 44 | Pull requests are welcome! If you would like to help us fix this bug, 45 | please check our [contributions 46 | guidelines](../blob/main/CONTRIBUTING.md). 47 | options: 48 | - label: I can submit a pull request. 49 | required: false 50 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature request 2 | description: Suggest an idea for this project 3 | title: Please replace with a clear and descriptive title 4 | labels: [enhancement] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: Thanks for suggesting a new feature! 9 | - type: checkboxes 10 | attributes: 11 | label: Guidelines 12 | options: 13 | - label: 14 | Please search other issues to make sure this feature has not already 15 | been requested. 16 | required: true 17 | - type: textarea 18 | attributes: 19 | label: Which problem is this feature request solving? 20 | placeholder: I'm always frustrated when [...] 21 | validations: 22 | required: true 23 | - type: textarea 24 | attributes: 25 | label: Describe the solution you'd like 26 | placeholder: This could be fixed by [...] 27 | validations: 28 | required: true 29 | - type: checkboxes 30 | attributes: 31 | label: Pull request (optional) 32 | description: 33 | Pull requests are welcome! If you would like to help us fix this bug, 34 | please check our [contributions 35 | guidelines](../blob/main/CONTRIBUTING.md). 36 | options: 37 | - label: I can submit a pull request. 38 | required: false 39 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 🎉 Thanks for sending this pull request! 🎉 2 | 3 | Please make sure the title is clear and descriptive. 4 | 5 | If you are fixing a typo or documentation, please skip these instructions. 6 | 7 | Otherwise please fill in the sections below. 8 | 9 | **Which problem is this pull request solving?** 10 | 11 | Example: I'm always frustrated when [...] 12 | 13 | **List other issues or pull requests related to this problem** 14 | 15 | Example: This fixes #5012 16 | 17 | **Describe the solution you've chosen** 18 | 19 | Example: I've fixed this by [...] 20 | 21 | **Describe alternatives you've considered** 22 | 23 | Example: Another solution would be [...] 24 | 25 | **Checklist** 26 | 27 | Please add a `x` inside each checkbox: 28 | 29 | - [ ] I have read the [contribution guidelines](../blob/main/CONTRIBUTING.md). 30 | - [ ] I have added tests (we are enforcing 100% test coverage). 31 | - [ ] I have added documentation in the `README.md`, the `docs` directory (if 32 | any) 33 | - [ ] The status checks are successful (continuous integration). Those can be 34 | seen below. 35 | -------------------------------------------------------------------------------- /.github/workflows/workflow.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: [push, pull_request] 3 | jobs: 4 | combinations: 5 | uses: ehmicky/dev-tasks/.github/workflows/build.yml@main 6 | secrets: inherit 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.swp 3 | npm-debug.log 4 | node_modules 5 | /core 6 | .eslintcache 7 | .lycheecache 8 | .npmrc 9 | .yarn-error.log 10 | !.github/ 11 | /coverage 12 | /build 13 | !/src/fixtures/**/node_modules 14 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 11.0.3 2 | 3 | ## Documentation 4 | 5 | - Improve documentation in `README.md` 6 | 7 | # 11.0.2 8 | 9 | ## Security fix 10 | 11 | - Fix vulnerability in `tar-fs` dependency by upgrading it 12 | 13 | # 11.0.1 14 | 15 | ## Bug fixes 16 | 17 | - Fix `EMFILE` errors thrown on Windows when `nvexeca()` is called multiple 18 | times at once. 19 | 20 | # 11.0.0 21 | 22 | ## Breaking changes 23 | 24 | - Upgrade [Execa](https://github.com/sindresorhus/execa) to 25 | [`9.0.0`](https://github.com/sindresorhus/execa/releases/tag/v9.0.0) 26 | 27 | # 10.0.0 28 | 29 | ## Breaking changes 30 | 31 | - Minimal supported Node.js version is now `18.18.0` 32 | 33 | # 9.2.1 34 | 35 | ## Dependencies 36 | 37 | - Upgrade [Execa](https://github.com/sindresorhus/execa) to 38 | [`8.0.0`](https://github.com/sindresorhus/execa/releases/tag/v8.0.0) 39 | 40 | # 9.2.0 41 | 42 | ## Features 43 | 44 | - The Node.js version can now be specified as a file path to a 45 | [`.nvmrc`](https://github.com/nvm-sh/nvm#nvmrc), 46 | [`package.json` (`engines.node` field)](https://docs.npmjs.com/files/package.json#engines) 47 | or 48 | [similar files](https://github.com/ehmicky/preferred-node-version/blob/main/README.md). 49 | 50 | ```js 51 | const { childProcess, version } = await nvexeca('/path/to/.nvmrc', 'node', [ 52 | '--version', 53 | ]) 54 | ``` 55 | 56 | # 9.1.1 57 | 58 | ## Bug fixes 59 | 60 | - Ensure the [`signal` option](https://github.com/sindresorhus/execa/#signal-1) 61 | also cancels the command 62 | 63 | # 9.1.0 64 | 65 | ## Features 66 | 67 | - Make the [`signal` option](https://github.com/sindresorhus/execa/#signal-1) 68 | also cancel downloading the Node.js binary 69 | 70 | # 9.0.0 71 | 72 | ## Breaking changes 73 | 74 | - Minimal supported Node.js version is now `16.17.0` 75 | 76 | # 8.5.0 77 | 78 | ## Features 79 | 80 | - Upgrade Execa 81 | 82 | # 8.4.0 83 | 84 | ## Features 85 | 86 | - Improve tree-shaking support 87 | 88 | # 8.3.0 89 | 90 | ## Features 91 | 92 | - Reduce npm package size by 47% 93 | 94 | # 8.2.0 95 | 96 | ## Features 97 | 98 | - Reduce npm package size 99 | 100 | # 8.1.0 101 | 102 | ## Features 103 | 104 | - Add TypeScript types 105 | 106 | # 8.0.0 107 | 108 | ## Breaking changes 109 | 110 | - Minimal supported Node.js version is now `14.18.0` 111 | 112 | ## Features 113 | 114 | - The [`cwd` option](https://github.com/ehmicky/nvexeca#cwd) can now be a 115 | `file:` URL 116 | 117 | # 7.0.0 118 | 119 | ## Breaking changes 120 | 121 | - Minimal supported Node.js version is now `12.20.0` 122 | - This package is now an ES module. It can only be loaded with an `import` or 123 | `import()` statement, not `require()`. See 124 | [this post for more information](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c). 125 | 126 | ## Bug fixes 127 | 128 | - Fix support for the latest version of `npm` 129 | 130 | # 6.0.1 131 | 132 | ## Bug fixes 133 | 134 | - Fix crash when downloading several Node.js binaries in parallel 135 | 136 | # 6.0.0 137 | 138 | ## Breaking changes 139 | 140 | - Rename the [alias `here`](/README.md#nvexecaversionrange-command-args-options) 141 | to `local` 142 | 143 | ## Features 144 | 145 | - Add the [alias `global`](/README.md#nvexecaversionrange-command-args-options) 146 | to target the global Node version, regardless of the current directory 147 | 148 | # 5.0.0 149 | 150 | ## Breaking changes 151 | 152 | - Rename the [alias `now`](/README.md#nvexecaversionrange-command-args-options) 153 | to `here` 154 | 155 | # 4.0.0 156 | 157 | ## Breaking changes 158 | 159 | - Aliases `c` and `current` renamed to `now` 160 | - The [alias `now`](/README.md#nvexecaversionrange-command-args-options) now 161 | takes into account `package.json` `engines.node` field and 162 | [additional files](https://github.com/ehmicky/preferred-node-version/blob/main/README.md) 163 | used by other Node.js version managers. 164 | - Alias `l` removed: please use `latest` instead 165 | 166 | ## Features 167 | 168 | - Added [alias `lts`](/README.md#nvexecaversionrange-command-args-options) to 169 | target the latest LTS version 170 | 171 | # 3.0.1 172 | 173 | ## Bug fixes 174 | 175 | - Fix aliases shortcuts `l` and `c` 176 | 177 | # 3.0.0 178 | 179 | ## Breaking changes 180 | 181 | - Rename `*` alias to 182 | [`latest` or `l`](/README.md#nvexecaversionrange-command-args-options) 183 | - Rename `.` alias to 184 | [`current` or `c`](/README.md#nvexecaversionrange-command-args-options) 185 | - Remove `_` alias 186 | 187 | # 2.3.0 188 | 189 | ## Features 190 | 191 | - Add [`fetch` option](/README.md#fetch) to control caching 192 | 193 | ## Bug fixes 194 | 195 | - Checksum checks were not working when the `mirror` option was used 196 | 197 | # 2.2.0 198 | 199 | ## Features 200 | 201 | - Can use the `_` alias to refer to the 202 | [current process's Node.js version](/README.md#nvexecaversionrange-command-args-options) 203 | - Can use the `.` alias to refer to the 204 | [current project's Node.js version](/README.md#nvexecaversionrange-command-args-options) 205 | using its `.nvmrc`, `.node-version` or `.naverc`. The current directory can be 206 | changed using the [`cwd` option](/README.md#cwd). 207 | 208 | # 2.1.3 209 | 210 | ## Bug fixes 211 | 212 | - Fix crash when using multiple drives on Windows 213 | 214 | # 2.1.2 215 | 216 | ## Bug fixes 217 | 218 | - Fix terminal color changing on Windows 219 | 220 | # 2.1.1 221 | 222 | ## Bug fixes 223 | 224 | - Fix 225 | [`arch` option](https://github.com/ehmicky/nvexeca/blob/main/README.md#arch) 226 | 227 | # 2.1.0 228 | 229 | ## Features 230 | 231 | - Add 232 | [`arch` option](https://github.com/ehmicky/nvexeca/blob/main/README.md#arch) 233 | to specify the CPU architecture. 234 | 235 | # 2.0.0 236 | 237 | ## Breaking changes 238 | 239 | - Minimal supported Node.js version is now `10.17.0` 240 | 241 | # 1.7.0 242 | 243 | ## Features 244 | 245 | - Node.js binary download is now 50% faster on Windows 246 | 247 | ## Bug fixes 248 | 249 | - Fix crash when Node.js binary URL is invalid 250 | 251 | # 1.6.0 252 | 253 | ## Features 254 | 255 | - Node.js binary download is now twice faster on Windows 256 | 257 | ## Bug fixes 258 | 259 | - Fix ARM, PowerPC, S390 support 260 | 261 | # 1.5.5 262 | 263 | ## Bug fixes 264 | 265 | - Fix running `npm` or `npx` binaries on Windows 266 | 267 | # 1.5.4 268 | 269 | ## Bug fixes 270 | 271 | - Fix global binaries 272 | [not working on Windows](https://github.com/ehmicky/nve/issues/14) 273 | 274 | # 1.5.3 275 | 276 | ## Bug fixes 277 | 278 | - Fix executing binaries by specifying their file paths on Windows 279 | 280 | # 1.5.2 281 | 282 | ## Bug fixes 283 | 284 | - Fix executing `yarn`. 285 | 286 | # 1.5.1 287 | 288 | ## Bug fixes 289 | 290 | - Executing `npm`, `yarn` and `pnpm` was not working properly, for example when 291 | doing global installs (`npm i -g ...`). 292 | 293 | # 1.5.0 294 | 295 | ## Features 296 | 297 | - Improve the internal directory structure used to cache the Node.js binary 298 | - Cleanup temporary files when Node.js download fails 299 | 300 | # 1.4.0 301 | 302 | ## Features 303 | 304 | - Improve the appearance of the progress bar 305 | 306 | # 1.3.0 307 | 308 | ## Features 309 | 310 | - Ensure Node.js binaries are not corrupted by checking their 311 | [checksums](https://github.com/nodejs/node#verifying-binaries) 312 | - Use cache when offline (no network connection) 313 | 314 | # 1.2.0 315 | 316 | ## Features 317 | 318 | - Make Node.js binary download twice faster on Linux and MacOS 319 | - Improve error messages 320 | 321 | # 1.1.2 322 | 323 | ## Dependencies 324 | 325 | - Reduce the number of dependencies 326 | 327 | # 1.1.1 328 | 329 | ## Dependencies 330 | 331 | - Reduce the number of dependencies 332 | 333 | # 1.1.0 334 | 335 | ## Features 336 | 337 | - Upgrade `get-node` to `5.5.0` 338 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | This text is available in 4 | [many other languages](https://www.contributor-covenant.org/translations). 5 | 6 | ## Our Pledge 7 | 8 | We as members, contributors, and leaders pledge to make participation in our 9 | community a harassment-free experience for everyone, regardless of age, body 10 | size, visible or invisible disability, ethnicity, sex characteristics, gender 11 | identity and expression, level of experience, education, socio-economic status, 12 | nationality, personal appearance, race, religion, or sexual identity and 13 | orientation. 14 | 15 | We pledge to act and interact in ways that contribute to an open, welcoming, 16 | diverse, inclusive, and healthy community. 17 | 18 | ## Our Standards 19 | 20 | Examples of behavior that contributes to a positive environment for our 21 | community include: 22 | 23 | - Demonstrating empathy and kindness toward other people 24 | - Being respectful of differing opinions, viewpoints, and experiences 25 | - Giving and gracefully accepting constructive feedback 26 | - Accepting responsibility and apologizing to those affected by our mistakes, 27 | and learning from the experience 28 | - Focusing on what is best not just for us as individuals, but for the overall 29 | community 30 | 31 | Examples of unacceptable behavior include: 32 | 33 | - The use of sexualized language or imagery, and sexual attention or advances of 34 | any kind 35 | - Trolling, insulting or derogatory comments, and personal or political attacks 36 | - Public or private harassment 37 | - Publishing others' private information, such as a physical or email address, 38 | without their explicit permission 39 | - Other conduct which could reasonably be considered inappropriate in a 40 | professional setting 41 | 42 | ## Enforcement Responsibilities 43 | 44 | Community leaders are responsible for clarifying and enforcing our standards of 45 | acceptable behavior and will take appropriate and fair corrective action in 46 | response to any behavior that they deem inappropriate, threatening, offensive, 47 | or harmful. 48 | 49 | Community leaders have the right and responsibility to remove, edit, or reject 50 | comments, commits, code, wiki edits, issues, and other contributions that are 51 | not aligned to this Code of Conduct, and will communicate reasons for moderation 52 | decisions when appropriate. 53 | 54 | ## Scope 55 | 56 | This Code of Conduct applies within all community spaces, and also applies when 57 | an individual is officially representing the community in public spaces. 58 | Examples of representing our community include using an official e-mail address, 59 | posting via an official social media account, or acting as an appointed 60 | representative at an online or offline event. 61 | 62 | ## Enforcement 63 | 64 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 65 | reported to the community leaders responsible for enforcement at 66 | ehmicky+report@gmail.com All complaints will be reviewed and investigated 67 | promptly and fairly. 68 | 69 | All community leaders are obligated to respect the privacy and security of the 70 | reporter of any incident. 71 | 72 | ## Enforcement Guidelines 73 | 74 | Community leaders will follow these Community Impact Guidelines in determining 75 | the consequences for any action they deem in violation of this Code of Conduct: 76 | 77 | ### 1. Correction 78 | 79 | **Community Impact**: Use of inappropriate language or other behavior deemed 80 | unprofessional or unwelcome in the community. 81 | 82 | **Consequence**: A private, written warning from community leaders, providing 83 | clarity around the nature of the violation and an explanation of why the 84 | behavior was inappropriate. A public apology may be requested. 85 | 86 | ### 2. Warning 87 | 88 | **Community Impact**: A violation through a single incident or series of 89 | actions. 90 | 91 | **Consequence**: A warning with consequences for continued behavior. No 92 | interaction with the people involved, including unsolicited interaction with 93 | those enforcing the Code of Conduct, for a specified period of time. This 94 | includes avoiding interactions in community spaces as well as external channels 95 | like social media. Violating these terms may lead to a temporary or permanent 96 | ban. 97 | 98 | ### 3. Temporary Ban 99 | 100 | **Community Impact**: A serious violation of community standards, including 101 | sustained inappropriate behavior. 102 | 103 | **Consequence**: A temporary ban from any sort of interaction or public 104 | communication with the community for a specified period of time. No public or 105 | private interaction with the people involved, including unsolicited interaction 106 | with those enforcing the Code of Conduct, is allowed during this period. 107 | Violating these terms may lead to a permanent ban. 108 | 109 | ### 4. Permanent Ban 110 | 111 | **Community Impact**: Demonstrating a pattern of violation of community 112 | standards, including sustained inappropriate behavior, harassment of an 113 | individual, or aggression toward or disparagement of classes of individuals. 114 | 115 | **Consequence**: A permanent ban from any sort of public interaction within the 116 | community. 117 | 118 | ## Attribution 119 | 120 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 121 | version 2.0, available at 122 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 123 | 124 | Community Impact Guidelines were inspired by 125 | [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). 126 | 127 | [homepage]: https://www.contributor-covenant.org 128 | 129 | For answers to common questions about this code of conduct, see the FAQ at 130 | https://www.contributor-covenant.org/faq. Translations are available at 131 | https://www.contributor-covenant.org/translations. 132 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributions 2 | 3 | 🎉 Thanks for considering contributing to this project! 🎉 4 | 5 | These guidelines will help you send a pull request. 6 | 7 | If you're submitting an issue instead, please skip this document. 8 | 9 | If your pull request is related to a typo or the documentation being unclear, 10 | please click on the relevant page's `Edit` button (pencil icon) and directly 11 | suggest a correction instead. 12 | 13 | This project was made with ❤️. The simplest way to give back is by starring and 14 | sharing it online. 15 | 16 | Everyone is welcome regardless of personal background. We enforce a 17 | [Code of conduct](CODE_OF_CONDUCT.md) in order to promote a positive and 18 | inclusive environment. 19 | 20 | # Development process 21 | 22 | First fork and clone the repository. If you're not sure how to do this, please 23 | watch 24 | [these videos](https://egghead.io/courses/how-to-contribute-to-an-open-source-project-on-github). 25 | 26 | Run: 27 | 28 | ```bash 29 | npm install 30 | ``` 31 | 32 | Make sure everything is correctly setup with: 33 | 34 | ```bash 35 | npm test 36 | ``` 37 | 38 | We use Gulp tasks to lint, test and build this project. Please check 39 | [dev-tasks](https://github.com/ehmicky/dev-tasks/blob/main/README.md) to learn 40 | how to use them. You don't need to know Gulp to use these tasks. 41 | 42 | # Requirements 43 | 44 | Our coding style is documented 45 | [here](https://github.com/ehmicky/eslint-config#coding-style). Linting and 46 | formatting should automatically handle it though. 47 | 48 | After submitting the pull request, please make sure the Continuous Integration 49 | checks are passing. 50 | 51 | We enforce 100% test coverage: each line of code must be tested. 52 | 53 | New options, methods, properties, configuration and behavior must be documented 54 | in all of these: 55 | 56 | - the `README.md` 57 | - the `docs` directory (if any) 58 | 59 | Please use the same style as the rest of the documentation and examples. 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2025 ehmicky 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | nvexeca logo 4 | 5 | 6 | [![Node](https://img.shields.io/badge/-Node.js-808080?logo=node.js&colorA=404040&logoColor=66cc33)](https://www.npmjs.com/package/nvexeca) 7 | [![TypeScript](https://img.shields.io/badge/-Typed-808080?logo=typescript&colorA=404040&logoColor=0096ff)](/src/main.d.ts) 8 | [![Codecov](https://img.shields.io/badge/-Tested%20100%25-808080?logo=codecov&colorA=404040)](https://codecov.io/gh/ehmicky/nvexeca) 9 | [![Mastodon](https://img.shields.io/badge/-Mastodon-808080.svg?logo=mastodon&colorA=404040&logoColor=9590F9)](https://fosstodon.org/@ehmicky) 10 | [![Medium](https://img.shields.io/badge/-Medium-808080.svg?logo=medium&colorA=404040)](https://medium.com/@ehmicky) 11 | 12 | nvm + execa = nvexeca. 13 | 14 | [Execa](https://github.com/sindresorhus/execa) improves 15 | [child processes](https://nodejs.org/api/child_process.html) execution with a 16 | promise interface, cross-platform support, local binaries, interleaved output, 17 | [and more](https://github.com/sindresorhus/execa#features). 18 | 19 | nvexeca is a thin wrapper around Execa to run any file or command using any 20 | Node.js version. 21 | 22 | Unlike [`nvm exec`](https://github.com/nvm-sh/nvm/blob/master/README.md#usage) 23 | it: 24 | 25 | - is run programmatically 26 | - does not need a separate installation step for each Node version 27 | - can run the major release's latest minor/patch version automatically 28 | - does not require Bash 29 | - is installed as a Node module 30 | - works on Windows. No need to run as Administrator. 31 | 32 | `nvexeca` executes a **single file or command**. It does not change the `node` 33 | nor `npm` global binaries. To run a specific Node.js version for an **entire 34 | project or shell session**, please use [`nvm`](https://github.com/nvm-sh/nvm), 35 | [`nvm-windows`](https://github.com/coreybutler/nvm-windows), 36 | [`n`](https://github.com/tj/n) or [`nvs`](https://github.com/jasongin/nvs) 37 | instead. 38 | 39 | # Example 40 | 41 | ```js 42 | import nvexeca from 'nvexeca' 43 | 44 | const { childProcess, versionRange, version } = await nvexeca('8', 'node', [ 45 | '--version', 46 | ]) 47 | console.log(`Node ${versionRange} (${version})`) // Node 8 (8.16.2) 48 | const { exitCode, stdout, stderr } = await childProcess 49 | console.log(`Exit code: ${exitCode}`) // 0 50 | console.log(stdout) // v8.16.2 51 | ``` 52 | 53 | # Install 54 | 55 | ```bash 56 | npm install nvexeca 57 | ``` 58 | 59 | `node >=18.18.0` must be installed. However the command run by `nvexeca` can use 60 | any Node version (providing it is compatible with it). 61 | 62 | This package is an ES module and must be loaded using 63 | [an `import` or `import()` statement](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c), 64 | not `require()`. If TypeScript is used, it must be configured to 65 | [output ES modules](https://www.typescriptlang.org/docs/handbook/esm-node.html), 66 | not CommonJS. 67 | 68 | To use this as a CLI instead, please check 69 | [`nve`](https://github.com/ehmicky/nve). 70 | 71 | # Usage 72 | 73 | ## nvexeca(versionRange, command, args?, options?) 74 | 75 | Executes `command ...args` with a specific Node.js `versionRange`. 76 | 77 | ### Arguments 78 | 79 | #### versionRange 80 | 81 | _Type_: `string` 82 | 83 | This can be: 84 | 85 | - any [version range](https://github.com/npm/node-semver) such as `12`, `12.6.0` 86 | or `<12` 87 | - `latest`: Latest available Node version 88 | - `lts`: Latest LTS Node version 89 | - `global`: Global Node version 90 | - Using the home directory [`.nvmrc`](https://github.com/nvm-sh/nvm#nvmrc) or 91 | [`package.json` (`engines.node` field)](https://docs.npmjs.com/files/package.json#engines) 92 | - [Some similar files](https://github.com/ehmicky/preferred-node-version/blob/main/README.md) 93 | used by other Node.js version managers are also searched for 94 | - If nothing is found, defaults to the current process's Node version 95 | - `local`: Current directory's Node version 96 | - Using the current directory or parent directories 97 | [`.nvmrc`](https://github.com/nvm-sh/nvm#nvmrc), 98 | [`package.json` (`engines.node` field)](https://docs.npmjs.com/files/package.json#engines) 99 | or 100 | [similar files](https://github.com/ehmicky/preferred-node-version/blob/main/README.md) 101 | - Defaults to the `global` version 102 | - a file path towards a [`.nvmrc`](https://github.com/nvm-sh/nvm#nvmrc), 103 | [`package.json` (`engines.node` field)](https://docs.npmjs.com/files/package.json#engines) 104 | or 105 | [similar files](https://github.com/ehmicky/preferred-node-version/blob/main/README.md) 106 | 107 | #### command 108 | 109 | _Type_: `string` 110 | 111 | File or command to execute. Both global and local binaries can be executed. 112 | 113 | Must be compatible with the specific Node `versionRange`. For example `npm` is 114 | [only compatible with Node `>=6`](https://github.com/npm/cli#important). 115 | 116 | #### args 117 | 118 | _Type_: `string[]?` 119 | 120 | Arguments to pass to the [`command`](#command). 121 | 122 | ### Options 123 | 124 | _Type_: `object?` 125 | 126 | All Execa options are available. Please refer to Execa for the list of 127 | [possible options](https://github.com/sindresorhus/execa/blob/main/docs/api.md#options). 128 | 129 | The 130 | [`preferLocal` option](https://github.com/sindresorhus/execa/blob/main/docs/api.md#optionspreferlocal) 131 | is always `true`. 132 | 133 | The following options are also available. 134 | 135 | #### dry 136 | 137 | _Type_: `boolean`\ 138 | _Default_: `false` 139 | 140 | Do not execute the command. This can be used to cache the initial Node.js binary 141 | download. 142 | 143 | #### progress 144 | 145 | _Type_: `boolean`\ 146 | _Default_: `false` 147 | 148 | Whether to show a progress bar when the Node binary is downloading. 149 | 150 | #### mirror 151 | 152 | _Type_: `string`\ 153 | _Default_: `https://nodejs.org/dist` 154 | 155 | Base URL to retrieve Node binaries. Can be overridden (for example 156 | `https://npmmirror.com/mirrors/node`). 157 | 158 | The following environment variables can also be used: `NODE_MIRROR`, 159 | `NVM_NODEJS_ORG_MIRROR`, `N_NODE_MIRROR` or `NODIST_NODE_MIRROR`. 160 | 161 | #### fetch 162 | 163 | _Type_: `boolean`\ 164 | _Default_: `undefined` 165 | 166 | The list of available Node.js versions is cached for one hour by default. If the 167 | `fetch` option is: 168 | 169 | - `true`: the cache will not be used 170 | - `false`: the cache will be used even if it's older than one hour 171 | 172 | #### arch 173 | 174 | _Type_: `string`\ 175 | _Default_: [`process.arch`](https://nodejs.org/api/process.html#process_process_arch) 176 | 177 | Node.js binary's CPU architecture. This is useful for example when you're on x64 178 | but would like to run Node.js x32. 179 | 180 | All the values from 181 | [`process.arch`](https://nodejs.org/api/process.html#process_process_arch) are 182 | allowed except `mips` and `mipsel`. 183 | 184 | #### cwd 185 | 186 | _Type_: `string | URL`\ 187 | _Default_: `process.cwd()` 188 | 189 | Current working directory of the child process. 190 | 191 | When using the [`local` alias](#nvexecaversionrange-command-args-options), this 192 | also starts looking for a Node.js version file from this directory. 193 | 194 | ### Return value 195 | 196 | _Type_: `Promise` 197 | 198 | `Promise` resolved after the Node.js version has been cached locally (if it has 199 | not been cached yet). 200 | 201 | If you want to wait for the `command` to complete as well, you should `await` 202 | the returned `childProcess`. 203 | 204 | ```js 205 | const { childProcess } = await nvexeca('8', 'node', ['--version']) 206 | const { exitCode, stdout, stderr } = await childProcess 207 | ``` 208 | 209 | #### childProcess 210 | 211 | _Type_: 212 | [`ResultPromise?`](https://github.com/sindresorhus/execa/blob/main/docs/api.md#return-value) 213 | 214 | [`childProcess` instance](https://nodejs.org/api/child_process.html#child_process_class_childprocess). 215 | It is also a `Promise` resolving or rejecting with a 216 | [`Result`](https://github.com/sindresorhus/execa/blob/main/docs/api.md#result). 217 | The `Promise` should be awaited if you want to wait for the process to complete. 218 | 219 | This is `undefined` when the [`dry`](#dry) option is `true`. 220 | 221 | #### versionRange 222 | 223 | _Type_: `string` 224 | 225 | Node.js version passed as input, such as `"v10"`. 226 | 227 | #### version 228 | 229 | _Type_: `string` 230 | 231 | Normalized Node.js version. For example if `"v10"` was passed as input, 232 | `version` will be `"10.17.0"`. 233 | 234 | #### command 235 | 236 | _Type_: `string` 237 | 238 | File or command that was executed. 239 | 240 | #### args 241 | 242 | _Type_: `string[]` 243 | 244 | Arguments that were passed to the `command`. 245 | 246 | #### execaOptions 247 | 248 | _Type_: `object` 249 | 250 | [Options](https://github.com/sindresorhus/execa/blob/main/docs/api.md#options) 251 | that were passed to [Execa](https://github.com/sindresorhus/execa). 252 | 253 | ## Initial download 254 | 255 | The first time `nvexeca` is run with a new `VERSION`, the Node binary is 256 | downloaded under the hood. This initially takes few seconds. However subsequent 257 | runs are almost instantaneous. 258 | 259 | ## Native modules 260 | 261 | If your code is using native modules, `nvexeca` works providing: 262 | 263 | - they are built with [N-API](https://nodejs.org/api/n-api.html) 264 | - the target Node.js version is `>=8.12.0` (since N-API was not available or 265 | stable before that) 266 | 267 | Otherwise the following error message is shown: 268 | `Error: The module was compiled against a different Node.js version`. 269 | 270 | # See also 271 | 272 | - [`nve`](https://github.com/ehmicky/nve): `nvexeca` as a CLI 273 | - [`execa`](https://github.com/sindresorhus/execa): Process execution for humans 274 | - [`get-node`](https://github.com/ehmicky/get-node): Download Node.js 275 | - [`preferred-node-version`](https://github.com/ehmicky/preferred-node-version): 276 | Get the preferred Node.js version of a project or user 277 | - [`node-version-alias`](https://github.com/ehmicky/node-version-alias): Resolve 278 | Node.js version aliases like `latest`, `lts` or `erbium` 279 | - [`normalize-node-version`](https://github.com/ehmicky/normalize-node-version): 280 | Normalize and validate Node.js versions 281 | - [`all-node-versions`](https://github.com/ehmicky/all-node-versions): List all 282 | available Node.js versions 283 | - [`fetch-node-website`](https://github.com/ehmicky/fetch-node-website): Fetch 284 | releases on nodejs.org 285 | - [`global-cache-dir`](https://github.com/ehmicky/global-cache-dir): Get the 286 | global cache directory 287 | 288 | # Support 289 | 290 | For any question, _don't hesitate_ to [submit an issue on GitHub](../../issues). 291 | 292 | Everyone is welcome regardless of personal background. We enforce a 293 | [Code of conduct](CODE_OF_CONDUCT.md) in order to promote a positive and 294 | inclusive environment. 295 | 296 | # Contributing 297 | 298 | This project was made with ❤️. The simplest way to give back is by starring and 299 | sharing it online. 300 | 301 | If the documentation is unclear or has a typo, please click on the page's `Edit` 302 | button (pencil icon) and suggest a correction. 303 | 304 | If you would like to help us fix a bug or add a new feature, please check our 305 | [guidelines](CONTRIBUTING.md). Pull requests are welcome! 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 |

ehmicky

💻 🎨 🤔 📖

Nicolas Goudry

📖

Pedro Augusto de Paula Barbosa

💬
319 | 320 | 321 | 322 | 323 | 324 | -------------------------------------------------------------------------------- /ava.config.js: -------------------------------------------------------------------------------- 1 | import avaConfig from '@ehmicky/dev-tasks/ava.config.js' 2 | import isCI from 'is-ci' 3 | 4 | export default { 5 | ...avaConfig, 6 | // CI machines have lower limits for parallel network requests, and sometimes 7 | // fail, especially macOS on GitHub actions 8 | serial: isCI, 9 | workerThreads: false, 10 | } 11 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | export { default } from '@ehmicky/eslint-config' 2 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | export * from '@ehmicky/dev-tasks' 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nvexeca", 3 | "version": "11.0.3", 4 | "type": "module", 5 | "exports": { 6 | "types": "./build/src/main.d.ts", 7 | "default": "./build/src/main.js" 8 | }, 9 | "main": "./build/src/main.js", 10 | "types": "./build/src/main.d.ts", 11 | "files": [ 12 | "build/src/**/*.{js,json,d.ts}", 13 | "!build/src/**/*.test.js", 14 | "!build/src/{helpers,fixtures}" 15 | ], 16 | "sideEffects": false, 17 | "scripts": { 18 | "test": "gulp test" 19 | }, 20 | "description": "nvm + execa = nvexeca", 21 | "keywords": [ 22 | "nodejs", 23 | "node", 24 | "nvm", 25 | "npx", 26 | "versions", 27 | "versioning", 28 | "exec", 29 | "shell", 30 | "terminal", 31 | "command-line", 32 | "cli", 33 | "dependency-management", 34 | "es6", 35 | "javascript", 36 | "typescript", 37 | "library", 38 | "npmjs", 39 | "operating-system", 40 | "package-manager", 41 | "bash" 42 | ], 43 | "license": "Apache-2.0", 44 | "homepage": "https://www.github.com/ehmicky/nvexeca", 45 | "repository": { 46 | "type": "git", 47 | "url": "git+https://github.com/ehmicky/nvexeca.git" 48 | }, 49 | "bugs": { 50 | "url": "https://github.com/ehmicky/nvexeca/issues" 51 | }, 52 | "author": "ehmicky (https://github.com/ehmicky)", 53 | "directories": { 54 | "lib": "src" 55 | }, 56 | "dependencies": { 57 | "execa": "^9.5.2", 58 | "get-node": "^15.0.3", 59 | "global-cache-dir": "^6.0.1", 60 | "is-plain-obj": "^4.1.0", 61 | "npm-run-path": "^6.0.0", 62 | "p-map": "^7.0.3", 63 | "path-exists": "^5.0.0", 64 | "path-key": "^4.0.0", 65 | "path-type": "^6.0.0" 66 | }, 67 | "devDependencies": { 68 | "@ehmicky/dev-tasks": "^3.0.34", 69 | "@ehmicky/eslint-config": "^20.0.32", 70 | "@ehmicky/prettier-config": "^1.0.6", 71 | "@types/node": "^22.13.14", 72 | "is-ci": "^4.1.0", 73 | "semver": "^7.7.1", 74 | "test-each": "^7.0.1", 75 | "yarn": "^1.22.22" 76 | }, 77 | "engines": { 78 | "node": ">=18.18.0" 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | export { default } from '@ehmicky/prettier-config' 2 | -------------------------------------------------------------------------------- /src/copy/content.js: -------------------------------------------------------------------------------- 1 | import { readFile } from 'node:fs/promises' 2 | 3 | // We need to slightly modify the binaries so that their file paths take into 4 | // account the new location. Moving the binaries should make the 5 | // `if exists ./node.exe` always fail, so this does not need to be changed. 6 | // Note that global binaries use `cmd-shim` (https://github.com/npm/cmd-shim) to 7 | // produce the shim files. But `npm` and `npx` global binaries shim files are 8 | // slightly different (https://github.com/npm/cli/blob/latest/bin/npm) 9 | export const getContent = async ({ type, srcBinDir, filename }) => { 10 | const path = `${srcBinDir}/${filename}` 11 | const content = await readFile(path, 'utf8') 12 | const distContent = CONTENTS[type](srcBinDir, content) 13 | return distContent 14 | } 15 | 16 | const getCmdContent = (srcBinDir, content) => { 17 | const srcBinDirA = srcBinDir.replaceAll('/', '\\') 18 | return content.replaceAll(CMD_REGEXP, `${srcBinDirA}$2`) 19 | } 20 | 21 | // %%F|%~dp0 is used in npm|npx `cmd` binaries, %dp0% in other binaries 22 | const CMD_REGEXP = /(%%F|%~dp0|%dp0%)(\\node_modules)/gu 23 | 24 | // This also works with the Powershell file 25 | const getShellContent = (srcBinDir, content) => { 26 | const srcBinDirA = srcBinDir.replace('\\', '/') 27 | return content.replaceAll(SHELL_REGEXP, `${srcBinDirA}$1`) 28 | } 29 | 30 | // `CLI_BASEDIR|NPM_PREFIX` is used in npm|npx shell binaries 31 | // $basedir in other shell binaries and in old Powershell 32 | // "$nodedir and "$npmprefix in old Powershell 33 | // $PSScriptRoot in new Powershell 34 | const SHELL_REGEXP = 35 | /(\$basedir|\$CLI_BASEDIR|\$NPM_PREFIX|\$PSScriptRoot|"\$nodedir|"\$npmprefix)(\/node_modules)/gu 36 | 37 | const CONTENTS = { 38 | cmd: getCmdContent, 39 | bash: getShellContent, 40 | ps1: getShellContent, 41 | } 42 | -------------------------------------------------------------------------------- /src/copy/list.js: -------------------------------------------------------------------------------- 1 | import { readdir, readFile, stat } from 'node:fs/promises' 2 | import { delimiter, normalize } from 'node:path' 3 | 4 | import pMap from 'p-map' 5 | import { isDirectory } from 'path-type' 6 | 7 | import { getContent } from './content.js' 8 | import { isOutputDir } from './output.js' 9 | 10 | // Retrieve the list of binaries to copy. 11 | // Look inside each directory in `PATH` to find any npm global binary executed 12 | // with Node. 13 | export const listSrcPaths = async (pathValue) => { 14 | const srcBinDirs = [...new Set(pathValue.split(delimiter))] 15 | const srcPaths = await Promise.all(srcBinDirs.map(getSrcPaths)) 16 | return srcPaths.flat().filter(hasPriority) 17 | } 18 | 19 | const getSrcPaths = async (srcBinDir) => { 20 | const srcBinDirA = normalize(srcBinDir) 21 | 22 | if (isOutputDir(srcBinDirA)) { 23 | return [] 24 | } 25 | 26 | if (!(await isDirectory(srcBinDirA))) { 27 | return [] 28 | } 29 | 30 | const filenames = await readdir(srcBinDirA) 31 | const srcPaths = await pMap( 32 | filenames, 33 | (filename) => getSrcPath({ srcBinDir: srcBinDirA, filenames, filename }), 34 | { concurrency: FILE_CONCURRENCY }, 35 | ) 36 | return srcPaths.flat() 37 | } 38 | 39 | // Avoid EMFILE errors due to too many open files at once 40 | const FILE_CONCURRENCY = 100 41 | 42 | // We are looking for *.cmd files that have a sibling Bash file. Those are 43 | // most likely to be npm global binaries. 44 | // Each detected *.cmd file also return its sibling Bash and Powershell file. 45 | const getSrcPath = async ({ srcBinDir, filenames, filename }) => { 46 | if (!CMD_BINARY_REGEXP.test(filename)) { 47 | return [] 48 | } 49 | 50 | const bashFilename = filename.replace(CMD_BINARY_REGEXP, '') 51 | 52 | if (!(await isNodeBinary(srcBinDir, filenames, bashFilename))) { 53 | return [] 54 | } 55 | 56 | const ps1Filename = filename.replace(CMD_BINARY_REGEXP, '.ps1') 57 | 58 | const srcPaths = await readSrcPaths({ 59 | srcBinDir, 60 | bashFilename, 61 | filename, 62 | ps1Filename, 63 | filenames, 64 | }) 65 | return srcPaths 66 | } 67 | 68 | const CMD_BINARY_REGEXP = /\.cmd$/u 69 | 70 | // npm global binaries are not necessary executed with node. For example, a npm 71 | // package can have no shabang or use a different shabang than node. Those do 72 | // not need to be fixed, i.e. can be skipped. 73 | // We detect this by looking at the binary file's content looking for the `node` 74 | // word. 75 | const isNodeBinary = async (srcBinDir, filenames, bashFilename) => { 76 | if (!filenames.includes(bashFilename)) { 77 | return false 78 | } 79 | 80 | const bashPath = `${srcBinDir}/${bashFilename}` 81 | const bashStat = await stat(bashPath) 82 | 83 | if (!bashStat.isFile()) { 84 | return false 85 | } 86 | 87 | const bashContent = await readFile(bashPath, 'utf8') 88 | return NODE_DETECT_REGEXP.test(bashContent) 89 | } 90 | 91 | // This works with both normal shim files and `npm`/`npx` which use slightly 92 | // different shim files. We support `npm`/`npx` >= 8.15.0, `bin-links` >= 4.0.1, 93 | // `cmd-shim` >=6.0.0 since this is shipped with Node 18.18.0 (our minimally 94 | // supported Node version) 95 | const NODE_DETECT_REGEXP = /\[ -x "(\$basedir\/node|\$NODE_EXE)" \]/u 96 | 97 | const readSrcPaths = ({ 98 | srcBinDir, 99 | bashFilename, 100 | filename, 101 | ps1Filename, 102 | filenames, 103 | }) => 104 | Promise.all([ 105 | readSrcPath({ type: 'bash', srcBinDir, filename: bashFilename }), 106 | readSrcPath({ type: 'cmd', srcBinDir, filename }), 107 | // npm and npx do not have *.ps1 files 108 | ...(filenames.includes(ps1Filename) 109 | ? [readSrcPath({ type: 'ps1', srcBinDir, filename: ps1Filename })] 110 | : []), 111 | ]) 112 | 113 | // Find out the content of the copied file 114 | const readSrcPath = async ({ type, srcBinDir, filename }) => { 115 | const content = await getContent({ type, srcBinDir, filename }) 116 | return { filename, content } 117 | } 118 | 119 | // The same binary might be present several times in `PATH`. We only keep the 120 | // first one, to respect `PATH` priority order. 121 | const hasPriority = ({ filename }, index, srcPaths) => 122 | srcPaths.slice(0, index).every((srcPath) => srcPath.filename !== filename) 123 | -------------------------------------------------------------------------------- /src/copy/main.js: -------------------------------------------------------------------------------- 1 | import { platform } from 'node:process' 2 | 3 | import { listSrcPaths } from './list.js' 4 | import { getDistBinDir } from './output.js' 5 | import { addToPath, getPath } from './path.js' 6 | import { writeBinaries } from './write.js' 7 | 8 | // npm installs global binaries on Unix with symlinks. But on Windows, it 9 | // creates a small `*.cmd` file (https://github.com/npm/cmd-shim) that just 10 | // forwards to `{executable} ...` where executable is guessed from the shabang 11 | // (if any). 12 | // This is because Windows cannot execute shabang files otherwise. 13 | // It also does it with a Bash file and a `*.ps1` file (Powershell). 14 | // However the shim files prioritize the global binaries directory over the 15 | // `PATH` environment variable by looking for a sibling `node.exe`. But `nve` 16 | // needs to use the `PATH` environment variable. 17 | // We fix this by copying the shim file to a directory that we push in front of 18 | // the `PATH`. This removes that prioritization since there are no more 19 | // `node.exe`. 20 | // The fix is npm-specific. Yarn does not have that issue. Although it also 21 | // checks for a sibling `node.exe`, there is never such a file in practice. 22 | export const copyBinaries = async (execaOptions) => { 23 | if (platform !== 'win32') { 24 | return execaOptions 25 | } 26 | 27 | const { pathName, pathValue } = getPath(execaOptions) 28 | 29 | // No `PATH` environment variable. Very unlikely. 30 | if (pathValue === undefined) { 31 | return execaOptions 32 | } 33 | 34 | const srcPaths = await listSrcPaths(pathValue) 35 | 36 | if (srcPaths.length === 0) { 37 | return execaOptions 38 | } 39 | 40 | return applyCopy({ execaOptions, pathName, pathValue, srcPaths }) 41 | } 42 | 43 | const applyCopy = async ({ execaOptions, pathName, pathValue, srcPaths }) => { 44 | const distBinDir = await getDistBinDir(srcPaths) 45 | await writeBinaries(srcPaths, distBinDir) 46 | 47 | const execaOptionsA = addToPath({ 48 | execaOptions, 49 | pathName, 50 | pathValue, 51 | distBinDir, 52 | }) 53 | return execaOptionsA 54 | } 55 | -------------------------------------------------------------------------------- /src/copy/main.test.js: -------------------------------------------------------------------------------- 1 | import { join } from 'node:path' 2 | import { platform } from 'node:process' 3 | import { fileURLToPath } from 'node:url' 4 | 5 | import test from 'ava' 6 | import pathKey from 'path-key' 7 | import { each } from 'test-each' 8 | 9 | import { run, runPrint, runThrows } from '../helpers/copy.test.js' 10 | import { HELPER_VERSION } from '../helpers/versions.test.js' 11 | 12 | const FORK_FILE = fileURLToPath( 13 | new URL('../helpers/copy_fork.test.js', import.meta.url), 14 | ) 15 | const FIXTURES_DIR = fileURLToPath(new URL('../fixtures', import.meta.url)) 16 | const INVALID_DIR = fileURLToPath(new URL('../../invalid', import.meta.url)) 17 | 18 | const PATH = pathKey() 19 | 20 | if (platform !== 'win32') { 21 | test('Global binaries', async (t) => { 22 | await runPrint(t, [`${FIXTURES_DIR}/unix`]) 23 | }) 24 | } 25 | 26 | // We need to run tests serially because of some ETXTBUSY errors thrown 27 | // otherwise 28 | if (platform === 'win32') { 29 | each(['10.16.0', '13.1.0', '18.18.0', '22.1.0'], ({ title }, nodeVersion) => { 30 | const fixtureDir = join(FIXTURES_DIR, nodeVersion) 31 | 32 | test.serial(`Global binaries | ${title}`, async (t) => { 33 | await runPrint(t, [fixtureDir]) 34 | }) 35 | 36 | test.serial(`extendEnv: false | ${title}`, async (t) => { 37 | await runPrint(t, [fixtureDir], { extendEnv: false }) 38 | }) 39 | 40 | test.serial(`Non-existing directory in PATH | ${title}`, async (t) => { 41 | await runPrint(t, [fixtureDir, INVALID_DIR]) 42 | }) 43 | 44 | test.serial(`Recursively | ${title}`, async (t) => { 45 | await run({ 46 | t, 47 | pathParts: [fixtureDir], 48 | version: HELPER_VERSION, 49 | command: 'node', 50 | args: [FORK_FILE], 51 | }) 52 | }) 53 | 54 | test.serial(`Binary twice in PATH | ${title}`, async (t) => { 55 | await runPrint(t, [fixtureDir, fixtureDir]) 56 | }) 57 | 58 | test.serial(`*.cmd not a npm binary | ${title}`, async (t) => { 59 | await runPrint(t, [fixtureDir, join(FIXTURES_DIR, 'not_npm')]) 60 | }) 61 | 62 | test.serial(`Not node executable | ${title}`, async (t) => { 63 | await runPrint(t, [fixtureDir, join(FIXTURES_DIR, 'not_node')]) 64 | }) 65 | 66 | test.serial(`Sibling is a directory | ${title}`, async (t) => { 67 | await runPrint(t, [fixtureDir, join(FIXTURES_DIR, 'sibling_dir')]) 68 | }) 69 | }) 70 | 71 | test.serial('PATH: undefined', async (t) => { 72 | await runThrows(t, { env: { [PATH]: undefined } }) 73 | }) 74 | } 75 | -------------------------------------------------------------------------------- /src/copy/output.js: -------------------------------------------------------------------------------- 1 | import { createHash } from 'node:crypto' 2 | import { join } from 'node:path' 3 | 4 | import globalCacheDir from 'global-cache-dir' 5 | 6 | // Retrieve directory where binaries are copied to. 7 | // Each `nvexeca()` call gets its own directory. This is because they might have 8 | // different `PATH` environment variables or be called at different times 9 | // leading to different available binaries. 10 | // For performance reasons and better concurrent behavior, two `nvexeca()` calls 11 | // with the same directory contents share the same directory. We do this by 12 | // hashing that contents and including the hash in the directory filename. 13 | export const getDistBinDir = async (srcPaths) => { 14 | const hash = getHash(srcPaths) 15 | const output = await getOutput() 16 | const distBinDir = join(output, BIN_DIR_PARENT, `${hash}${BIN_DIR_SUFFIX}`) 17 | return distBinDir 18 | } 19 | 20 | const getHash = (srcPaths) => { 21 | const contents = srcPaths.map(getSrcPathHash).join('\n') 22 | const hash = computeSha(contents) 23 | return hash 24 | } 25 | 26 | const getSrcPathHash = ({ filename, content }) => `${filename}\n${content}` 27 | 28 | const computeSha = (contents) => { 29 | const hashStream = createHash('sha256') 30 | hashStream.update(contents) 31 | const hash = hashStream.digest('hex') 32 | return hash 33 | } 34 | 35 | // Retrieve cache directory 36 | export const getOutput = () => globalCacheDir(CACHE_DIR) 37 | 38 | const CACHE_DIR = 'nve' 39 | 40 | // If the output directory is in the PATH, we ignore it. 41 | // This happens on recursive calls. 42 | export const isOutputDir = (dir) => 43 | dir.replace(TRAILING_SLASH_REGEXP, '').endsWith(BIN_DIR_SUFFIX) 44 | 45 | const TRAILING_SLASH_REGEXP = /[/\\]$/u 46 | 47 | const BIN_DIR_PARENT = 'all' 48 | const BIN_DIR_SUFFIX = '-nve-bin' 49 | -------------------------------------------------------------------------------- /src/copy/path.js: -------------------------------------------------------------------------------- 1 | import { delimiter } from 'node:path' 2 | import { env } from 'node:process' 3 | 4 | import pathKey from 'path-key' 5 | 6 | // Retrieve the `PATH` environment variable that will be used in the child 7 | // process. This means we are emulating `execa` logic. 8 | export const getPath = ({ extendEnv = true, env: envOpt }) => { 9 | const fullEnv = { ...(extendEnv ? env : {}), ...envOpt } 10 | const pathName = pathKey({ env: fullEnv }) 11 | const pathValue = fullEnv[pathName] 12 | return { pathName, pathValue } 13 | } 14 | 15 | // When copying global binaries (on Windows), add their directory to the `PATH` 16 | // environment variable 17 | export const addToPath = ({ 18 | execaOptions, 19 | pathName, 20 | pathValue, 21 | distBinDir, 22 | }) => { 23 | const pathEnv = `${distBinDir}${delimiter}${pathValue}` 24 | return { ...execaOptions, env: { ...execaOptions.env, [pathName]: pathEnv } } 25 | } 26 | -------------------------------------------------------------------------------- /src/copy/write.js: -------------------------------------------------------------------------------- 1 | import { mkdir, rename, rm, writeFile } from 'node:fs/promises' 2 | 3 | import { pathExists } from 'path-exists' 4 | 5 | // Copy binaries to the destination directory. 6 | // Directories contain their contents hash in their filename, i.e. if they 7 | // already exist, we can re-use them. 8 | // We write files to a temporary directory first before renaming it to its final 9 | // name. We do this to support concurrent calls. Otherwise a concurrent process 10 | // might consider binaries already written based on their directory existing, 11 | // even though they are being written. This would lead to `ETXTBUSY` errors. 12 | export const writeBinaries = async (srcPaths, distBinDir) => { 13 | if (await pathExists(distBinDir)) { 14 | return 15 | } 16 | 17 | const tmpBinDir = getTmpBinDir(distBinDir) 18 | await mkdir(tmpBinDir, { recursive: true }) 19 | 20 | await Promise.all(srcPaths.map((srcPath) => writeBinary(srcPath, tmpBinDir))) 21 | 22 | try { 23 | await rename(tmpBinDir, distBinDir) 24 | // This might fail if two concurrent processes are happening at exactly the 25 | // same time 26 | } catch { 27 | await rm(tmpBinDir, { force: true, recursive: true }) 28 | } 29 | } 30 | 31 | const getTmpBinDir = (distBinDir) => { 32 | const randomId = String(Math.random()).replace('.', '') 33 | return `${distBinDir}-${randomId}` 34 | } 35 | 36 | const writeBinary = async ({ filename, content }, tmpBinDir) => { 37 | const tmpPath = `${tmpBinDir}/${filename}` 38 | await writeFile(tmpPath, content, { mode: DIST_MODE }) 39 | } 40 | 41 | const DIST_MODE = 0o755 42 | -------------------------------------------------------------------------------- /src/fixtures/10.16.0/node_modules/printversion/main.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | // eslint-disable-next-line no-restricted-globals, no-console, n/prefer-global/process 4 | console.log(process.version) 5 | -------------------------------------------------------------------------------- /src/fixtures/10.16.0/npm: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | (set -o igncr) 2>/dev/null && set -o igncr; # cygwin encoding fix 3 | 4 | basedir=`dirname "$0"` 5 | 6 | case `uname` in 7 | *CYGWIN*) basedir=`cygpath -w "$basedir"`;; 8 | esac 9 | 10 | NODE_EXE="$basedir/node.exe" 11 | if ! [ -x "$NODE_EXE" ]; then 12 | NODE_EXE=node 13 | fi 14 | 15 | NPM_CLI_JS="$basedir/node_modules/npm/bin/npm-cli.js" 16 | 17 | case `uname` in 18 | *MINGW*) 19 | NPM_PREFIX=`"$NODE_EXE" "$NPM_CLI_JS" prefix -g` 20 | NPM_PREFIX_NPM_CLI_JS="$NPM_PREFIX/node_modules/npm/bin/npm-cli.js" 21 | if [ -f "$NPM_PREFIX_NPM_CLI_JS" ]; then 22 | NPM_CLI_JS="$NPM_PREFIX_NPM_CLI_JS" 23 | fi 24 | ;; 25 | *CYGWIN*) 26 | NPM_PREFIX=`"$NODE_EXE" "$NPM_CLI_JS" prefix -g` 27 | NPM_PREFIX_NPM_CLI_JS="$NPM_PREFIX/node_modules/npm/bin/npm-cli.js" 28 | if [ -f "$NPM_PREFIX_NPM_CLI_JS" ]; then 29 | NPM_CLI_JS="$NPM_PREFIX_NPM_CLI_JS" 30 | fi 31 | ;; 32 | esac 33 | 34 | "$NODE_EXE" "$NPM_CLI_JS" "$@" 35 | -------------------------------------------------------------------------------- /src/fixtures/10.16.0/npm.cmd: -------------------------------------------------------------------------------- 1 | :: Created by npm, please don't edit manually. 2 | @ECHO OFF 3 | 4 | SETLOCAL 5 | 6 | SET "NODE_EXE=%~dp0\node.exe" 7 | IF NOT EXIST "%NODE_EXE%" ( 8 | SET "NODE_EXE=node" 9 | ) 10 | 11 | SET "NPM_CLI_JS=%~dp0\node_modules\npm\bin\npm-cli.js" 12 | FOR /F "delims=" %%F IN ('CALL "%NODE_EXE%" "%NPM_CLI_JS%" prefix -g') DO ( 13 | SET "NPM_PREFIX_NPM_CLI_JS=%%F\node_modules\npm\bin\npm-cli.js" 14 | ) 15 | IF EXIST "%NPM_PREFIX_NPM_CLI_JS%" ( 16 | SET "NPM_CLI_JS=%NPM_PREFIX_NPM_CLI_JS%" 17 | ) 18 | 19 | "%NODE_EXE%" "%NPM_CLI_JS%" %* 20 | -------------------------------------------------------------------------------- /src/fixtures/10.16.0/npx: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | (set -o igncr) 2>/dev/null && set -o igncr; # cygwin encoding fix 3 | 4 | basedir=`dirname "$0"` 5 | 6 | case `uname` in 7 | *CYGWIN*) basedir=`cygpath -w "$basedir"`;; 8 | esac 9 | 10 | NODE_EXE="$basedir/node.exe" 11 | if ! [ -x "$NODE_EXE" ]; then 12 | NODE_EXE=node 13 | fi 14 | 15 | NPM_CLI_JS="$basedir/node_modules/npm/bin/npm-cli.js" 16 | NPX_CLI_JS="$basedir/node_modules/npm/bin/npx-cli.js" 17 | 18 | case `uname` in 19 | *MINGW*) 20 | NPM_PREFIX=`"$NODE_EXE" "$NPM_CLI_JS" prefix -g` 21 | NPM_PREFIX_NPX_CLI_JS="$NPM_PREFIX/node_modules/npm/bin/npx-cli.js" 22 | if [ -f "$NPM_PREFIX_NPX_CLI_JS" ]; then 23 | NPX_CLI_JS="$NPM_PREFIX_NPX_CLI_JS" 24 | fi 25 | ;; 26 | *CYGWIN*) 27 | NPM_PREFIX=`"$NODE_EXE" "$NPM_CLI_JS" prefix -g` 28 | NPM_PREFIX_NPX_CLI_JS="$NPM_PREFIX/node_modules/npm/bin/npx-cli.js" 29 | if [ -f "$NPM_PREFIX_NPX_CLI_JS" ]; then 30 | NPX_CLI_JS="$NPM_PREFIX_NPX_CLI_JS" 31 | fi 32 | ;; 33 | esac 34 | 35 | "$NODE_EXE" "$NPX_CLI_JS" "$@" 36 | -------------------------------------------------------------------------------- /src/fixtures/10.16.0/npx.cmd: -------------------------------------------------------------------------------- 1 | :: Created by npm, please don't edit manually. 2 | @ECHO OFF 3 | 4 | SETLOCAL 5 | 6 | SET "NODE_EXE=%~dp0\node.exe" 7 | IF NOT EXIST "%NODE_EXE%" ( 8 | SET "NODE_EXE=node" 9 | ) 10 | 11 | SET "NPM_CLI_JS=%~dp0\node_modules\npm\bin\npm-cli.js" 12 | SET "NPX_CLI_JS=%~dp0\node_modules\npm\bin\npx-cli.js" 13 | FOR /F "delims=" %%F IN ('CALL "%NODE_EXE%" "%NPM_CLI_JS%" prefix -g') DO ( 14 | SET "NPM_PREFIX_NPX_CLI_JS=%%F\node_modules\npm\bin\npx-cli.js" 15 | ) 16 | IF EXIST "%NPM_PREFIX_NPX_CLI_JS%" ( 17 | SET "NPX_CLI_JS=%NPM_PREFIX_NPX_CLI_JS%" 18 | ) 19 | 20 | "%NODE_EXE%" "%NPX_CLI_JS%" %* 21 | -------------------------------------------------------------------------------- /src/fixtures/10.16.0/printversion: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") 3 | 4 | case `uname` in 5 | *CYGWIN*) basedir=`cygpath -w "$basedir"`;; 6 | esac 7 | 8 | if [ -x "$basedir/node" ]; then 9 | "$basedir/node" "$basedir/node_modules/printversion/main.js" "$@" 10 | ret=$? 11 | else 12 | node "$basedir/node_modules/printversion/main.js" "$@" 13 | ret=$? 14 | fi 15 | exit $ret 16 | -------------------------------------------------------------------------------- /src/fixtures/10.16.0/printversion.cmd: -------------------------------------------------------------------------------- 1 | @IF EXIST "%~dp0\node.exe" ( 2 | "%~dp0\node.exe" "%~dp0\node_modules\printversion\main.js" %* 3 | ) ELSE ( 4 | @SETLOCAL 5 | @SET PATHEXT=%PATHEXT:;.JS;=;% 6 | node "%~dp0\node_modules\printversion\main.js" %* 7 | ) 8 | -------------------------------------------------------------------------------- /src/fixtures/13.1.0/node_modules/printversion/main.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | // eslint-disable-next-line no-restricted-globals, no-console, n/prefer-global/process 4 | console.log(process.version) 5 | -------------------------------------------------------------------------------- /src/fixtures/13.1.0/npm: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | (set -o igncr) 2>/dev/null && set -o igncr; # cygwin encoding fix 3 | 4 | basedir=`dirname "$0"` 5 | 6 | case `uname` in 7 | *CYGWIN*) basedir=`cygpath -w "$basedir"`;; 8 | esac 9 | 10 | NODE_EXE="$basedir/node.exe" 11 | if [ -x "$NODE_EXE" ] && [ -f "/bin/wslpath" ]; then # run the corresponding command prompt when Node for Windows is executed within WSL 12 | cmd.exe /c `wslpath -w "$basedir/npm.cmd"` "$@" 13 | exit $? 14 | fi 15 | if ! [ -x "$NODE_EXE" ]; then 16 | NODE_EXE="$basedir/node" 17 | fi 18 | if ! [ -x "$NODE_EXE" ]; then 19 | NODE_EXE=node 20 | fi 21 | 22 | NPM_CLI_JS="$basedir/node_modules/npm/bin/npm-cli.js" 23 | 24 | case `uname` in 25 | *MINGW*) 26 | NPM_PREFIX=`"$NODE_EXE" "$NPM_CLI_JS" prefix -g` 27 | NPM_PREFIX_NPM_CLI_JS="$NPM_PREFIX/node_modules/npm/bin/npm-cli.js" 28 | if [ -f "$NPM_PREFIX_NPM_CLI_JS" ]; then 29 | NPM_CLI_JS="$NPM_PREFIX_NPM_CLI_JS" 30 | fi 31 | ;; 32 | *CYGWIN*) 33 | NPM_PREFIX=`"$NODE_EXE" "$NPM_CLI_JS" prefix -g` 34 | NPM_PREFIX_NPM_CLI_JS="$NPM_PREFIX/node_modules/npm/bin/npm-cli.js" 35 | if [ -f "$NPM_PREFIX_NPM_CLI_JS" ]; then 36 | NPM_CLI_JS="$NPM_PREFIX_NPM_CLI_JS" 37 | fi 38 | ;; 39 | esac 40 | 41 | "$NODE_EXE" "$NPM_CLI_JS" "$@" 42 | -------------------------------------------------------------------------------- /src/fixtures/13.1.0/npm.cmd: -------------------------------------------------------------------------------- 1 | :: Created by npm, please don't edit manually. 2 | @ECHO OFF 3 | 4 | SETLOCAL 5 | 6 | SET "NODE_EXE=%~dp0\node.exe" 7 | IF NOT EXIST "%NODE_EXE%" ( 8 | SET "NODE_EXE=node" 9 | ) 10 | 11 | SET "NPM_CLI_JS=%~dp0\node_modules\npm\bin\npm-cli.js" 12 | FOR /F "delims=" %%F IN ('CALL "%NODE_EXE%" "%NPM_CLI_JS%" prefix -g') DO ( 13 | SET "NPM_PREFIX_NPM_CLI_JS=%%F\node_modules\npm\bin\npm-cli.js" 14 | ) 15 | IF EXIST "%NPM_PREFIX_NPM_CLI_JS%" ( 16 | SET "NPM_CLI_JS=%NPM_PREFIX_NPM_CLI_JS%" 17 | ) 18 | 19 | "%NODE_EXE%" "%NPM_CLI_JS%" %* 20 | -------------------------------------------------------------------------------- /src/fixtures/13.1.0/npx: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | (set -o igncr) 2>/dev/null && set -o igncr; # cygwin encoding fix 3 | 4 | basedir=`dirname "$0"` 5 | 6 | case `uname` in 7 | *CYGWIN*) basedir=`cygpath -w "$basedir"`;; 8 | esac 9 | 10 | NODE_EXE="$basedir/node.exe" 11 | if [ -x "$NODE_EXE" ] && [ -f "/bin/wslpath" ]; then # run the corresponding command prompt when Node for Windows is executed within WSL 12 | cmd.exe /c `wslpath -w "$basedir/npx.cmd"` "$@" 13 | exit $? 14 | fi 15 | if ! [ -x "$NODE_EXE" ]; then 16 | NODE_EXE=node 17 | fi 18 | 19 | NPM_CLI_JS="$basedir/node_modules/npm/bin/npm-cli.js" 20 | NPX_CLI_JS="$basedir/node_modules/npm/bin/npx-cli.js" 21 | 22 | case `uname` in 23 | *MINGW*) 24 | NPM_PREFIX=`"$NODE_EXE" "$NPM_CLI_JS" prefix -g` 25 | NPM_PREFIX_NPX_CLI_JS="$NPM_PREFIX/node_modules/npm/bin/npx-cli.js" 26 | if [ -f "$NPM_PREFIX_NPX_CLI_JS" ]; then 27 | NPX_CLI_JS="$NPM_PREFIX_NPX_CLI_JS" 28 | fi 29 | ;; 30 | *CYGWIN*) 31 | NPM_PREFIX=`"$NODE_EXE" "$NPM_CLI_JS" prefix -g` 32 | NPM_PREFIX_NPX_CLI_JS="$NPM_PREFIX/node_modules/npm/bin/npx-cli.js" 33 | if [ -f "$NPM_PREFIX_NPX_CLI_JS" ]; then 34 | NPX_CLI_JS="$NPM_PREFIX_NPX_CLI_JS" 35 | fi 36 | ;; 37 | esac 38 | 39 | "$NODE_EXE" "$NPX_CLI_JS" "$@" 40 | -------------------------------------------------------------------------------- /src/fixtures/13.1.0/npx.cmd: -------------------------------------------------------------------------------- 1 | :: Created by npm, please don't edit manually. 2 | @ECHO OFF 3 | 4 | SETLOCAL 5 | 6 | SET "NODE_EXE=%~dp0\node.exe" 7 | IF NOT EXIST "%NODE_EXE%" ( 8 | SET "NODE_EXE=node" 9 | ) 10 | 11 | SET "NPM_CLI_JS=%~dp0\node_modules\npm\bin\npm-cli.js" 12 | SET "NPX_CLI_JS=%~dp0\node_modules\npm\bin\npx-cli.js" 13 | FOR /F "delims=" %%F IN ('CALL "%NODE_EXE%" "%NPM_CLI_JS%" prefix -g') DO ( 14 | SET "NPM_PREFIX_NPX_CLI_JS=%%F\node_modules\npm\bin\npx-cli.js" 15 | ) 16 | IF EXIST "%NPM_PREFIX_NPX_CLI_JS%" ( 17 | SET "NPX_CLI_JS=%NPM_PREFIX_NPX_CLI_JS%" 18 | ) 19 | 20 | "%NODE_EXE%" "%NPX_CLI_JS%" %* 21 | -------------------------------------------------------------------------------- /src/fixtures/13.1.0/printversion: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") 3 | 4 | case `uname` in 5 | *CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;; 6 | esac 7 | 8 | if [ -x "$basedir/node" ]; then 9 | "$basedir/node" "$basedir/node_modules/printversion/main.js" "$@" 10 | ret=$? 11 | else 12 | node "$basedir/node_modules/printversion/main.js" "$@" 13 | ret=$? 14 | fi 15 | exit $ret 16 | -------------------------------------------------------------------------------- /src/fixtures/13.1.0/printversion.cmd: -------------------------------------------------------------------------------- 1 | @ECHO off 2 | SETLOCAL 3 | CALL :find_dp0 4 | 5 | IF EXIST "%dp0%\node.exe" ( 6 | SET "_prog=%dp0%\node.exe" 7 | ) ELSE ( 8 | SET "_prog=node" 9 | SET PATHEXT=%PATHEXT:;.JS;=;% 10 | ) 11 | 12 | "%_prog%" "%dp0%\node_modules\printversion\main.js" %* 13 | ENDLOCAL 14 | EXIT /b %errorlevel% 15 | :find_dp0 16 | SET dp0=%~dp0 17 | EXIT /b 18 | -------------------------------------------------------------------------------- /src/fixtures/13.1.0/printversion.ps1: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pwsh 2 | $basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent 3 | 4 | $exe="" 5 | if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) { 6 | # Fix case when both the Windows and Linux builds of Node 7 | # are installed in the same directory 8 | $exe=".exe" 9 | } 10 | $ret=0 11 | if (Test-Path "$basedir/node$exe") { 12 | & "$basedir/node$exe" "$basedir/node_modules/printversion/main.js" $args 13 | $ret=$LASTEXITCODE 14 | } else { 15 | & "node$exe" "$basedir/node_modules/printversion/main.js" $args 16 | $ret=$LASTEXITCODE 17 | } 18 | exit $ret 19 | -------------------------------------------------------------------------------- /src/fixtures/18.18.0/node_modules/printversion/main.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | // eslint-disable-next-line no-restricted-globals, no-console, n/prefer-global/process 4 | console.log(process.version) 5 | -------------------------------------------------------------------------------- /src/fixtures/18.18.0/npm: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This is used by the Node.js installer, which expects the cygwin/mingw 4 | # shell script to already be present in the npm dependency folder. 5 | 6 | (set -o igncr) 2>/dev/null && set -o igncr; # cygwin encoding fix 7 | 8 | basedir=`dirname "$0"` 9 | 10 | case `uname` in 11 | *CYGWIN*) basedir=`cygpath -w "$basedir"`;; 12 | esac 13 | 14 | if [ `uname` = 'Linux' ] && type wslpath &>/dev/null ; then 15 | IS_WSL="true" 16 | fi 17 | 18 | function no_node_dir { 19 | # if this didn't work, then everything else below will fail 20 | echo "Could not determine Node.js install directory" >&2 21 | exit 1 22 | } 23 | 24 | NODE_EXE="$basedir/node.exe" 25 | if ! [ -x "$NODE_EXE" ]; then 26 | NODE_EXE="$basedir/node" 27 | fi 28 | if ! [ -x "$NODE_EXE" ]; then 29 | NODE_EXE=node 30 | fi 31 | 32 | # this path is passed to node.exe, so it needs to match whatever 33 | # kind of paths Node.js thinks it's using, typically win32 paths. 34 | CLI_BASEDIR="$("$NODE_EXE" -p 'require("path").dirname(process.execPath)' 2> /dev/null)" 35 | if [ $? -ne 0 ]; then 36 | # this fails under WSL 1 so add an additional message. we also suppress stderr above 37 | # because the actual error raised is not helpful. in WSL 1 node.exe cannot handle 38 | # output redirection properly. See https://github.com/microsoft/WSL/issues/2370 39 | if [ "$IS_WSL" == "true" ]; then 40 | echo "WSL 1 is not supported. Please upgrade to WSL 2 or above." >&2 41 | fi 42 | no_node_dir 43 | fi 44 | NPM_CLI_JS="$CLI_BASEDIR/node_modules/npm/bin/npm-cli.js" 45 | NPM_PREFIX=`"$NODE_EXE" "$NPM_CLI_JS" prefix -g` 46 | if [ $? -ne 0 ]; then 47 | no_node_dir 48 | fi 49 | NPM_PREFIX_NPM_CLI_JS="$NPM_PREFIX/node_modules/npm/bin/npm-cli.js" 50 | 51 | # a path that will fail -f test on any posix bash 52 | NPM_WSL_PATH="/.." 53 | 54 | # WSL can run Windows binaries, so we have to give it the win32 path 55 | # however, WSL bash tests against posix paths, so we need to construct that 56 | # to know if npm is installed globally. 57 | if [ "$IS_WSL" == "true" ]; then 58 | NPM_WSL_PATH=`wslpath "$NPM_PREFIX_NPM_CLI_JS"` 59 | fi 60 | if [ -f "$NPM_PREFIX_NPM_CLI_JS" ] || [ -f "$NPM_WSL_PATH" ]; then 61 | NPM_CLI_JS="$NPM_PREFIX_NPM_CLI_JS" 62 | fi 63 | 64 | "$NODE_EXE" "$NPM_CLI_JS" "$@" 65 | -------------------------------------------------------------------------------- /src/fixtures/18.18.0/npm.cmd: -------------------------------------------------------------------------------- 1 | :: Created by npm, please don't edit manually. 2 | @ECHO OFF 3 | 4 | SETLOCAL 5 | 6 | SET "NODE_EXE=%~dp0\node.exe" 7 | IF NOT EXIST "%NODE_EXE%" ( 8 | SET "NODE_EXE=node" 9 | ) 10 | 11 | SET "NPM_CLI_JS=%~dp0\node_modules\npm\bin\npm-cli.js" 12 | FOR /F "delims=" %%F IN ('CALL "%NODE_EXE%" "%NPM_CLI_JS%" prefix -g') DO ( 13 | SET "NPM_PREFIX_NPM_CLI_JS=%%F\node_modules\npm\bin\npm-cli.js" 14 | ) 15 | IF EXIST "%NPM_PREFIX_NPM_CLI_JS%" ( 16 | SET "NPM_CLI_JS=%NPM_PREFIX_NPM_CLI_JS%" 17 | ) 18 | 19 | "%NODE_EXE%" "%NPM_CLI_JS%" %* 20 | -------------------------------------------------------------------------------- /src/fixtures/18.18.0/npm.ps1: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pwsh 2 | $basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent 3 | 4 | $exe="" 5 | if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) { 6 | # Fix case when both the Windows and Linux builds of Node 7 | # are installed in the same directory 8 | $exe=".exe" 9 | } 10 | $ret=0 11 | 12 | $nodeexe = "node$exe" 13 | $nodebin = $(Get-Command $nodeexe -ErrorAction SilentlyContinue -ErrorVariable F).Source 14 | if ($nodebin -eq $null) { 15 | Write-Host "$nodeexe not found." 16 | exit 1 17 | } 18 | $nodedir = $(New-Object -ComObject Scripting.FileSystemObject).GetFile("$nodebin").ParentFolder.Path 19 | 20 | $npmclijs="$nodedir/node_modules/npm/bin/npm-cli.js" 21 | $npmprefix=(& $nodeexe $npmclijs prefix -g) 22 | if ($LASTEXITCODE -ne 0) { 23 | Write-Host "Could not determine Node.js install directory" 24 | exit 1 25 | } 26 | $npmprefixclijs="$npmprefix/node_modules/npm/bin/npm-cli.js" 27 | 28 | # Support pipeline input 29 | if ($MyInvocation.ExpectingInput) { 30 | $input | & $nodeexe $npmprefixclijs $args 31 | } else { 32 | & $nodeexe $npmprefixclijs $args 33 | } 34 | $ret=$LASTEXITCODE 35 | exit $ret 36 | -------------------------------------------------------------------------------- /src/fixtures/18.18.0/npx: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This is used by the Node.js installer, which expects the cygwin/mingw 4 | # shell script to already be present in the npm dependency folder. 5 | 6 | (set -o igncr) 2>/dev/null && set -o igncr; # cygwin encoding fix 7 | 8 | basedir=`dirname "$0"` 9 | 10 | case `uname` in 11 | *CYGWIN*) basedir=`cygpath -w "$basedir"`;; 12 | esac 13 | 14 | if [ `uname` = 'Linux' ] && type wslpath &>/dev/null ; then 15 | IS_WSL="true" 16 | fi 17 | 18 | function no_node_dir { 19 | # if this didn't work, then everything else below will fail 20 | echo "Could not determine Node.js install directory" >&2 21 | exit 1 22 | } 23 | 24 | NODE_EXE="$basedir/node.exe" 25 | if ! [ -x "$NODE_EXE" ]; then 26 | NODE_EXE="$basedir/node" 27 | fi 28 | if ! [ -x "$NODE_EXE" ]; then 29 | NODE_EXE=node 30 | fi 31 | 32 | # this path is passed to node.exe, so it needs to match whatever 33 | # kind of paths Node.js thinks it's using, typically win32 paths. 34 | CLI_BASEDIR="$("$NODE_EXE" -p 'require("path").dirname(process.execPath)' 2> /dev/null)" 35 | if [ $? -ne 0 ]; then 36 | # this fails under WSL 1 so add an additional message. we also suppress stderr above 37 | # because the actual error raised is not helpful. in WSL 1 node.exe cannot handle 38 | # output redirection properly. See https://github.com/microsoft/WSL/issues/2370 39 | if [ "$IS_WSL" == "true" ]; then 40 | echo "WSL 1 is not supported. Please upgrade to WSL 2 or above." >&2 41 | fi 42 | no_node_dir 43 | fi 44 | NPM_CLI_JS="$CLI_BASEDIR/node_modules/npm/bin/npm-cli.js" 45 | NPX_CLI_JS="$CLI_BASEDIR/node_modules/npm/bin/npx-cli.js" 46 | NPM_PREFIX=`"$NODE_EXE" "$NPM_CLI_JS" prefix -g` 47 | if [ $? -ne 0 ]; then 48 | no_node_dir 49 | fi 50 | NPM_PREFIX_NPX_CLI_JS="$NPM_PREFIX/node_modules/npm/bin/npx-cli.js" 51 | 52 | # a path that will fail -f test on any posix bash 53 | NPX_WSL_PATH="/.." 54 | 55 | # WSL can run Windows binaries, so we have to give it the win32 path 56 | # however, WSL bash tests against posix paths, so we need to construct that 57 | # to know if npm is installed globally. 58 | if [ "$IS_WSL" == "true" ]; then 59 | NPX_WSL_PATH=`wslpath "$NPM_PREFIX_NPX_CLI_JS"` 60 | fi 61 | if [ -f "$NPM_PREFIX_NPX_CLI_JS" ] || [ -f "$NPX_WSL_PATH" ]; then 62 | NPX_CLI_JS="$NPM_PREFIX_NPX_CLI_JS" 63 | fi 64 | 65 | "$NODE_EXE" "$NPX_CLI_JS" "$@" 66 | -------------------------------------------------------------------------------- /src/fixtures/18.18.0/npx.cmd: -------------------------------------------------------------------------------- 1 | :: Created by npm, please don't edit manually. 2 | @ECHO OFF 3 | 4 | SETLOCAL 5 | 6 | SET "NODE_EXE=%~dp0\node.exe" 7 | IF NOT EXIST "%NODE_EXE%" ( 8 | SET "NODE_EXE=node" 9 | ) 10 | 11 | SET "NPM_CLI_JS=%~dp0\node_modules\npm\bin\npm-cli.js" 12 | SET "NPX_CLI_JS=%~dp0\node_modules\npm\bin\npx-cli.js" 13 | FOR /F "delims=" %%F IN ('CALL "%NODE_EXE%" "%NPM_CLI_JS%" prefix -g') DO ( 14 | SET "NPM_PREFIX_NPX_CLI_JS=%%F\node_modules\npm\bin\npx-cli.js" 15 | ) 16 | IF EXIST "%NPM_PREFIX_NPX_CLI_JS%" ( 17 | SET "NPX_CLI_JS=%NPM_PREFIX_NPX_CLI_JS%" 18 | ) 19 | 20 | "%NODE_EXE%" "%NPX_CLI_JS%" %* 21 | -------------------------------------------------------------------------------- /src/fixtures/18.18.0/npx.ps1: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pwsh 2 | $basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent 3 | 4 | $exe="" 5 | if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) { 6 | # Fix case when both the Windows and Linux builds of Node 7 | # are installed in the same directory 8 | $exe=".exe" 9 | } 10 | $ret=0 11 | 12 | $nodeexe = "node$exe" 13 | $nodebin = $(Get-Command $nodeexe -ErrorAction SilentlyContinue -ErrorVariable F).Source 14 | if ($nodebin -eq $null) { 15 | Write-Host "$nodeexe not found." 16 | exit 1 17 | } 18 | $nodedir = $(New-Object -ComObject Scripting.FileSystemObject).GetFile("$nodebin").ParentFolder.Path 19 | 20 | $npmclijs="$nodedir/node_modules/npm/bin/npm-cli.js" 21 | $npmprefix=(& $nodeexe $npmclijs prefix -g) 22 | if ($LASTEXITCODE -ne 0) { 23 | Write-Host "Could not determine Node.js install directory" 24 | exit 1 25 | } 26 | $npmprefixclijs="$npmprefix/node_modules/npm/bin/npx-cli.js" 27 | 28 | # Support pipeline input 29 | if ($MyInvocation.ExpectingInput) { 30 | $input | & $nodeexe $npmprefixclijs $args 31 | } else { 32 | & $nodeexe $npmprefixclijs $args 33 | } 34 | $ret=$LASTEXITCODE 35 | exit $ret 36 | -------------------------------------------------------------------------------- /src/fixtures/18.18.0/printversion: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") 3 | 4 | case `uname` in 5 | *CYGWIN*|*MINGW*|*MSYS*) 6 | if command -v cygpath > /dev/null 2>&1; then 7 | basedir=`cygpath -w "$basedir"`;; 8 | fi 9 | esac 10 | 11 | if [ -x "$basedir/node" ]; then 12 | exec "$basedir/node" "$basedir/node_modules/printversion/main.js" "$@" 13 | else 14 | exec node "$basedir/node_modules/printversion/main.js" "$@" 15 | fi 16 | -------------------------------------------------------------------------------- /src/fixtures/18.18.0/printversion.cmd: -------------------------------------------------------------------------------- 1 | @ECHO off 2 | GOTO start 3 | :find_dp0 4 | SET dp0=%~dp0 5 | EXIT /b 6 | :start 7 | SETLOCAL 8 | CALL :find_dp0 9 | 10 | IF EXIST "%dp0%\node.exe" ( 11 | SET "_prog=%dp0%\node.exe" 12 | ) ELSE ( 13 | SET "_prog=node" 14 | SET PATHEXT=%PATHEXT:;.JS;=;% 15 | ) 16 | 17 | endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\node_modules\printversion\main.js" %* 18 | -------------------------------------------------------------------------------- /src/fixtures/18.18.0/printversion.ps1: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pwsh 2 | $basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent 3 | 4 | $exe="" 5 | if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) { 6 | # Fix case when both the Windows and Linux builds of Node 7 | # are installed in the same directory 8 | $exe=".exe" 9 | } 10 | $ret=0 11 | if (Test-Path "$basedir/node$exe") { 12 | # Support pipeline input 13 | if ($MyInvocation.ExpectingInput) { 14 | $input | & "$basedir/node$exe" "$basedir/node_modules/printversion/main.js" $args 15 | } else { 16 | & "$basedir/node$exe" "$basedir/node_modules/printversion/main.js" $args 17 | } 18 | $ret=$LASTEXITCODE 19 | } else { 20 | # Support pipeline input 21 | if ($MyInvocation.ExpectingInput) { 22 | $input | & "node$exe" "$basedir/node_modules/printversion/main.js" $args 23 | } else { 24 | & "node$exe" "$basedir/node_modules/printversion/main.js" $args 25 | } 26 | $ret=$LASTEXITCODE 27 | } 28 | exit $ret 29 | -------------------------------------------------------------------------------- /src/fixtures/22.1.0/node_modules/printversion/main.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | // eslint-disable-next-line no-restricted-globals, no-console, n/prefer-global/process 4 | console.log(process.version) 5 | -------------------------------------------------------------------------------- /src/fixtures/22.1.0/npm: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This is used by the Node.js installer, which expects the cygwin/mingw 4 | # shell script to already be present in the npm dependency folder. 5 | 6 | (set -o igncr) 2>/dev/null && set -o igncr; # cygwin encoding fix 7 | 8 | basedir=`dirname "$0"` 9 | 10 | case `uname` in 11 | *CYGWIN*) basedir=`cygpath -w "$basedir"`;; 12 | esac 13 | 14 | if [ `uname` = 'Linux' ] && type wslpath &>/dev/null ; then 15 | IS_WSL="true" 16 | fi 17 | 18 | function no_node_dir { 19 | # if this didn't work, then everything else below will fail 20 | echo "Could not determine Node.js install directory" >&2 21 | exit 1 22 | } 23 | 24 | NODE_EXE="$basedir/node.exe" 25 | if ! [ -x "$NODE_EXE" ]; then 26 | NODE_EXE="$basedir/node" 27 | fi 28 | if ! [ -x "$NODE_EXE" ]; then 29 | NODE_EXE=node 30 | fi 31 | 32 | # this path is passed to node.exe, so it needs to match whatever 33 | # kind of paths Node.js thinks it's using, typically win32 paths. 34 | CLI_BASEDIR="$("$NODE_EXE" -p 'require("path").dirname(process.execPath)' 2> /dev/null)" 35 | if [ $? -ne 0 ]; then 36 | # this fails under WSL 1 so add an additional message. we also suppress stderr above 37 | # because the actual error raised is not helpful. in WSL 1 node.exe cannot handle 38 | # output redirection properly. See https://github.com/microsoft/WSL/issues/2370 39 | if [ "$IS_WSL" == "true" ]; then 40 | echo "WSL 1 is not supported. Please upgrade to WSL 2 or above." >&2 41 | fi 42 | no_node_dir 43 | fi 44 | NPM_PREFIX_JS="$CLI_BASEDIR/node_modules/npm/bin/npm-prefix.js" 45 | NPM_CLI_JS="$CLI_BASEDIR/node_modules/npm/bin/npm-cli.js" 46 | NPM_PREFIX=`"$NODE_EXE" "$NPM_PREFIX_JS"` 47 | if [ $? -ne 0 ]; then 48 | no_node_dir 49 | fi 50 | NPM_PREFIX_NPM_CLI_JS="$NPM_PREFIX/node_modules/npm/bin/npm-cli.js" 51 | 52 | # a path that will fail -f test on any posix bash 53 | NPM_WSL_PATH="/.." 54 | 55 | # WSL can run Windows binaries, so we have to give it the win32 path 56 | # however, WSL bash tests against posix paths, so we need to construct that 57 | # to know if npm is installed globally. 58 | if [ "$IS_WSL" == "true" ]; then 59 | NPM_WSL_PATH=`wslpath "$NPM_PREFIX_NPM_CLI_JS"` 60 | fi 61 | if [ -f "$NPM_PREFIX_NPM_CLI_JS" ] || [ -f "$NPM_WSL_PATH" ]; then 62 | NPM_CLI_JS="$NPM_PREFIX_NPM_CLI_JS" 63 | fi 64 | 65 | "$NODE_EXE" "$NPM_CLI_JS" "$@" 66 | -------------------------------------------------------------------------------- /src/fixtures/22.1.0/npm.cmd: -------------------------------------------------------------------------------- 1 | :: Created by npm, please don't edit manually. 2 | @ECHO OFF 3 | 4 | SETLOCAL 5 | 6 | SET "NODE_EXE=%~dp0\node.exe" 7 | IF NOT EXIST "%NODE_EXE%" ( 8 | SET "NODE_EXE=node" 9 | ) 10 | 11 | SET "NPM_PREFIX_JS=%~dp0\node_modules\npm\bin\npm-prefix.js" 12 | SET "NPM_CLI_JS=%~dp0\node_modules\npm\bin\npm-cli.js" 13 | FOR /F "delims=" %%F IN ('CALL "%NODE_EXE%" "%NPM_PREFIX_JS%"') DO ( 14 | SET "NPM_PREFIX_NPM_CLI_JS=%%F\node_modules\npm\bin\npm-cli.js" 15 | ) 16 | IF EXIST "%NPM_PREFIX_NPM_CLI_JS%" ( 17 | SET "NPM_CLI_JS=%NPM_PREFIX_NPM_CLI_JS%" 18 | ) 19 | 20 | "%NODE_EXE%" "%NPM_CLI_JS%" %* 21 | -------------------------------------------------------------------------------- /src/fixtures/22.1.0/npm.ps1: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pwsh 2 | 3 | $NODE_EXE="$PSScriptRoot/node.exe" 4 | if (-not (Test-Path $NODE_EXE)) { 5 | $NODE_EXE="$PSScriptRoot/node" 6 | } 7 | if (-not (Test-Path $NODE_EXE)) { 8 | $NODE_EXE="node" 9 | } 10 | 11 | $NPM_PREFIX_JS="$PSScriptRoot/node_modules/npm/bin/npm-prefix.js" 12 | $NPM_CLI_JS="$PSScriptRoot/node_modules/npm/bin/npm-cli.js" 13 | $NPM_PREFIX=(& $NODE_EXE $NPM_PREFIX_JS) 14 | 15 | if ($LASTEXITCODE -ne 0) { 16 | Write-Host "Could not determine Node.js install directory" 17 | exit 1 18 | } 19 | 20 | $NPM_PREFIX_NPM_CLI_JS="$NPM_PREFIX/node_modules/npm/bin/npm-cli.js" 21 | if (Test-Path $NPM_PREFIX_NPM_CLI_JS) { 22 | $NPM_CLI_JS=$NPM_PREFIX_NPM_CLI_JS 23 | } 24 | 25 | # Support pipeline input 26 | if ($MyInvocation.ExpectingInput) { 27 | $input | & $NODE_EXE $NPM_CLI_JS $args 28 | } else { 29 | & $NODE_EXE $NPM_CLI_JS $args 30 | } 31 | 32 | exit $LASTEXITCODE 33 | -------------------------------------------------------------------------------- /src/fixtures/22.1.0/npx: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This is used by the Node.js installer, which expects the cygwin/mingw 4 | # shell script to already be present in the npm dependency folder. 5 | 6 | (set -o igncr) 2>/dev/null && set -o igncr; # cygwin encoding fix 7 | 8 | basedir=`dirname "$0"` 9 | 10 | case `uname` in 11 | *CYGWIN*) basedir=`cygpath -w "$basedir"`;; 12 | esac 13 | 14 | if [ `uname` = 'Linux' ] && type wslpath &>/dev/null ; then 15 | IS_WSL="true" 16 | fi 17 | 18 | function no_node_dir { 19 | # if this didn't work, then everything else below will fail 20 | echo "Could not determine Node.js install directory" >&2 21 | exit 1 22 | } 23 | 24 | NODE_EXE="$basedir/node.exe" 25 | if ! [ -x "$NODE_EXE" ]; then 26 | NODE_EXE="$basedir/node" 27 | fi 28 | if ! [ -x "$NODE_EXE" ]; then 29 | NODE_EXE=node 30 | fi 31 | 32 | # this path is passed to node.exe, so it needs to match whatever 33 | # kind of paths Node.js thinks it's using, typically win32 paths. 34 | CLI_BASEDIR="$("$NODE_EXE" -p 'require("path").dirname(process.execPath)' 2> /dev/null)" 35 | if [ $? -ne 0 ]; then 36 | # this fails under WSL 1 so add an additional message. we also suppress stderr above 37 | # because the actual error raised is not helpful. in WSL 1 node.exe cannot handle 38 | # output redirection properly. See https://github.com/microsoft/WSL/issues/2370 39 | if [ "$IS_WSL" == "true" ]; then 40 | echo "WSL 1 is not supported. Please upgrade to WSL 2 or above." >&2 41 | fi 42 | no_node_dir 43 | fi 44 | NPM_PREFIX_JS="$CLI_BASEDIR/node_modules/npm/bin/npm-prefix.js" 45 | NPX_CLI_JS="$CLI_BASEDIR/node_modules/npm/bin/npx-cli.js" 46 | NPM_PREFIX=`"$NODE_EXE" "$NPM_PREFIX_JS"` 47 | if [ $? -ne 0 ]; then 48 | no_node_dir 49 | fi 50 | NPM_PREFIX_NPX_CLI_JS="$NPM_PREFIX/node_modules/npm/bin/npx-cli.js" 51 | 52 | # a path that will fail -f test on any posix bash 53 | NPX_WSL_PATH="/.." 54 | 55 | # WSL can run Windows binaries, so we have to give it the win32 path 56 | # however, WSL bash tests against posix paths, so we need to construct that 57 | # to know if npm is installed globally. 58 | if [ "$IS_WSL" == "true" ]; then 59 | NPX_WSL_PATH=`wslpath "$NPM_PREFIX_NPX_CLI_JS"` 60 | fi 61 | if [ -f "$NPM_PREFIX_NPX_CLI_JS" ] || [ -f "$NPX_WSL_PATH" ]; then 62 | NPX_CLI_JS="$NPM_PREFIX_NPX_CLI_JS" 63 | fi 64 | 65 | "$NODE_EXE" "$NPX_CLI_JS" "$@" 66 | -------------------------------------------------------------------------------- /src/fixtures/22.1.0/npx.cmd: -------------------------------------------------------------------------------- 1 | :: Created by npm, please don't edit manually. 2 | @ECHO OFF 3 | 4 | SETLOCAL 5 | 6 | SET "NODE_EXE=%~dp0\node.exe" 7 | IF NOT EXIST "%NODE_EXE%" ( 8 | SET "NODE_EXE=node" 9 | ) 10 | 11 | SET "NPM_PREFIX_JS=%~dp0\node_modules\npm\bin\npm-prefix.js" 12 | SET "NPX_CLI_JS=%~dp0\node_modules\npm\bin\npx-cli.js" 13 | FOR /F "delims=" %%F IN ('CALL "%NODE_EXE%" "%NPM_PREFIX_JS%"') DO ( 14 | SET "NPM_PREFIX_NPX_CLI_JS=%%F\node_modules\npm\bin\npx-cli.js" 15 | ) 16 | IF EXIST "%NPM_PREFIX_NPX_CLI_JS%" ( 17 | SET "NPX_CLI_JS=%NPM_PREFIX_NPX_CLI_JS%" 18 | ) 19 | 20 | "%NODE_EXE%" "%NPX_CLI_JS%" %* 21 | -------------------------------------------------------------------------------- /src/fixtures/22.1.0/npx.ps1: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pwsh 2 | 3 | $NODE_EXE="$PSScriptRoot/node.exe" 4 | if (-not (Test-Path $NODE_EXE)) { 5 | $NODE_EXE="$PSScriptRoot/node" 6 | } 7 | if (-not (Test-Path $NODE_EXE)) { 8 | $NODE_EXE="node" 9 | } 10 | 11 | $NPM_PREFIX_JS="$PSScriptRoot/node_modules/npm/bin/npm-prefix.js" 12 | $NPX_CLI_JS="$PSScriptRoot/node_modules/npm/bin/npx-cli.js" 13 | $NPM_PREFIX=(& $NODE_EXE $NPM_PREFIX_JS) 14 | 15 | if ($LASTEXITCODE -ne 0) { 16 | Write-Host "Could not determine Node.js install directory" 17 | exit 1 18 | } 19 | 20 | $NPM_PREFIX_NPX_CLI_JS="$NPM_PREFIX/node_modules/npm/bin/npx-cli.js" 21 | if (Test-Path $NPM_PREFIX_NPX_CLI_JS) { 22 | $NPX_CLI_JS=$NPM_PREFIX_NPX_CLI_JS 23 | } 24 | 25 | # Support pipeline input 26 | if ($MyInvocation.ExpectingInput) { 27 | $input | & $NODE_EXE $NPX_CLI_JS $args 28 | } else { 29 | & $NODE_EXE $NPX_CLI_JS $args 30 | } 31 | 32 | exit $LASTEXITCODE 33 | -------------------------------------------------------------------------------- /src/fixtures/22.1.0/printversion: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") 3 | 4 | case `uname` in 5 | *CYGWIN*|*MINGW*|*MSYS*) 6 | if command -v cygpath > /dev/null 2>&1; then 7 | basedir=`cygpath -w "$basedir"`;; 8 | fi 9 | esac 10 | 11 | if [ -x "$basedir/node" ]; then 12 | exec "$basedir/node" "$basedir/node_modules/printversion/main.js" "$@" 13 | else 14 | exec node "$basedir/node_modules/printversion/main.js" "$@" 15 | fi 16 | -------------------------------------------------------------------------------- /src/fixtures/22.1.0/printversion.cmd: -------------------------------------------------------------------------------- 1 | @ECHO off 2 | GOTO start 3 | :find_dp0 4 | SET dp0=%~dp0 5 | EXIT /b 6 | :start 7 | SETLOCAL 8 | CALL :find_dp0 9 | 10 | IF EXIST "%dp0%\node.exe" ( 11 | SET "_prog=%dp0%\node.exe" 12 | ) ELSE ( 13 | SET "_prog=node" 14 | SET PATHEXT=%PATHEXT:;.JS;=;% 15 | ) 16 | 17 | endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\node_modules\printversion\main.js" %* 18 | -------------------------------------------------------------------------------- /src/fixtures/22.1.0/printversion.ps1: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pwsh 2 | $basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent 3 | 4 | $exe="" 5 | if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) { 6 | # Fix case when both the Windows and Linux builds of Node 7 | # are installed in the same directory 8 | $exe=".exe" 9 | } 10 | $ret=0 11 | if (Test-Path "$basedir/node$exe") { 12 | # Support pipeline input 13 | if ($MyInvocation.ExpectingInput) { 14 | $input | & "$basedir/node$exe" "$basedir/node_modules/printversion/main.js" $args 15 | } else { 16 | & "$basedir/node$exe" "$basedir/node_modules/printversion/main.js" $args 17 | } 18 | $ret=$LASTEXITCODE 19 | } else { 20 | # Support pipeline input 21 | if ($MyInvocation.ExpectingInput) { 22 | $input | & "node$exe" "$basedir/node_modules/printversion/main.js" $args 23 | } else { 24 | & "node$exe" "$basedir/node_modules/printversion/main.js" $args 25 | } 26 | $ret=$LASTEXITCODE 27 | } 28 | exit $ret 29 | -------------------------------------------------------------------------------- /src/fixtures/not_node/not_node: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") 3 | 4 | case `uname` in 5 | *CYGWIN*|*MINGW*|*MSYS*) 6 | if command -v cygpath > /dev/null 2>&1; then 7 | basedir=`cygpath -w "$basedir"`;; 8 | fi 9 | esac 10 | 11 | if [ -x "$basedir/python" ]; then 12 | exec "$basedir/python" "$basedir/node_modules/not_node/main.js" "$@" 13 | else 14 | exec python "$basedir/node_modules/not_node/main.js" "$@" 15 | fi 16 | -------------------------------------------------------------------------------- /src/fixtures/not_node/not_node.cmd: -------------------------------------------------------------------------------- 1 | @ECHO off 2 | GOTO start 3 | :find_dp0 4 | SET dp0=%~dp0 5 | EXIT /b 6 | :start 7 | SETLOCAL 8 | CALL :find_dp0 9 | 10 | IF EXIST "%dp0%\python.exe" ( 11 | SET "_prog=%dp0%\python.exe" 12 | ) ELSE ( 13 | SET "_prog=python" 14 | SET PATHEXT=%PATHEXT:;.JS;=;% 15 | ) 16 | 17 | endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\node_modules\not_node\main.js" %* 18 | -------------------------------------------------------------------------------- /src/fixtures/not_npm/not_npm.cmd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ehmicky/nvexeca/d0ed92ef53f067e5b52d852d9d4528a1ca6173f1/src/fixtures/not_npm/not_npm.cmd -------------------------------------------------------------------------------- /src/fixtures/nvmrc/.nvmrc: -------------------------------------------------------------------------------- 1 | 16.0.0 2 | -------------------------------------------------------------------------------- /src/fixtures/package/bin.test.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | // eslint-disable-next-line no-restricted-globals, no-console, n/prefer-global/process 4 | console.log(process.version) 5 | -------------------------------------------------------------------------------- /src/fixtures/package/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "package", 3 | "version": "1.0.0", 4 | "bin": "bin.test.js" 5 | } 6 | -------------------------------------------------------------------------------- /src/fixtures/package_scripts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "package", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "test": "node --version" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/fixtures/sibling_dir/sibling_dir.cmd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ehmicky/nvexeca/d0ed92ef53f067e5b52d852d9d4528a1ca6173f1/src/fixtures/sibling_dir/sibling_dir.cmd -------------------------------------------------------------------------------- /src/fixtures/sibling_dir/sibling_dir/empty: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ehmicky/nvexeca/d0ed92ef53f067e5b52d852d9d4528a1ca6173f1/src/fixtures/sibling_dir/sibling_dir/empty -------------------------------------------------------------------------------- /src/fixtures/unix/node_modules/printversion/main.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | // eslint-disable-next-line no-restricted-globals, no-console, n/prefer-global/process 4 | console.log(process.version) 5 | -------------------------------------------------------------------------------- /src/fixtures/unix/printversion: -------------------------------------------------------------------------------- 1 | node_modules/printversion/main.js -------------------------------------------------------------------------------- /src/helpers/copy.test.js: -------------------------------------------------------------------------------- 1 | import { delimiter } from 'node:path' 2 | import { env } from 'node:process' 3 | 4 | import pathKey from 'path-key' 5 | 6 | import { TEST_VERSION } from './versions.test.js' 7 | 8 | import nvexeca from 'nvexeca' 9 | 10 | const PATH = pathKey() 11 | 12 | export const runPrint = async (t, pathParts, execaOptions) => { 13 | await run({ 14 | t, 15 | pathParts, 16 | execaOptions, 17 | version: TEST_VERSION, 18 | command: 'printversion', 19 | args: [], 20 | }) 21 | } 22 | 23 | // Run nvexeca while appending some `pathParts` to the `PATH` environment 24 | // variable. Then verify the command (usually `printversion` which prints 25 | // `process.version`) output is showing the correct Node.js version. 26 | // This is used to check that Windows binaries are using the right Node.js 27 | // version. 28 | export const run = async ({ 29 | t, 30 | pathParts, 31 | execaOptions, 32 | version, 33 | command, 34 | args, 35 | }) => { 36 | const pathEnv = [...pathParts, env[PATH]].join(delimiter) 37 | const { childProcess } = await nvexeca(version, command, args, { 38 | env: { [PATH]: pathEnv }, 39 | ...execaOptions, 40 | }) 41 | const { stdout } = await childProcess 42 | 43 | t.is(stdout, `v${version}`) 44 | } 45 | 46 | export const runThrows = async (t, execaOptions) => { 47 | const { childProcess } = await nvexeca( 48 | TEST_VERSION, 49 | 'printversion', 50 | execaOptions, 51 | ) 52 | await t.throwsAsync(childProcess) 53 | } 54 | -------------------------------------------------------------------------------- /src/helpers/copy_fork.test.js: -------------------------------------------------------------------------------- 1 | import { version } from 'node:process' 2 | 3 | import nvexeca from 'nvexeca' 4 | 5 | nvexeca(version, 'printversion', { stdio: 'inherit' }) 6 | -------------------------------------------------------------------------------- /src/helpers/deep.test.js: -------------------------------------------------------------------------------- 1 | import { HELPER_VERSION } from './versions.test.js' 2 | 3 | import nvexeca from 'nvexeca' 4 | 5 | nvexeca(HELPER_VERSION, 'node', ['--version'], { stdio: 'inherit' }) 6 | -------------------------------------------------------------------------------- /src/helpers/fork.test.js: -------------------------------------------------------------------------------- 1 | import { spawn } from 'node:child_process' 2 | import { argv } from 'node:process' 3 | 4 | const [command, ...args] = argv.slice(2) 5 | spawn(command, args, { stdio: 'inherit' }) 6 | -------------------------------------------------------------------------------- /src/helpers/versions.test.js: -------------------------------------------------------------------------------- 1 | export const TEST_VERSION = '16.0.0' 2 | export const HELPER_VERSION = '18.18.0' 3 | export const ALIAS_VERSION = 'lts' 4 | export const INVALID_VERSION = 'invalid_version' 5 | -------------------------------------------------------------------------------- /src/main.d.ts: -------------------------------------------------------------------------------- 1 | import type { Options as ExecaOptions, ResultPromise } from 'execa' 2 | import type { Options as GetNodeOptions, SemverVersion } from 'get-node' 3 | 4 | export interface ProcessInfo { 5 | /** 6 | * [`childProcess` instance](https://nodejs.org/api/child_process.html#child_process_class_childprocess). 7 | * It is also a `Promise` resolving or rejecting with a 8 | * [`Result`](https://github.com/sindresorhus/execa/blob/main/docs/api.md#result). 9 | * The `Promise` should be awaited if you want to wait for the process to 10 | * complete. 11 | * This is `undefined` when the `dry` option is `true`. 12 | */ 13 | childProcess?: ResultPromise 14 | 15 | /** 16 | * Node.js version passed as input, such as `"v10"`. 17 | */ 18 | versionRange: string 19 | 20 | /** 21 | * Normalized Node.js version 22 | * For example if `"v10"` was passed as input, `version` will be `"10.17.0"`. 23 | */ 24 | version: SemverVersion 25 | 26 | /** 27 | * File or command that was executed. 28 | */ 29 | command: string 30 | 31 | /** 32 | * Arguments that were passed to the `command`. 33 | */ 34 | args: string[] 35 | 36 | /** 37 | * [Options](https://github.com/sindresorhus/execa/blob/main/docs/api.md#options) 38 | * that were passed to [Execa](https://github.com/sindresorhus/execa). 39 | */ 40 | execaOptions: ExecaOptions 41 | } 42 | 43 | /** 44 | * All Execa options are available. Please refer to Execa for the list of 45 | * [possible options](https://github.com/sindresorhus/execa/blob/main/docs/api.md#options). 46 | * The 47 | * [`preferLocal` option](https://github.com/sindresorhus/execa/blob/main/docs/api.md#optionspreferlocal) 48 | * is always `true`. 49 | */ 50 | export type Options = 51 | | ExecaOptions 52 | | Partial<{ 53 | /** 54 | * Do not execute the command. 55 | * This can be used to cache the initial Node.js binary download. 56 | * 57 | * @default false 58 | */ 59 | dry: boolean 60 | 61 | /** 62 | * Whether to show a progress bar. 63 | * 64 | * @default false 65 | */ 66 | progress: GetNodeOptions['progress'] 67 | 68 | /** 69 | * Base URL to retrieve Node.js binaries. 70 | * Can be customized (for example `https://npmmirror.com/mirrors/node`). 71 | * The following environment variables can also be used: `NODE_MIRROR`, 72 | * `NVM_NODEJS_ORG_MIRROR`, `N_NODE_MIRROR` or `NODIST_NODE_MIRROR`. 73 | * 74 | * @default "https://nodejs.org/dist" 75 | */ 76 | mirror: GetNodeOptions['mirror'] 77 | 78 | /** 79 | * The list of available Node.js versions is cached for one hour by default. 80 | * If the `fetch` option is: 81 | * - `true`: the cache will not be used 82 | * - `false`: the cache will be used even if it's older than one hour 83 | * 84 | * @default undefined 85 | */ 86 | fetch: GetNodeOptions['fetch'] 87 | 88 | /** 89 | * Node.js binary's CPU architecture. This is useful for example when you're 90 | * on x64 but would like to run Node.js x32. 91 | * All the values from 92 | * [`process.arch`](https://nodejs.org/api/process.html#process_process_arch) 93 | * are allowed except `mips` and `mipsel`. 94 | * 95 | * @default `process.arch` 96 | */ 97 | arch: GetNodeOptions['arch'] 98 | 99 | /** 100 | * Current working directory of the child process. 101 | * When using the `local` alias, start looking for a Node.js version file 102 | * from this directory. 103 | * 104 | * @default "." 105 | */ 106 | cwd: GetNodeOptions['cwd'] 107 | }> 108 | 109 | /** 110 | * Executes `command ...args` with a specific Node.js `versionRange`. 111 | * 112 | * @example 113 | * ```js 114 | * const { childProcess, versionRange, version } = await nvexeca('8', 'node', [ 115 | * '--version', 116 | * ]) 117 | * console.log(`Node ${versionRange} (${version})`) // Node 8 (8.16.2) 118 | * const { exitCode, stdout, stderr } = await childProcess 119 | * console.log(`Exit code: ${exitCode}`) // 0 120 | * console.log(stdout) // v8.16.2 121 | * ``` 122 | */ 123 | // eslint-disable-next-line @typescript-eslint/max-params 124 | export default function nvexeca( 125 | /** 126 | * This can be: 127 | * - any [version range](https://github.com/npm/node-semver) such as `12`, 128 | * `12.6.0` or `<12` 129 | * - `latest`: Latest available Node version 130 | * - `lts`: Latest LTS Node version 131 | * - `global`: Global Node version 132 | * - Using the home directory 133 | * [`.nvmrc`](https://github.com/nvm-sh/nvm#nvmrc) or 134 | * [`package.json` (`engines.node` field)](https://docs.npmjs.com/files/package.json#engines) 135 | * - [Some similar files](https://github.com/ehmicky/preferred-node-version/blob/main/README.md) 136 | * used by other Node.js version managers are also searched for 137 | * - If nothing is found, defaults to the current process's Node version 138 | * - `local`: Current directory's Node version 139 | * - Using the current directory or parent directories 140 | * [`.nvmrc`](https://github.com/nvm-sh/nvm#nvmrc), 141 | * [`package.json` (`engines.node` field)](https://docs.npmjs.com/files/package.json#engines) 142 | * or 143 | * [similar files](https://github.com/ehmicky/preferred-node-version/blob/main/README.md) 144 | * - Defaults to the `global` version 145 | * - a file path towards a [`.nvmrc`](https://github.com/nvm-sh/nvm#nvmrc), 146 | * [`package.json` (`engines.node` field)](https://docs.npmjs.com/files/package.json#engines) 147 | * or 148 | * [similar files](https://github.com/ehmicky/preferred-node-version/blob/main/README.md) 149 | */ 150 | versionRange: string, 151 | 152 | /** 153 | * File or command to execute. Both global and local binaries can be executed. 154 | * Must be compatible with the specific Node `versionRange`. For example `npm` 155 | * is [only compatible with Node `>=6`](https://github.com/npm/cli#important). 156 | */ 157 | command: string, 158 | 159 | /** 160 | * Arguments passed to the `command`. 161 | */ 162 | args: string[], 163 | 164 | options?: Options, 165 | ): Promise 166 | 167 | export default function nvexeca( 168 | versionRange: string, 169 | command: string, 170 | options?: Options, 171 | ): Promise 172 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import { execa } from 'execa' 2 | import getNode from 'get-node' 3 | import { npmRunPathEnv } from 'npm-run-path' 4 | 5 | import { copyBinaries } from './copy/main.js' 6 | import { getOpts } from './options.js' 7 | 8 | // Forwards command to another node instance of a specific `versionRange` 9 | // eslint-disable-next-line max-params 10 | const nvexeca = async (versionRange, command, args, opts) => { 11 | const { 12 | args: argsA, 13 | dry, 14 | getNodeOpts, 15 | execaOptions, 16 | } = getOpts({ versionRange, command, args, opts }) 17 | 18 | const [{ path: nodePath, version }, execaOptionsA] = await Promise.all([ 19 | getNode(versionRange, getNodeOpts), 20 | copyBinaries(execaOptions), 21 | ]) 22 | 23 | const commandA = getCommand(nodePath, command) 24 | const execaOptionsB = getExecaOptions(nodePath, execaOptionsA) 25 | const childProcess = startProcess({ 26 | command: commandA, 27 | args: argsA, 28 | execaOptions: execaOptionsB, 29 | dry, 30 | }) 31 | 32 | return { 33 | childProcess, 34 | version, 35 | versionRange, 36 | command: commandA, 37 | args: argsA, 38 | execaOptions: execaOptionsB, 39 | } 40 | } 41 | 42 | export default nvexeca 43 | 44 | // Some libraries like `spawn-wrap` monkey patch `child_process.spawn()` to 45 | // modify `$PATH` and prepend their own `node` wrapper. We fix it by using the 46 | // `node` absolute path instead of relying on `$PATH`. 47 | // Note that this does not work: 48 | // - with nested child processes 49 | // - with binaries 50 | // This is also slightly faster as it does not require any `$PATH` lookup. 51 | const getCommand = (nodePath, command) => 52 | command === 'node' ? nodePath : command 53 | 54 | // Forward arguments to another node binary located at `nodePath`. 55 | // Fix `$PATH` so that `node` points to the right version. 56 | // We do this instead of directly calling `node` so that: 57 | // - child processes use the same Node.js version 58 | // - binaries work, even on Windows 59 | // We use `execa` `nodePath` for this. 60 | const getExecaOptions = (nodePath, execaOptions) => ({ 61 | ...execaOptions, 62 | ...getEnv(nodePath, execaOptions), 63 | nodePath, 64 | preferLocal: true, 65 | }) 66 | 67 | // `nodePath` requires `node: true`, so we emulate it instead. 68 | // However, we still set `nodePath` it as an Execa option in case `node: true` 69 | // is used. 70 | const getEnv = (nodePath, { extendEnv = true, env }) => ({ 71 | extendEnv: extendEnv || env === undefined, 72 | env: npmRunPathEnv({ 73 | env: env ?? {}, 74 | execPath: nodePath, 75 | preferLocal: false, 76 | }), 77 | }) 78 | 79 | // `signal` cancels downloading the Node.js binary, but not the command 80 | // execution. This allows `nve --parallel` to execute both failing and 81 | // successful commands. However, non-command related failures (like Node.js 82 | // binary download issue) aborts everything. 83 | const startProcess = ({ command, args, execaOptions, dry }) => { 84 | if (dry) { 85 | return 86 | } 87 | 88 | return execa(command, args, execaOptions) 89 | } 90 | -------------------------------------------------------------------------------- /src/main.test-d.ts: -------------------------------------------------------------------------------- 1 | import type { Stream } from 'node:stream' 2 | 3 | import { expectAssignable, expectNotAssignable, expectType } from 'tsd' 4 | 5 | import nvexeca, { type Options, type ProcessInfo } from 'nvexeca' 6 | 7 | await nvexeca('14', 'echo') 8 | // @ts-expect-error 9 | await nvexeca() 10 | // @ts-expect-error 11 | await nvexeca('14') 12 | const NODE_VERSION = 14 13 | // @ts-expect-error 14 | await nvexeca(NODE_VERSION, 'echo') 15 | // @ts-expect-error 16 | await nvexeca('14', true) 17 | 18 | await nvexeca('14', 'echo', []) 19 | await nvexeca('14', 'echo', ['arg']) 20 | // @ts-expect-error 21 | await nvexeca('14', 'echo', true) 22 | // @ts-expect-error 23 | await nvexeca('14', 'echo', [true]) 24 | 25 | await nvexeca('14', 'echo', {}) 26 | await nvexeca('14', 'echo', [], {}) 27 | await nvexeca('14', 'echo', ['arg'], {}) 28 | expectAssignable({}) 29 | // @ts-expect-error 30 | await nvexeca('14', 'echo', true) 31 | // @ts-expect-error 32 | await nvexeca('14', 'echo', [], true) 33 | // @ts-expect-error 34 | await nvexeca('14', 'echo', { unknown: true }) 35 | // @ts-expect-error 36 | await nvexeca('14', 'echo', [], { unknown: true }) 37 | 38 | await nvexeca('14', 'echo', { dry: true }) 39 | expectAssignable({ dry: true }) 40 | // @ts-expect-error 41 | await nvexeca('14', 'echo', { dry: 'true' }) 42 | 43 | await nvexeca('14', 'echo', { progress: true }) 44 | expectAssignable({ progress: true }) 45 | // @ts-expect-error 46 | await nvexeca('14', 'echo', { progress: 'true' }) 47 | 48 | await nvexeca('14', 'echo', { mirror: 'https://example.com' }) 49 | expectAssignable({ mirror: 'https://example.com' }) 50 | // @ts-expect-error 51 | await nvexeca('14', 'echo', { mirror: true }) 52 | 53 | await nvexeca('14', 'echo', { cancelSignal: AbortSignal.abort() }) 54 | expectAssignable({ cancelSignal: AbortSignal.abort() }) 55 | // @ts-expect-error 56 | await nvexeca('14', 'echo', { signal: 'signal' }) 57 | 58 | await nvexeca('14', 'echo', { fetch: true }) 59 | await nvexeca('14', 'echo', { fetch: undefined }) 60 | expectAssignable({ fetch: true }) 61 | // @ts-expect-error 62 | await nvexeca('14', 'echo', { fetch: 'true' }) 63 | 64 | await nvexeca('14', 'echo', { arch: 'x64' }) 65 | expectAssignable({ arch: 'x64' }) 66 | // @ts-expect-error 67 | await nvexeca('14', 'echo', { arch: true }) 68 | // @ts-expect-error 69 | await nvexeca('14', 'echo', { arch: 'unknownArch' }) 70 | 71 | await nvexeca('14', 'echo', { cwd: '.' }) 72 | expectAssignable({ cwd: '.' }) 73 | expectAssignable({ cwd: new URL('file://example.com') }) 74 | // @ts-expect-error 75 | await nvexeca('14', 'echo', { cwd: true }) 76 | 77 | await nvexeca('14', 'echo', { ipc: true }) 78 | expectAssignable({ ipc: true }) 79 | // @ts-expect-error 80 | await nvexeca('14', 'echo', { ipc: 'true' }) 81 | 82 | const processInfo = await nvexeca('14', 'echo') 83 | expectType(processInfo) 84 | const { 85 | childProcess, 86 | versionRange, 87 | version, 88 | command, 89 | args, 90 | execaOptions: { ipc }, 91 | } = processInfo 92 | 93 | expectAssignable(childProcess?.stdout) 94 | expectAssignable(childProcess?.all) 95 | const { isCanceled, exitCode } = await childProcess! 96 | expectType(isCanceled) 97 | expectType(exitCode) 98 | 99 | expectAssignable(version) 100 | expectAssignable(versionRange) 101 | expectAssignable(command) 102 | expectAssignable(args) 103 | expectAssignable(ipc) 104 | expectNotAssignable(ipc) 105 | -------------------------------------------------------------------------------- /src/main.test.js: -------------------------------------------------------------------------------- 1 | import { ChildProcess } from 'node:child_process' 2 | import { rm } from 'node:fs/promises' 3 | import { fileURLToPath } from 'node:url' 4 | 5 | import test from 'ava' 6 | import { execa } from 'execa' 7 | import pathKey from 'path-key' 8 | import semver from 'semver' 9 | import { each } from 'test-each' 10 | 11 | import { run } from './helpers/copy.test.js' 12 | import { 13 | ALIAS_VERSION, 14 | HELPER_VERSION, 15 | TEST_VERSION, 16 | } from './helpers/versions.test.js' 17 | 18 | // eslint-disable-next-line import/max-dependencies 19 | import nvexeca from 'nvexeca' 20 | 21 | const FIXTURES_DIR_URL = new URL('fixtures/', import.meta.url) 22 | const FIXTURES_DIR = fileURLToPath(FIXTURES_DIR_URL) 23 | // `npm install -g` to `os.tmpdir()` throws a `chmod()` error, so we use a local 24 | // directory 25 | const TMP_DIR = fileURLToPath(new URL('helpers/tmp', import.meta.url)) 26 | const FORK_FILE = fileURLToPath( 27 | new URL('helpers/fork.test.js', import.meta.url), 28 | ) 29 | const DEEP_FILE = fileURLToPath( 30 | new URL('helpers/deep.test.js', import.meta.url), 31 | ) 32 | 33 | test('Return normalized Node.js version', async (t) => { 34 | const { version } = await nvexeca(`v${TEST_VERSION}`, 'node', ['--version']) 35 | 36 | t.is(version, TEST_VERSION) 37 | }) 38 | 39 | test('Return non-normalized Node.js version', async (t) => { 40 | const { versionRange } = await nvexeca(`v${TEST_VERSION}`, 'node', [ 41 | '--version', 42 | ]) 43 | 44 | t.is(versionRange, `v${TEST_VERSION}`) 45 | }) 46 | 47 | test('Can use aliases', async (t) => { 48 | const { version } = await nvexeca(ALIAS_VERSION, 'node', ['--version']) 49 | 50 | t.is(semver.clean(version), version) 51 | }) 52 | 53 | test('Can use file paths', async (t) => { 54 | const { version } = await nvexeca(`${FIXTURES_DIR}/nvmrc/.nvmrc`, 'node', [ 55 | '--version', 56 | ]) 57 | 58 | t.is(version, TEST_VERSION) 59 | }) 60 | 61 | test('Can omit arguments but specify options', async (t) => { 62 | const { version } = await nvexeca(`v${TEST_VERSION}`, 'echo', {}) 63 | 64 | t.is(version, TEST_VERSION) 65 | }) 66 | 67 | test('Can omit both arguments and options', async (t) => { 68 | const { version } = await nvexeca(`v${TEST_VERSION}`, 'echo') 69 | 70 | t.is(version, TEST_VERSION) 71 | }) 72 | 73 | test('Returns the modified command', async (t) => { 74 | const { command } = await nvexeca(TEST_VERSION, 'node', ['--version']) 75 | 76 | t.not(command, 'node') 77 | }) 78 | 79 | test('Returns the modified args', async (t) => { 80 | const { args } = await nvexeca(TEST_VERSION, 'node', ['--version']) 81 | 82 | t.deepEqual(args, ['--version']) 83 | }) 84 | 85 | test('Returns the Execa options', async (t) => { 86 | const { 87 | execaOptions: { preferLocal }, 88 | } = await nvexeca(TEST_VERSION, 'node', ['--version']) 89 | 90 | t.true(preferLocal) 91 | }) 92 | 93 | test('Forward child process', async (t) => { 94 | const { childProcess } = await nvexeca(TEST_VERSION, 'node', ['-p', '"test"']) 95 | 96 | t.true(childProcess instanceof ChildProcess) 97 | 98 | const { exitCode, stdout } = await childProcess 99 | t.is(exitCode, 0) 100 | t.is(stdout, 'test') 101 | }) 102 | 103 | test('Dry mode', async (t) => { 104 | const { childProcess } = await nvexeca(TEST_VERSION, 'node', { dry: true }) 105 | 106 | t.true(childProcess === undefined) 107 | }) 108 | 109 | test('Global binaries integration test', async (t) => { 110 | await execa('npm', ['install', '-g', `${FIXTURES_DIR}/package`], { 111 | env: { NPM_CONFIG_PREFIX: TMP_DIR }, 112 | stdio: 'ignore', 113 | }) 114 | await run({ 115 | t, 116 | // Unix installs inside `bin` but not Windows 117 | pathParts: [TMP_DIR, `${TMP_DIR}/bin`], 118 | version: TEST_VERSION, 119 | command: 'package', 120 | args: [], 121 | }) 122 | 123 | await rm(TMP_DIR, { force: true, recursive: true }) 124 | }) 125 | 126 | each( 127 | [ 128 | ['node', '--version'], 129 | ['node', DEEP_FILE], 130 | ['node', FORK_FILE, 'node', '--version'], 131 | ], 132 | [{}, { [pathKey()]: undefined }], 133 | ({ title }, args, env) => { 134 | test(`Works with child processes | ${title}`, async (t) => { 135 | const { childProcess } = await nvexeca( 136 | HELPER_VERSION, 137 | 'node', 138 | [FORK_FILE, ...args], 139 | { env }, 140 | ) 141 | const { stdout } = await childProcess 142 | 143 | t.is(stdout.trim(), `v${HELPER_VERSION}`) 144 | }) 145 | }, 146 | ) 147 | 148 | test('Does not change process.execPath', async (t) => { 149 | // eslint-disable-next-line no-restricted-globals, n/prefer-global/process 150 | const { execPath } = process 151 | await nvexeca(TEST_VERSION, 'node', ['--version']) 152 | 153 | // eslint-disable-next-line no-restricted-globals, n/prefer-global/process 154 | const { execPath: newExecPath } = process 155 | t.is(newExecPath, execPath) 156 | }) 157 | 158 | each( 159 | [ 160 | { stdio: 'ignore', output: undefined }, 161 | { stdio: 'inherit', output: undefined }, 162 | { stdio: 'pipe', output: `v${TEST_VERSION}` }, 163 | { output: `v${TEST_VERSION}` }, 164 | ], 165 | ({ title }, { stdio, output }) => { 166 | test(`Can use stdio | ${title}`, async (t) => { 167 | const { childProcess } = await nvexeca( 168 | TEST_VERSION, 169 | 'node', 170 | ['--version'], 171 | { stdio }, 172 | ) 173 | const { stdout } = await childProcess 174 | 175 | t.is(stdout, output) 176 | }) 177 | }, 178 | ) 179 | 180 | // Serial to avoid "too many open files" on Windows 181 | test.serial('Can fire global binaries', async (t) => { 182 | const { childProcess } = await nvexeca(HELPER_VERSION, 'npm', ['--version']) 183 | const { stdout } = await childProcess 184 | 185 | t.not(stdout, '') 186 | }) 187 | 188 | test.serial('Can fire local binaries', async (t) => { 189 | const { childProcess } = await runWithoutPath({}) 190 | const { stdout } = await childProcess 191 | 192 | t.not(stdout, '') 193 | }) 194 | 195 | test('Can use preferLocal: true (noop)', async (t) => { 196 | const { childProcess } = await runWithoutPath({ preferLocal: true }) 197 | const { stdout } = await childProcess 198 | 199 | t.not(stdout, '') 200 | }) 201 | 202 | test('Can use cwd options for local binaries', async (t) => { 203 | const { childProcess } = await runWithoutPath({ cwd: '/', stdio: 'ignore' }) 204 | const { exitCode } = await t.throwsAsync(childProcess) 205 | 206 | t.not(exitCode, 0) 207 | }) 208 | 209 | // Use `serial` to avoid "too many file open" on Windows 210 | test.serial('Can run in shell mode', async (t) => { 211 | const { childProcess } = await nvexeca( 212 | TEST_VERSION, 213 | 'node --version && node --version', 214 | [], 215 | { shell: true }, 216 | ) 217 | const { exitCode, stdout } = await childProcess 218 | 219 | t.is(exitCode, 0) 220 | t.is(stdout.replace('\r', ''), `v${TEST_VERSION}\nv${TEST_VERSION}`) 221 | }) 222 | 223 | const runWithoutPath = (execaOptions) => 224 | nvexeca(HELPER_VERSION, 'ava', ['--version'], { 225 | env: { [pathKey()]: '' }, 226 | ...execaOptions, 227 | }) 228 | 229 | test.serial('Works with npm scripts', async (t) => { 230 | const { childProcess: nveChildProcess } = await nvexeca( 231 | HELPER_VERSION, 232 | 'npm', 233 | ['--loglevel=silent', 'test'], 234 | { cwd: new URL('package_scripts', FIXTURES_DIR_URL) }, 235 | ) 236 | const { stdout: nveStdout } = await nveChildProcess 237 | t.is(nveStdout, `v${HELPER_VERSION}`) 238 | }) 239 | 240 | test.serial('Can abort the command', async (t) => { 241 | await nvexeca(TEST_VERSION, 'node', { dry: true }) 242 | const { childProcess } = await nvexeca(TEST_VERSION, 'node', ['--version'], { 243 | cancelSignal: AbortSignal.abort(), 244 | }) 245 | const { isCanceled } = await t.throwsAsync(childProcess) 246 | t.true(isCanceled) 247 | }) 248 | 249 | each([undefined, {}], ({ title }, env) => { 250 | test(`Can use extendEnv: false and env: ${title}`, async (t) => { 251 | const { version } = await nvexeca( 252 | `v${TEST_VERSION}`, 253 | 'node', 254 | ['--version'], 255 | { extendEnv: false, env }, 256 | ) 257 | 258 | t.is(version, TEST_VERSION) 259 | }) 260 | }) 261 | -------------------------------------------------------------------------------- /src/options.js: -------------------------------------------------------------------------------- 1 | import isPlainObj from 'is-plain-obj' 2 | 3 | import { validateBasic } from './validate.js' 4 | 5 | // Validate input parameters and assign default values. 6 | export const getOpts = ({ versionRange, command, args, opts }) => { 7 | const { args: argsA, opts: optsA } = parseBasic({ args, opts }) 8 | 9 | validateBasic({ versionRange, command, args: argsA, opts: optsA }) 10 | 11 | const { 12 | dry = false, 13 | progress, 14 | fetch: fetchOpt, 15 | mirror, 16 | arch, 17 | ...execaOptions 18 | } = optsA 19 | const getNodeOpts = { 20 | progress, 21 | fetch: fetchOpt, 22 | mirror, 23 | arch, 24 | signal: execaOptions.cancelSignal, 25 | cwd: execaOptions.cwd, 26 | } 27 | 28 | if (typeof dry !== 'boolean') { 29 | throw new TypeError(`Option "dry" must be a boolean: ${dry}`) 30 | } 31 | 32 | return { args: argsA, dry, getNodeOpts, execaOptions } 33 | } 34 | 35 | // `args` and `opts` are both optional 36 | const parseBasic = ({ args: oArgs, opts: oOpts, args = [], opts = {} }) => 37 | oOpts === undefined && isPlainObj(oArgs) 38 | ? { args: [], opts: oArgs } 39 | : { args, opts } 40 | -------------------------------------------------------------------------------- /src/options.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import { each } from 'test-each' 3 | 4 | import { INVALID_VERSION, TEST_VERSION } from './helpers/versions.test.js' 5 | 6 | import nvexeca from 'nvexeca' 7 | 8 | each( 9 | [ 10 | [], 11 | [TEST_VERSION], 12 | [TEST_VERSION, true], 13 | [TEST_VERSION, 'node', true], 14 | [TEST_VERSION, 'node', [true]], 15 | [TEST_VERSION, 'node', [], true], 16 | [TEST_VERSION, 'node', [], { dry: '' }], 17 | [TEST_VERSION, 'node', [], { fetch: 0 }], 18 | [TEST_VERSION, 'node', [], { arch: true }], 19 | [INVALID_VERSION, 'node'], 20 | ], 21 | ({ title }, args) => { 22 | test(`Invalid arguments | ${title}`, async (t) => { 23 | await t.throwsAsync(nvexeca(...args)) 24 | }) 25 | }, 26 | ) 27 | 28 | each( 29 | [{ cwd: '.' }, { cwd: new URL('.', import.meta.url) }], 30 | ({ title }, opts) => { 31 | test(`Valid arguments | ${title}`, async (t) => { 32 | const { version } = await nvexeca(`v${TEST_VERSION}`, 'echo', opts) 33 | t.is(version, TEST_VERSION) 34 | }) 35 | }, 36 | ) 37 | -------------------------------------------------------------------------------- /src/validate.js: -------------------------------------------------------------------------------- 1 | import isPlainObj from 'is-plain-obj' 2 | 3 | // Validate input parameters 4 | export const validateBasic = ({ versionRange, command, args, opts }) => { 5 | validateRange(versionRange) 6 | 7 | if (typeof command !== 'string') { 8 | throw new TypeError(`Second argument must be a command: ${command}`) 9 | } 10 | 11 | if (!isStringArray(args)) { 12 | throw new TypeError(`Third argument must be an array of strings: ${args}`) 13 | } 14 | 15 | if (!isPlainObj(opts)) { 16 | throw new TypeError(`Last argument must be an options object: ${opts}`) 17 | } 18 | } 19 | 20 | const validateRange = (versionRange) => { 21 | if (typeof versionRange !== 'string') { 22 | throw new TypeError(`Invalid version: ${versionRange}`) 23 | } 24 | } 25 | 26 | const isStringArray = (args) => Array.isArray(args) && args.every(isString) 27 | 28 | const isString = (arg) => typeof arg === 'string' 29 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@ehmicky/dev-tasks/tsconfig.json" 3 | } 4 | --------------------------------------------------------------------------------