Showing preview only (264K chars total). Download the full file or copy to clipboard to get everything.
Repository: coreybutler/nvm-windows
Branch: master
Commit: 5b18223ca19f
Files: 47
Total size: 250.4 KB
Directory structure:
gitextract_df78uor0/
├── .gitattributes
├── .github/
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.yml
│ │ ├── config.yml
│ │ └── feaure_request.yml
│ ├── OLD_ISSUE_TEMPLATE
│ ├── dependabot.yml
│ └── workflows/
│ ├── autotag.yml
│ ├── bot.yml
│ ├── release.yml
│ ├── scanner.yml
│ └── winget.yml
├── .gitignore
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── SUPPORT.md
├── assets/
│ ├── buildtools/
│ │ ├── Default.isl
│ │ ├── ISPPBuiltins.iss
│ │ ├── Setup.e32
│ │ └── SetupLdr.e32
│ ├── elevate.cmd
│ ├── elevate.vbs
│ ├── install.cmd
│ ├── run.cmd
│ ├── setuserenv.vbs
│ └── unsetuserenv.vbs
├── build.js
├── examples/
│ └── settings.txt
├── nvm.iss
└── src/
├── arch/
│ └── arch.go
├── author/
│ └── bridge.go
├── encoding/
│ └── encoding.go
├── file/
│ └── file.go
├── go.mod
├── go.sum
├── manifest.json
├── node/
│ └── node.go
├── nvm.go
├── semver/
│ └── semver.go
├── upgrade/
│ ├── check.go
│ ├── notification.go
│ ├── register.go
│ └── upgrade.go
├── utility/
│ ├── logging.go
│ └── rename.go
└── web/
└── web.go
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitattributes
================================================
* text eol=lf
*.exe binary
*.dll binary
*.e32 binary
================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms
github: [coreybutler]
patreon: # coreybutler
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with a single custom sponsorship URL
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.yml
================================================
name: "🕷️ Bug Report"
description: Report errors or unexpected behavior
title: "[Issue]: "
labels:
- "needs review"
body:
- type: markdown
attributes:
value: |
## Before submitting your issue, please do the following:
1. Make sure you're running Windows. If you're using macOS, Linux, WSL/WSL2, or a Docker container using Linux, then you probably want [nvm-sh](https://github.com/nvm-sh/nvm) (this is NVM _for Windows_).
1. **Run `nvm debug` (v1.1.11+) to see if you can resolve your issue yourself.**
1. Review the **[common issues and resolutions](https://github.com/coreybutler/nvm-windows/wiki/Common-Issues)** wiki page.
1. **Search existing issues to see if your issue has already been opened/closed (help prevent duplicates).**
1. Remember, there is a [wiki](https://github.com/coreybutler/nvm-windows/wiki) that answers questions about installation types, building from source, and project philosophy concerns.
1. **[Read the README](https://github.com/coreybutler/nvm-windows)**. We took the time to write it for you. Many people ignore this.
1. If your issue is actually a question, please use the [discussions page](https://github.com/coreybutler/nvm-windows/discussions) so the whole community can help.
1. If you're seeking commercial help or requesting something you really need _for work/employment_, please consider [sponsoring](https://github.com/sponsors/coreybutler) the change/fix.
- type: textarea
id: issue
attributes:
label: What happened?
description: Please provide a clear and concise description of what happened.
placeholder: Tell us what you see!
validations:
required: true
- type: textarea
id: expected
attributes:
label: What did you expect to happen?
description: Please provide a clear and concise description of what you expected to happen.
placeholder: Tell us what you thought you'd see!
validations:
required: true
- type: dropdown
id: version
attributes:
label: Version
description: What version are you running? Run `nvm version` to find out. **Please don't assume. Check the version.**
options:
- 1.2.0 or newer (Default)
- 1.1.12
- 1.1.11
- 1.1.10
- 1.1.9
- 1.1.8
- 1.1.7
- 1.1.6 or older
validations:
required: true
- type: dropdown
id: os-version
attributes:
label: Which version of Windows?
multiple: true
options:
- Windows 11+ (Default)
- Windows 10
- Windows 8.1
- Windows 8
- Windows 7
- Windows Server 2022
- Windows Server 2019
- Older
- type: dropdown
id: os-language
attributes:
label: Which locale?
description: Are you running a non-English version of Windows?
multiple: false
options:
- English (Default)
- Western European
- Asian
- Other Non-English
- type: dropdown
attributes:
label: Which shell are you running NVM4W in?
options:
- "Command Prompt"
- PowerShell
- WSL or WSL2
- Cygwin
- Mingw64
- ConEmu
- Commander
- Hyper
- Other
multiple: true
- type: dropdown
id: permissions
attributes:
label: User Permissions?
options:
- Administrative Privileges, Elevated
- Administrative Privileges, Non-Elevated
- Standard User, Elevated
- Standard Use, Non-Elevated
- I don't know (Default)
- Other, please describe
validations:
required: true
- type: dropdown
id: developer-mode
attributes:
label: Is Developer Mode enabled?
description: See [Enable your device for development](https://docs.microsoft.com/en-us/windows/apps/get-started/enable-your-device-for-development).
options:
- "Yes"
- No (Default)
- type: textarea
id: logs
attributes:
label: Relevant log/console output
description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
render: Shell
validations:
required: false
- type: textarea
attributes:
label: Debug Output
description: |
If possible, please provide the output of `nvm debug`.
The output of `nvm debug` looks like this
```
<username> is not using admin or elevated rights.
PowerShell
Windows Developer Mode: UNKNOWN (user cannot read registry)
NVM4W Version: 1.2.0
NVM4W Path: C:\Users\username\AppData\Roaming\nvm\nvm.exe
NVM4W Settings: C:\Users\username\AppData\Roaming\nvm\settings.txt
NVM_HOME: C:\Users\uername\AppData\Roaming\nvm
NVM_SYMLINK: C:\Program Files\nodejs
Node Installations: C:\Users\username\AppData\Roaming\nvm
Active Node.js Version: v20.7.0
No problems detected.
Find help at https://github.com/coreybutler/nvm-windows/wiki/Common-Issues
```
If you only see the standard help menu (as shown below), then you are probably running v1.1.10 or below. Upgrade to NVM4W 1.1.11+.
```sh
Running version 1.1.12.
Usage:
nvm arch : Show if node is running in 32 or 64 bit mode.
nvm current : Display active version.
nvm debug : Check the NVM4W process for known problems (troubleshooter).
nvm install <version> [arch] : The version can be a specific version, "latest" for the latest current version, or "lts" for the
most recent LTS version. Optionally specify whether to install the 32 or 64 bit version (defaults
to system arch). Set [arch] to "all" to install 32 AND 64 bit versions.
Add --insecure to the end of this command to bypass SSL validation of the remote download server.
nvm list [available] : List the node.js installations. Type "available" at the end to see what can be installed. Aliased as ls.
nvm on : Enable node.js version management.
nvm off : Disable node.js version management.
```
render: Shell
validations:
required: true
- type: textarea
id: misc
attributes:
label: Anything else?
description: |
Links? References? Anything that will give us more context about the issue you are encountering!
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
validations:
required: false
================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
- name: "\u003F General Question"
url: https://github.com/coreybutler/nvm-windows/discussions
about: Discuss features or general questions
================================================
FILE: .github/ISSUE_TEMPLATE/feaure_request.yml
================================================
name: "⭐ Feature or enhancement request"
title: "[Feature]: "
description: Propose something new.
labels:
- "needs review"
- "enhancement request"
body:
- type: markdown
attributes:
value: |
**Be aware that NVM for Windows feature development is currently [frozen](https://github.com/coreybutler/nvm-windows/wiki/Runtime#nvm4w-feature-freeze).** We are actively developing [Runtime](https://github.com/coreybutler/nvm-windows/wiki/Runtime), a new JavaScript runtime manager to succeed NVM4W.
Feature requests submitted here will be considered for Runtime. For more information, see the [new features explanation](https://github.com/coreybutler/nvm-windows/wiki/New-Features).
If you are requesting an nvm-sh feature, please recognize that it is a different project from NVM4W (nvm-windows). For more about feature parity, see [Why Aren't Some nvm for macOS/Linux Features Supported?](https://github.com/coreybutler/nvm-windows/wiki/Common-Issues#why-isnt-nvmrc-supported-why-arent-some-nvm-for-macoslinux-features-supported)
- type: textarea
attributes:
label: Description of the new feature / enhancement
placeholder: What is the expected behavior of the proposed feature?
validations:
required: true
- type: textarea
attributes:
label: Scenario when this would be used?
placeholder: What is the scenario this would be used? Why is this important to your workflow as a power user?
validations:
required: true
- type: textarea
attributes:
label: Supporting information
placeholder: "Having additional evidence, data, tweets, blog posts, research, ... anything is extremely helpful. This information provides context to the scenario that may otherwise be lost."
validations:
required: false
- type: markdown
attributes:
value: Please limit one request per issue.
================================================
FILE: .github/OLD_ISSUE_TEMPLATE
================================================
<!--
👋 WELCOME
===============================================================================
PLEASE, pretty please, make sure your issue hasn't already been addressed.
===============================================================================
Here's how:
- Read the README to be aware of npm gotchas & antivirus issues.
- Review the wiki at https://github.com/coreybutler/nvm-windows/wiki
- Verify you're running NVM4W with a user account that has administrative privileges.
- Search the issues (open and closed) to make sure this isn't a duplicate.
- Review the Discussion board.
--------------------------------------------------------------------------------
| If this isn't a bug report, please use the discussion feature instead! |
--------------------------------------------------------------------------------
NOTICE:
Windows 7 is not technically supported. This software may work on
Windows 7, but no fixes will be made for it. Microsoft ended EXTENDED support
for Windows 7 on January 14, 2020. There is a note about Windows 7 in the wiki.
Only the standard terminal app and Powershell are supported. Non-standard shell
environments (Cmder, Hyper, Cygwin, git) often manipulate file paths and other
features. While an attempt is made to make NVM4W work in as many environments
as possible, we cannot realistically test every non-standard shell.
💬 Failure to fill in the basic questions may result in slow/no response or
dismissal of the issue. This project has had too many empty issues with nothing
more than a title and we cannot guess about your environment. Save everyone some
time and provide answers that will help the project maintainer(s) isolate and
fix the issue.
-->
**NVM4W version:** <!-- Run `nvm version` to find out. -->
### My Environment
<!--
💬 Please provide only the environments you're experiencing a problem with.
Example:
- Windows 8
- Windows 8.1
- Windows 10
- Windows 10 IoT Core
- Windows Server 2012
- Windows Server 2012 R2
- Windows Server 2016
🤔 If your Windows installation is non-English, please mention it.
-->
### Expected Behavior
<!-- 💬 Uncomment and fill me in... -->
### Actual Behavior
<!-- 💬 Uncomment and fill me in... -->
### Steps to reproduce the problem:
<!-- 💬 Uncomment and fill me in... -->
================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
================================================
FILE: .github/workflows/autotag.yml
================================================
name: Autotag
on:
push:
branches:
- main
- master
jobs:
autotag:
runs-on: ubuntu-latest
outputs:
tag_name: ${{ steps.create_tag.outputs.tag_name}}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Identify version
id: read_version
run: |
cd ./src
echo identifying version...
VERSION=$(jq -r '.version' manifest.json)
echo "version=$VERSION" >> $GITHUB_OUTPUT
cd ../
- name: Check if tag exists
id: check_tag
run: |
VERSION=${{ steps.read_version.outputs.version }}
if git rev-parse "$VERSION" >/dev/null 2>&1; then
echo "Tag $VERSION already exists."
echo "tag_exists=true" >> $GITHUB_OUTPUT
else
echo "Tag $VERSION does not exist."
echo "tag_exists=false" >> $GITHUB_OUTPUT
fi
- name: Import GPG Key
uses: crazy-max/ghaction-import-gpg@v6.2.0
with:
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
passphrase: ${{ secrets.GPG_PASSPHRASE }}
git_tag_gpgsign: true
git_user_signingkey: true
- name: Create tag
if: success() && steps.check_tag.outputs.tag_exists == 'false'
id: create_tag
run: |
VERSION=${{ steps.read_version.outputs.version }}
git tag -s "$VERSION" -m "Release $VERSION"
git push origin "$VERSION"
echo "$VERSION tag created."
================================================
FILE: .github/workflows/bot.yml
================================================
name: 'Close stale issues and PR'
on:
schedule:
- cron: '30 1 * * *'
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9
with:
stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 7 days.'
stale-pr-message: 'This PR is stale because it has been open 45 days with no activity.'
close-issue-message: 'This issue was closed because it has been stalled for 7 days with no activity.'
days-before-stale: 30
days-before-close: 7
days-before-pr-close: -1
start-date: '2021-09-15T05:00:00Z'
exempt-all-assignees: true
exempt-all-milestones: true
exempt-issue-labels: 'bug,not stale,help wanted,consider for rt'
================================================
FILE: .github/workflows/release.yml
================================================
name: Release
on:
workflow_run:
workflows: ["Autotag"]
types:
- completed
jobs:
build:
runs-on: windows-latest
if: ${{ github.event.workflow_run.conclusion == 'success' }}
permissions:
id-token: write
contents: write
attestations: write
env:
GO_VERSION: 1.23
QUIKGO_VERSION: 1.2.6
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
AUTHOR_BRIDGE_TOKEN: ${{ secrets.AUTHOR_BRIDGE_TOKEN }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Extract Tag from Event
id: extract_tag
shell: pwsh
run: |
Write-Host "Event payload:"
Get-Content $env:GITHUB_EVENT_PATH
# Extract the commit SHA from the event payload
$COMMIT_SHA = (Get-Content -Raw $env:GITHUB_EVENT_PATH | ConvertFrom-Json).workflow_run.head_commit.id
if (-not $COMMIT_SHA) {
Write-Host "Error: No commit SHA found in the event payload."
exit 1
}
Write-Host "Commit SHA: $COMMIT_SHA"
# Fetch tags and find the tag associated with the commit
git fetch --tags
$TAG_REF = git tag --contains $COMMIT_SHA | ForEach-Object { $_.Trim() } | Select-Object -First 1
if (-not $TAG_REF) {
Write-Host "Error: No tag found for this commit."
exit 1
}
Write-Host "Extracted tag reference: $TAG_REF"
echo "TAG=$TAG_REF" | Out-File -Append -FilePath $env:GITHUB_ENV
- name: Display Tag
shell: pwsh
run: |
Write-Host "Identified Tag: $env:TAG"
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '${{ env.GO_VERSION }}'
- name: Cache Go Modules
uses: actions/cache@v4
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Cache QuikGo
uses: actions/cache@v4
with:
path: |
~/go/bin
key: ${{ runner.os }}-qgo-v${{ env.QUIKGO_VERSION }}
restore-keys: |
${{ runner.os }}-qgo-
- name: Install QuikGo
run: |
if (-not (go list -m github.com/quikdev/go/cmd/qgo@v${{ env.QUIKGO_VERSION }})) {
if (-not (go list -m github.com/quikdev/go/cmd/qgo@v${{ env.QUIKGO_VERSION }})) {
go install github.com/quikdev/go/cmd/qgo@v${{ env.QUIKGO_VERSION }}
} else {
echo "QuikGo version ${{ env.QUIKGO_VERSION }} already installed."
}
} else {
echo "QuikGo version ${{ env.QUIKGO_VERSION }} already installed."
}
- name: Confirm QuikGo Installation
run: qgo --version
- name: Build NVM for Windows
run: |
cd src
qgo build --profile=release
echo ${{ github.workspace }}\bin
dir ${{ github.workspace }}\bin
cd ..\
# - name: Compress Executables
# uses: crazy-max/ghaction-upx@v3
# with:
# version: latest
# files: |
# ${{ github.workspace }}/bin/*.exe
# args: --brute
- name: Download Resource Hacker
run: |
Invoke-WebRequest -Uri "https://www.angusj.com/resourcehacker/resource_hacker.zip" -OutFile "resource_hacker.zip"
Expand-Archive -Path "resource_hacker.zip" -DestinationPath "$env:USERPROFILE\resource_hacker"
- name: Apply icon to executable
run: |
$resourceHackerPath = "$env:USERPROFILE\resource_hacker\ResourceHacker.exe"
$exe = "${{ github.workspace }}\bin\nvm.exe"
$iconFile = "${{ github.workspace }}\assets\nvm.ico"
& $resourceHackerPath -open $exe -save $exe -action add -res $iconFile -mask ICONGROUP,MAINICON
- name: Sign Executables
uses: azure/trusted-signing-action@v0.5.0
with:
azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }}
azure-client-id: ${{ secrets.AZURE_CLIENT_ID }}
azure-client-secret: ${{ secrets.AZURE_CLIENT_SECRET }}
endpoint: ${{ secrets.AZURE_ENDPOINT }}
trusted-signing-account-name: ${{ secrets.AZURE_CODE_SIGNING_NAME }}
certificate-profile-name: ${{ secrets.AZURE_CERT_PROFILE_NAME }}
files-folder: ${{ github.workspace }}\bin
files-folder-filter: exe,dll
file-digest: SHA256
timestamp-rfc3161: http://timestamp.acs.microsoft.com
timestamp-digest: SHA256
- name: Identify & Download Latest Author-NVM Bridge App (Using GitHub API)
id: get_release
run: |
$repoOwner = "author"
$repoName = "author-nvm"
$token = $env:AUTHOR_BRIDGE_TOKEN # Ensure the token is provided as a secret
# Fetch the latest release information
Write-Host "GET https://api.github.com/repos/$repoOwner/$repoName/releases"
$response = Invoke-RestMethod -Uri "https://api.github.com/repos/$repoOwner/$repoName/releases" `
-Headers @{ Authorization = "token $token"; Accept = "application/vnd.github.v3+json"; }
if ($response -and $response[0].tag_name) {
$releaseTag = $response[0].tag_name
Write-Host "Latest release tag identified: $releaseTag"
# Fetch the release details for the identified tag
Write-Host "GET https://api.github.com/repos/$repoOwner/$repoName/releases/tags/$releaseTag"
$releaseDetails = Invoke-RestMethod -Uri "https://api.github.com/repos/$repoOwner/$repoName/releases/tags/$releaseTag" `
-Headers @{ Authorization = "token $token"; Accept = "application/vnd.github.v3+json"; }
# Extract asset ID for author-nvm.exe
$asset = $releaseDetails.assets | Where-Object { $_.name -eq 'author-nvm.exe' } | Select-Object -First 1
if ($asset -and $asset.id) {
$assetId = $asset.id
Write-Host "Asset ID for author-nvm.exe: $assetId"
"ASSET_ID=$assetId" | Out-File -Append -FilePath $env:GITHUB_ENV
# Download asset
$assetDownloadUrl = "https://api.github.com/repos/$repoOwner/$repoName/releases/assets/$assetId"
Write-Host "GET $assetDownloadUrl"
Invoke-WebRequest -Uri $assetDownloadUrl `
-Headers @{ Authorization = "token $token"; Accept = "application/octet-stream"; } `
-OutFile "$env:GITHUB_WORKSPACE\bin\author-nvm.exe"
} else {
Write-Error "author-nvm.exe not found in the latest release."
exit 1
}
} else {
Write-Error "No release tags found in the repository."
exit 1
}
- name: Generate Core Assets
run: |
$bin = "${{ github.workspace }}\bin"
$license = "${{ github.workspace }}\LICENSE"
$source = "${{ github.workspace }}\assets"
$target = "${{ github.workspace }}\.tmp"
$distros = "${{ github.workspace }}\.dist"
if (!(Test-Path -Path $target)) {
New-Item -ItemType Directory -Path $target
}
# Copy binaries to distribution
Get-ChildItem -Path $bin -File | ForEach-Object {
Copy-Item -Path $_.FullName -Destination $target
}
# Copy assets to distribution
Get-ChildItem -Path $source -File | ForEach-Object {
Copy-Item -Path $_.FullName -Destination $target
}
# Copy license to distribution
Copy-Item -Path $license -Destination $target
shell: pwsh
- name: Generate Asset Distribution
run: |
$source = "${{ github.workspace }}\.tmp"
$manual = "${{ github.workspace }}\.dist\nvm-noinstall.zip"
$checksum = "${{ github.workspace }}\.dist\nvm-noinstall.zip.checksum.txt"
if (Test-Path -Path $manual) {
Remove-Item -Path $manual -Force
}
& "C:\Program Files\7-Zip\7z.exe" a -tzip $manual "$source\*" -mx=9
$hash = Get-FileHash -Path $manual -Algorithm MD5
$hash.Hash | Out-File -FilePath $checksum -Encoding utf8
shell: pwsh
# - name: Install Inno Setup
# run: |
# choco install innosetup -y
# # Verify installation
# if (!(Test-Path "C:\Program Files (x86)\Inno Setup 6\ISCC.exe")) {
# Write-Error "Inno Setup installation failed."
# exit 1
# }
- name: Generate Installer
run: |
$iss = "${{ github.workspace }}\nvm.iss"
$distros = "${{ github.workspace }}\.dist"
if (!(Test-Path -Path $iss)) {
Write-Error "Inno Setup Script file not found: $iss"
exit 1
}
# Replace version placeholder in the .iss file
$version = $env:TAG -replace "^v", ""
(Get-Content -Path $iss) -replace "{{VERSION}}", $version | Set-Content -Path $iss
# Output the file to assure replacements worked
Get-Content $iss
# Get the files and directories in the source directory
$items = Get-ChildItem -Path $source -Recurse
$source = "${{ github.workspace }}\.tmp"
$destination = "${{ github.workspace }}\bin"
foreach ($item in $items) {
# Construct the target path
$target = $item.FullName -replace [regex]::Escape($source), [regex]::Escape($destination)
if ($item.PSIsContainer) {
# Create directories if they don't exist
if (-not (Test-Path -Path $target)) {
New-Item -ItemType Directory -Path $target
}
} else {
# Copy files if they don't already exist
if (-not (Test-Path -Path $target)) {
Copy-Item -Path $item.FullName -Destination $target
}
}
}
Write-Host "Available assets in bin directory:"
dir ${{ github.workspace }}\bin
Write-Host "Available assets in .tmp directory:"
dir ${{ github.workspace }}\.tmp
Write-Host "Available build tools:"
dir ${{ github.workspace }}\assets\buildtools
${{ github.workspace }}\assets\buildtools\iscc.exe "$iss" "/o$distros"
# iscc "$iss" "/o$distros"
shell: pwsh
- name: Sign Installer
uses: azure/trusted-signing-action@v0.5.0
with:
azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }}
azure-client-id: ${{ secrets.INSTALLER_AZURE_CLIENT_ID }}
azure-client-secret: ${{ secrets.INSTALLER_AZURE_CLIENT_SECRET }}
endpoint: ${{ secrets.AZURE_ENDPOINT }}
trusted-signing-account-name: ${{ secrets.AZURE_CODE_SIGNING_NAME }}
certificate-profile-name: ${{ secrets.AZURE_CERT_PROFILE_NAME }}
files-folder: ${{ github.workspace }}\.dist
files-folder-filter: exe,dll
file-digest: SHA256
timestamp-rfc3161: http://timestamp.acs.microsoft.com
timestamp-digest: SHA256
- name: Generate Official Distribution
run: |
$source = "${{ github.workspace }}\.dist\nvm-setup.exe"
$distro = "${{ github.workspace }}\.dist\nvm-setup.zip"
$checksum = "${{ github.workspace }}\.dist\nvm-setup.zip.checksum.txt"
if (!(Test-Path -Path $source)) {
Write-Error "Source file for ZIP not found: $source"
exit 1
}
& "C:\Program Files\7-Zip\7z.exe" a -tzip $distro "$source" -mx=9
if (!(Test-Path -Path $distro)) {
Write-Error "ZIP file generation failed."
exit 1
}
$hash = Get-FileHash -Path $distro -Algorithm MD5
$hash.Hash | Out-File -FilePath $checksum -Encoding utf8
shell: pwsh
- name: Attestation
uses: actions/attest-build-provenance@v2
id: attest
with:
subject-path: |
${{ github.workspace }}/bin/*.exe
${{ github.workspace }}/.dist/*.exe
${{ github.workspace }}/.dist/*.zip
${{ github.workspace }}/.dist/*.checksum.txt
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Release
uses: softprops/action-gh-release@v2
if: success()
with:
tag_name: ${{ env.TAG }}
files: |
${{ github.workspace }}/.dist/*
fail_on_unmatched_files: true
generate_release_notes: true
prerelease: false
draft: false
make_latest: true
- name: Add Attestation Report to Release Notes
if: success()
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ env.TAG }}
body: |
<br/>:office: **[Attestation Report](${{ steps.attest.outputs.attestation-url }})**
append_body: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
rollback:
runs-on: ubuntu-latest
needs: build
if: failure()
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0 # Ensures that all references, including tags, are fetched
ref: ${{ github.event.workflow_run.head_sha }}
- name: Show current commit
run: |
git log -1 --decorate
git tag --contains HEAD
- name: Get the commit associated with the trigger
id: get_commit
run: |
if [ -f "$GITHUB_EVENT_PATH" ]; then
# Try to get the head SHA from workflow_run
COMMIT_SHA=$(jq -r '.head_commit.id' < "$GITHUB_EVENT_PATH")
if [ "$COMMIT_SHA" = "null" ]; then
COMMIT_SHA=$(jq -r '.workflow_run.head_commit.id' < "$GITHUB_EVENT_PATH")
fi
if [ -z "$COMMIT_SHA" ] || [ "$COMMIT_SHA" = "null" ]; then
echo "Error: Unable to retrieve commit SHA from GITHUB_EVENT_PATH"
cat "$GITHUB_EVENT_PATH"
echo "HEAD: $(git rev-parse HEAD)"
exit 1
fi
echo "Commit SHA: $COMMIT_SHA"
echo "COMMIT_SHA=$COMMIT_SHA" >> $GITHUB_ENV
else
echo "Error: GITHUB_EVENT_PATH not found"
exit 1
fi
- name: Fetch the tag associated with the commit using git
id: fetch_tag
run: |
# Debugging: print all tags in the repository
git tag
# Get the tag associated with the commit
echo "Finding tag associated with the commit..."
TAG=$(git tag --contains "$COMMIT_SHA" | head -n 1)
echo "Tag associated with the commit is: $TAG"
# Debugging: Check if the tag is found
if [ -z "$TAG" ]; then
echo "No tag found for this commit."
else
echo "Found tag: $TAG"
fi
echo "TAG=$TAG" >> $GITHUB_ENV
- name: Delete release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Use the TAG from the environment variable
if [ -z "$TAG" ]; then
echo "No tag to delete."
exit 1
fi
RELEASE_ID=$(gh release view "$TAG" --json id --jq '.id' || echo "")
if [ -n "$RELEASE_ID" ]; then
gh release delete "$TAG" --yes
echo "Release $TAG deleted successfully."
else
echo "Release $TAG not found."
fi
- name: Delete tag associated with release
run: |
# Use the TAG from the environment variable
if [ -z "$TAG" ]; then
echo "No tag to delete."
exit 1
fi
git tag -d "$TAG"
git push --delete origin "$TAG"
- name: Notify Rollback
run: echo "Rollback completed for release $TAG"
================================================
FILE: .github/workflows/scanner.yml
================================================
name: Virus Scan
on:
workflow_run:
workflows: ["Release"]
types:
- completed
jobs:
scan:
if: ${{ github.event.workflow_run.conclusion }} == 'success'
runs-on: ubuntu-latest
steps:
- name: Get latest release details
id: get_latest_release
uses: actions/github-script@v7
with:
script: |
const { data: latestRelease } = await github.rest.repos.getLatestRelease({
owner: context.repo.owner,
repo: context.repo.repo
});
core.setOutput('tag', latestRelease.tag_name);
core.setOutput('assets', JSON.stringify(latestRelease.assets));
- name: Download release assets
env:
ASSETS_JSON: ${{ steps.get_latest_release.outputs.assets }}
run: |
echo "Assets: $ASSETS_JSON"
for asset in $(echo $ASSETS_JSON | jq -r '.[].browser_download_url'); do
echo "Downloading $asset"
curl -L -O "$asset"
done
- name: Extract Main Executables
run: |
unzip nvm-noinstall.zip -d ./
- name: List downloaded assets
run: ls -alh ./
- name: Scan with VirusTotal
uses: WoozyMasta/virustotal-action@v1.0.0
id: scan
with:
vt_api_key: ${{ secrets.VIRUSTOTAL_API_KEY }}
github_token: ${{ secrets.GITHUB_TOKEN }}
file_globs: |
nvm.exe
author-nvm.exe
nvm-setup.exe
- name: Generate release notes
id: release_notes
run: |
input_list="${{ steps.scan.outputs.results }}"
notes=":bulb: **Antivirus Report**<br/><ul>"
# Process each item in the list
IFS=',' read -ra ITEMS <<< "$input_list"
for item in "${ITEMS[@]}"; do
filename=$(echo "$item" | cut -d'/' -f1)
id=$(echo "$item" | cut -d'/' -f2)
notes="${notes}<li><a href='https://www.virustotal.com/gui/file-analysis/${id}'>${filename}</a></li>"
done
notes="${notes}</ul>"
# Output the result
echo "Generated Notes:"
echo "$notes"
echo "NOTES=$notes" >> $GITHUB_OUTPUT
- name: Update release body
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.get_latest_release.outputs.tag }}
body: |
${{ steps.release_notes.outputs.NOTES }}
append_body: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# - name: Scan with VirusTotal
# uses: crazy-max/ghaction-virustotal@v4
# with:
# vt_api_key: ${{ secrets.VIRUSTOTAL_API_KEY }}
# files: |
# *.exe
# .exe$
# update_release_body: true
# github_token: ${{ secrets.GITHUB_TOKEN }}
# request_rate: 4
================================================
FILE: .github/workflows/winget.yml
================================================
name: Publish to Winget
on:
release:
types: [released]
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: vedantmgoyal2009/winget-releaser@v2
with:
identifier: CoreyButler.NVMforWindows
version: ${{ github.event.release.tag_name }}
installers-regex: 'nvm-setup\.exe$'
token: ${{ secrets.WINGET_TOKEN }}
================================================
FILE: .gitignore
================================================
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
*.old
# Folders
_obj
_test
bin
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
!utilities/**/*.exe
*.test
*.prof
**/.*
dist
src/v*
bin/*.exe
bin/*.log
bin/LICENSE
!bin/buildtools/*
!assets/buildtools/*
bin/*.zip
bin/nvm/*
!.gitignore
!.gitattributes
!.github
tests
================================================
FILE: CONTRIBUTING.md
================================================
## Before you submit an issue....
There have been a lot of duplicate issues filed. Please do a simple search for your issue before posting something new.
If you just have a question and no real problem, please use the [StackOverflow tag](https://stackoverflow.com/questions/tagged/nvm-windows) the [Gitter channel](https://gitter.im/coreybutler/nvm-windows) instead of clogging up the bug tracker with questions.
## Common Issues & Resolutions
See the [Common Issues](https://github.com/coreybutler/nvm-windows/wiki/Common-Issues) wiki page before submitting.
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2025 Author Software Inc.
Copyright (c) 2022 Ecor Ventures LLC.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
<div align="center"><h2>We are working full time on Author, including <a href="https://github.com/coreybutler/nvm-windows/wiki/Runtime">Runtime</a>, the successor to NVM for Windows.</h2>Complete <a href="https://t.co/oGqQCM9FPx">this form</a> to provide your thoughts and sign up for progress updates.<br/><br/>Updates will also be posted on the <A href="https://linkedin.com/company/authorsoftware">Author Software LinkedIn Page</a>.</div>
<br/><br/>
<h1 align="center">NVM for Windows</h1>
<div align="center">
The <a href="https://docs.microsoft.com/en-us/windows/nodejs/setup-on-windows">Microsoft</a>/<a href="https://docs.npmjs.com/cli/v9/configuring-npm/install#windows-node-version-managers">npm</a>/<a href="https://cloud.google.com/nodejs/docs/setup#installing_nvm">Google</a> recommended Node.js version manager for <em>Windows</em>.<br/>
<details>
<summary><b>This is not the same thing as nvm!</b> (expand for details)</summary>
_The original [nvm](https://github.com/nvm-sh/nvm) is a completely separate project for Mac/Linux only._ This project uses an entirely different philosophy and is not just a clone of nvm. Details are listed in [Why another version manager?](#bulb-why-another-version-manager) and [what's the big difference?](#bulb-whats-the-big-difference).
</details>
[](https://github.com/coreybutler/nvm-windows/releases) []((https://github.com/coreybutler/nvm-windows/releases))   [](https://github.com/coreybutler/nvm-windows/discussions) [](https://twitter.com/intent/tweet?hashtags=nodejs&original_referer=http%3A%2F%2F127.0.0.1%3A91%2F&text=Check%20out%20NVM%20for%20Windows!&tw_p=tweetbutton&url=http%3A%2F%2Fgithub.com%2Fcoreybutler%2Fnvm-windows&via=goldglovecb)
<br/>
[](https://github.com/coreybutler/nvm-windows/releases/tag/1.1.12) []((https://github.com/coreybutler/nvm-windows/releases/tag/1.1.12/))
<details>
<summary><b>Latest vs Stable?</b></summary>
The latest version (v1.2.x) is a [transition version]([https://](https://opensource.author.io/nvm-for-windows-v120)) as we complete work on [Runtime](https://github.com/coreybutler/nvm-windows/wiki/Runtime), the successor to NVM for Windows. However; there is a known bug that may impact some users when installing old (EOL) versions of Node.js. The last stable version supports most EOL versions.
Some companies need very specific features of both versions, which is not possible with today's public releases. While we recommend waiting for Runtime (anticipated March release), if you need a private release, contact support@author.io to arrange a paid support contract.
</details>
</div>
<div align="center">
<a href="https://trendshift.io/repositories/4201" target="_blank"><img src="https://trendshift.io/api/badge/repositories/4201" alt="coreybutler%2Fnvm-windows | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
</div>
<h5 align="center">Sponsors</h5>
<div align="center">
<table cellpadding="5" cellspacing="0" border="0" align="center">
<tr>
<td><a href="https://linkedin.com/company/authorsoftware"><img src="https://github.com/coreybutler/staticassets/blob/master/sponsors/logo_author_software_flat.png" width="200px"/></a></td>
<td width="33%" align="center"><a href="https://ecorventures.com"><img src="https://avatars.githubusercontent.com/u/8259581?s=200&v=4" height="30px"/></a></td>
<td width="33%" align="center"><a href="https://github.com/microsoft"><img src="https://user-images.githubusercontent.com/770982/195955265-5c3dca78-7140-4ec6-b05a-f308518643ee.png" height="30px"/></a></td>
</tr>
<tr>
<td colspan="4" align="center">
<a href="https://github.com/sponsors/coreybutler"><img src="https://img.shields.io/github/sponsors/coreybutler?label=Individual%20Sponsors&logo=github&style=social"/></a>
<a href="https://github.com/sponsors/coreybutler"><img src="https://img.shields.io/badge/-Become%20a%20Sponsor-yellow"/></a>
</td>
</tr>
<tr>
<td colspan="4" align="center">
<img src="https://github.blog/wp-content/uploads/2020/09/github-stars-logo_Color.png" width="50"/><br/>
<b>Can't sponsor?</b><br/>Consider <a href="https://stars.github.com/nominate/" target="_blank">nominating @coreybutler for a Github star</a>.
</td>
</tr>
</table>
</div>
<br/>
<div align="center"><b>Running into issues?</b> See the <a href="https://github.com/coreybutler/nvm-windows/wiki/Common-Issues">common issues wiki</a>.</div>
<br/>
<table style="background-color:red;padding:6px;border-radius:3px;">
<tr><td>
<h3>Seeking Feedback:</h3>
We're working on <a href="https://github.com/coreybutler/nvm-windows/wiki/Runtime">Runtime (rt)</a>, the successor to NVM For Windows. Please contribute by taking a minute to complete <a href="https://t.co/oGqQCM9FPx">this form</a>. Thank you!
<h3></h3>
</td></tr>
</table>
## Overview
Manage multiple installations of node.js on a Windows computer.
**tl;dr** Similar (not identical) to [nvm](https://github.com/creationix/nvm), but for Windows. Has an installer. [Download Now](https://github.com/coreybutler/nvm-windows/releases)!
This has always been a node version manager, not an io.js manager, so there is no back-support for io.js. Node 4+ is supported. Remember when running `nvm install` or `nvm use`, Windows usually requires administrative rights (to create symlinks). To install the latest version of Node.js, run `nvm install latest`. To install the latest stable version, run `nvm install lts`.

There are situations where the ability to switch between different versions of Node.js can be very useful. For example, if you want to test a module you're developing with the latest bleeding edge version without uninstalling the stable version of node, this utility can help.

### Installation & Upgrades
#### :star: :star: Uninstall any pre-existing Node installations!! :star: :star:
The simplest (recommended) way to get NVM for Windows running properly is to uninstall any prior Node installation _before_ installing NVM for Windows. It avoids all of the pitfalls listed below. However; you may not wish to nuke your Node installation if you've highly customized it. NVM for Windows _can_ assume management of an existing installation, but there are nuances to this (dependent entirely on the permissions of the user running the installation). If you have an administrative account, it's relatively safe to install NVM for Windows before uninstalling the original Node version. If you are working in a closed environment, such as a corporate Active Directory environment where installations/uninstallations are controlled by group policy, you should really consider removing the original version of Node before installing NVM4W.
_Permission Problems_
For security reasons, Windows will not allow an application from one vendor to "uninstall" an application from a different vendor. The official NVM4W installer will attempt assume management of an existing installation of Node., but it cannot actually uninstall the original Node.js version. To work around this, NVM for Windows installer attempts to copy the original Node.js installation files to the NVM root. This includes global npm modules and configurations. Once this process is complete, the original Node.js installation can be uninstalled without losing data.
_PATH Installation Problems_
If you attempt to configure the `NVM_SYMLINK` to use an existing directory (like `C:\Program Files\nodejs`), it will fail because a symlink cannot overwrite a physical directory. This is not a problem if you choose a different symlink path (such as `C:\nvm\node`).
_PATH Conflicts_
If you do not uninstall the original version, running `nvm use` may appear to do nothing at all. Running `node -v` will always show the original installation version. This is due to a [`PATH` conflict](https://github.com/coreybutler/nvm-windows/wiki/Common-Issues#why-do-i-need-to-uninstall-nodejs-before-installing-nvm-for-windows) that presents when the same application is installed multiple times. In NVM4W 1.1.11+, run `nvm debug` to determine if you have a `PATH` conflict.
For simpliciy, we recommend uninstalling any existing versions of Node.js before using NVM for Windows. Delete any existing Node.js installation directories (e.g., `%ProgramFiles%\nodejs`) that might remain. NVM's generated symlink will not overwrite an existing (even empty) installation directory.
:eyes: **Backup any global `npmrc` config** :eyes:
(e.g. `%AppData%\npm\etc\npmrc`)
Alternatively, copy the settings to the user config `%UserProfile%\.npmrc`. Delete the existing npm install location (e.g. `%AppData%\npm`) to prevent global module conflicts.
#### Install nvm-windows
Use the [latest installer](https://github.com/coreybutler/nvm/releases) (comes with an uninstaller). Alternatively, follow the [manual installation](https://github.com/coreybutler/nvm-windows/wiki#manual-installation) guide.
_If NVM4W doesn't appear to work immediately after installation, restart the terminal/powershell (not the whole computer)._

#### Reinstall any global utilities
After install, reinstalling global utilities (e.g. yarn) will have to be done for each installed version of node:
```
nvm use 14.0.0
npm install -g yarn
nvm use 12.0.1
npm install -g yarn
```
### Upgrading nvm-windows
:bulb: _As of v1.1.8, there is an upgrade utility that will automate the upgrade process._
**To upgrade nvm-windows**, run the new installer. It will safely overwrite the files it needs to update without touching your node.js installations. Make sure you use the same installation and symlink folder. If you originally installed to the default locations, you just need to click "next" on each window until it finishes.
### Usage
**nvm-windows runs in an Admin shell**. You'll need to start `powershell` or Command Prompt as Administrator to use nvm-windows
NVM for Windows is a command line tool. Simply type `nvm` in the console for help. The basic commands are:
- **`nvm arch [32|64]`**: Show if node is running in 32 or 64 bit mode. Specify 32 or 64 to override the default architecture.
- **`nvm debug`**: Check the NVM4W process for known problems.
- **`nvm current`**: Display active version.
- **`nvm install <version> [arch]`**: The version can be a specific version, "latest" for the latest current version, or "lts" for the most recent LTS version. Optionally specify whether to install the 32 or 64 bit version (defaults to system arch). Set [arch] to "all" to install 32 AND 64 bit versions. Add `--insecure` to the end of this command to bypass SSL validation of the remote download server.
- **`nvm list [available]`**: List the node.js installations. Type `available` at the end to show a list of versions available for download.
- **`nvm on`**: Enable node.js version management.
- **`nvm off`**: Disable node.js version management (does not uninstall anything).
- **`nvm proxy [url]`**: Set a proxy to use for downloads. Leave `[url]` blank to see the current proxy. Set `[url]` to "none" to remove the proxy.
- **`nvm uninstall <version>`**: Uninstall a specific version.
- **`nvm use <version> [arch]`**: Switch to use the specified version. Optionally use `latest`, `lts`, or `newest`. `newest` is the latest _installed_ version. Optionally specify 32/64bit architecture. `nvm use <arch>` will continue using the selected version, but switch to 32/64 bit mode. For information about using `use` in a specific directory (or using `.nvmrc`), please refer to [issue #16](https://github.com/coreybutler/nvm-windows/issues/16).
- **`nvm root <path>`**: Set the directory where nvm should store different versions of node.js. If `<path>` is not set, the current root will be displayed.
- **`nvm version`**: Displays the current running version of NVM for Windows.
- **`nvm node_mirror <node_mirror_url>`**: Set the node mirror.People in China can use *https://npmmirror.com/mirrors/node/*
- **`nvm npm_mirror <npm_mirror_url>`**: Set the npm mirror.People in China can use *https://npmmirror.com/mirrors/npm/*
### :warning: Gotcha!
Please note that any global npm modules you may have installed are **not** shared between the various versions of node.js you have installed. Additionally, some npm modules may not be supported in the version of node you're using, so be aware of your environment as you work.
### :name_badge: Antivirus
Users have reported some problems using antivirus, specifically McAfee. It appears the antivirus software is manipulating access to the VBScript engine. See [issue #133](https://github.com/coreybutler/nvm-windows/issues/133) for details and resolution.
**v1.1.8 is not code signed**, but all other versions are signed by [Ecor Ventures LLC](https://ecorventures.com)/[Author.io](https://author.io). This should help prevent false positives with most antivirus software.
> v1.1.8+ was not code signed due to an expired certificate (see the [release notes](https://github.com/coreybutler/nvm-windows/releases/tag/1.1.8) for reasons). **v1.1.9 _is_ code signed** thanks to [ajyong](https://github.com/ajyong), who sponsored the new certificate.
### Using Yarn
**tldr;** `npm i -g yarn`
See the [wiki](https://github.com/coreybutler/nvm-windows/wiki/Common-Issues#how-do-i-use-yarn-with-nvm-windows) for details.
### Build from source
- Install go from http://golang.org
- Download source / Git Clone the repo
- Change GOARCH to amd64 in build.bat if you feel like building a 64-bit executable
- Fire up a Windows command prompt and change directory to project dir
- Execute `go get github.com/blang/semver`
- Execute `go get github.com/olekukonko/tablewriter`
- Execute `build.bat`
- Check the `dist`directory for generated setup program.
---
## :bulb: Why another version manager?
There are several version managers for node.js. Tools like [nvm](https://github.com/creationix/nvm) and [n](https://github.com/tj/n)
only run on Mac OSX and Linux. Windows users are left in the cold? No. [nvmw](https://github.com/hakobera/nvmw) and [nodist](https://github.com/marcelklehr/nodist)
are both designed for Windows. So, why another version manager for Windows?
The architecture of most node version managers for Windows rely on `.bat` files, which do some clever tricks to set or mimic environment variables. Some of them use node itself (once it's downloaded), which is admirable, but prone to problems. Right around node 0.10.30, the installation structure changed a little, causing some of these to just stop working with anything new.
Additionally, some users struggle to install these modules since it requires a little more knowledge of node's installation structure. I believe if it were easier for people to switch between versions, people might take the time to test their code on back and future versions... which is just good practice.
## :bulb: What's the big difference?
First and foremost, this version of nvm has no dependency on node. It's written in [Go](https://golang.org/), which is a much more structured approach than hacking around a limited `.bat` file. It does not rely on having an existing node installation. Go offers the ability to create a Mac/Linux version on the same code base. In fact, this is already underway.
The control mechanism is also quite different. There are two general ways to support multiple node installations with hot switching capabilities. The first is to modify the system `PATH` any time you switch versions, or bypass it by using a `.bat` file to mimic the node executable and redirect accordingly. This always seemed a little hackish to me, and there are some quirks as a result of this implementation.
The second option is to use a symlink. This concept requires putting the symlink in the system `PATH`, then updating its target to the node installation directory you want to use. This is a straightforward approach, and seems to be what people recommend.... until they realize just how much of a pain symlinks are on Windows. This is why it hasn't happened before.
In order to create/modify a symlink, you must be running as an admin, and you must get around Windows UAC (that annoying prompt). Luckily, this is a challenge I already solved with some helper scripts in [node-windows](https://github.com/coreybutler/node-windows). As a result, NVM for Windows maintains a single symlink that is put in the system `PATH` during installation only. Switching to different versions of node is a matter of switching the symlink target. As a result, this utility does **not** require you to run `nvm use x.x.x` every time you open a console window. When you _do_ run `nvm use x.x.x`, the active version of node is automatically updated across all open console windows. It also persists between system reboots, so you only need to use nvm when you want to make a change.
NVM for Windows comes with an installer, courtesy of a byproduct of my work on [Fenix Web Server](https://preview.fenixwebserver.com).
Overall, this project brings together some ideas, a few battle-hardened pieces of other modules, and support for newer versions of node.
NVM for Windows recognizes the "latest" versions using a [list](https://nodejs.org/download/release/index.json) provided by the Node project. Version 1.1.1+ use this list. Before this list existed, I was scraping releases and serving it as a standalone [data feed](https://github.com/coreybutler/nodedistro). This list was used in versions 1.1.0 and prior, but is now deprecated.
## Motivation
I needed it, plain and simple. Additionally, it's apparent that [support for multiple versions](https://github.com/nodejs/node-v0.x-archive/issues/8075) is not coming to node core. It was also an excuse to play with Go.
## Why Go? Why not Node?
I chose Go because it is cross-platform, felt like less overhead than Java, has been around longer than most people think. Plus, I wanted to experiment with it. I've been asked why I didn't write it with Node. Trying to write a tool with the tool you're trying to install doesn't make sense to me. As a result, my project requirements for this were simple... something that's not Node. Node will continue to evolve and change. If you need a reminder of that, remember io.js, Ayo, all the breaking changes between 4.x.x and 6.x.x, and the shift to ES Modules in 12+. Change is inevitable in the world of software. JavaScript is extremely dynamic.
## :pray: Thanks
Thanks to everyone who has submitted issues on and off Github, made suggestions, and generally helped make this a better project. Special thanks to
- [@vkbansal](https://github.com/vkbansal), who provided significant early feedback throughout the early releases.
- [@rainabba](https://github.com/rainabba) and [@sullivanpt](https://github.com/sullivanpt) for getting Node v4 support integrated.
- [@s-h-a-d-o-w](https://github.com/s-h-a-d-o-w) who resolved the longstanding space escaping issue in path names ([#355](https://github.com/coreybutler/nvm-windows/pull/355)).
- [ajyong](https://github.com/ajyong) who sponsored the code signing certificate in late 2021.
<br/>

================================================
FILE: SUPPORT.md
================================================
# Need Help?
If you're running into problems using NVM4W, try the following resources.
1. Review the [wiki](https://github.com/coreybutler/nvm-windows/wiki).
1. Join ongoing [discussions](https://github.com/coreybutler/nvm-windows/discussions).
1. Search [closed issues](https://github.com/coreybutler/nvm-windows/issues?q=is%3Aissue+is%3Aclosed) for existing solutions.
1. Search [StackOverflow ([nvm-windows])](https://stackoverflow.com/questions/tagged/nvm-windows) for answers.
There are also a number of [YouTube videos](https://www.youtube.com/results?search_query=NVM+for+Windows) and blogs online that show how to use NVM for Windows.
If none of these things help and you suspect a bug, file an issue.
You can also try reaching out to [@goldglovecb](https://twitter.com/goldglovecb) on Twitter.
================================================
FILE: assets/buildtools/Default.isl
================================================
; *** Inno Setup version 6.1.0+ English messages ***
;
; To download user-contributed translations of this file, go to:
; https://jrsoftware.org/files/istrans/
;
; Note: When translating this text, do not add periods (.) to the end of
; messages that didn't have them already, because on those messages Inno
; Setup adds the periods automatically (appending a period would result in
; two periods being displayed).
[LangOptions]
; The following three entries are very important. Be sure to read and
; understand the '[LangOptions] section' topic in the help file.
LanguageName=English
LanguageID=$0409
LanguageCodePage=0
; If the language you are translating to requires special font faces or
; sizes, uncomment any of the following entries and change them accordingly.
;DialogFontName=
;DialogFontSize=8
;WelcomeFontName=Verdana
;WelcomeFontSize=12
;TitleFontName=Arial
;TitleFontSize=29
;CopyrightFontName=Arial
;CopyrightFontSize=8
[Messages]
; *** Application titles
SetupAppTitle=Setup
SetupWindowTitle=Setup - %1
UninstallAppTitle=Uninstall
UninstallAppFullTitle=%1 Uninstall
; *** Misc. common
InformationTitle=Information
ConfirmTitle=Confirm
ErrorTitle=Error
; *** SetupLdr messages
SetupLdrStartupMessage=This will install %1. Do you wish to continue?
LdrCannotCreateTemp=Unable to create a temporary file. Setup aborted
LdrCannotExecTemp=Unable to execute file in the temporary directory. Setup aborted
HelpTextNote=
; *** Startup error messages
LastErrorMessage=%1.%n%nError %2: %3
SetupFileMissing=The file %1 is missing from the installation directory. Please correct the problem or obtain a new copy of the program.
SetupFileCorrupt=The setup files are corrupted. Please obtain a new copy of the program.
SetupFileCorruptOrWrongVer=The setup files are corrupted, or are incompatible with this version of Setup. Please correct the problem or obtain a new copy of the program.
InvalidParameter=An invalid parameter was passed on the command line:%n%n%1
SetupAlreadyRunning=Setup is already running.
WindowsVersionNotSupported=This program does not support the version of Windows your computer is running.
WindowsServicePackRequired=This program requires %1 Service Pack %2 or later.
NotOnThisPlatform=This program will not run on %1.
OnlyOnThisPlatform=This program must be run on %1.
OnlyOnTheseArchitectures=This program can only be installed on versions of Windows designed for the following processor architectures:%n%n%1
WinVersionTooLowError=This program requires %1 version %2 or later.
WinVersionTooHighError=This program cannot be installed on %1 version %2 or later.
AdminPrivilegesRequired=You must be logged in as an administrator when installing this program.
PowerUserPrivilegesRequired=You must be logged in as an administrator or as a member of the Power Users group when installing this program.
SetupAppRunningError=Setup has detected that %1 is currently running.%n%nPlease close all instances of it now, then click OK to continue, or Cancel to exit.
UninstallAppRunningError=Uninstall has detected that %1 is currently running.%n%nPlease close all instances of it now, then click OK to continue, or Cancel to exit.
; *** Startup questions
PrivilegesRequiredOverrideTitle=Select Setup Install Mode
PrivilegesRequiredOverrideInstruction=Select install mode
PrivilegesRequiredOverrideText1=%1 can be installed for all users (requires administrative privileges), or for you only.
PrivilegesRequiredOverrideText2=%1 can be installed for you only, or for all users (requires administrative privileges).
PrivilegesRequiredOverrideAllUsers=Install for &all users
PrivilegesRequiredOverrideAllUsersRecommended=Install for &all users (recommended)
PrivilegesRequiredOverrideCurrentUser=Install for &me only
PrivilegesRequiredOverrideCurrentUserRecommended=Install for &me only (recommended)
; *** Misc. errors
ErrorCreatingDir=Setup was unable to create the directory "%1"
ErrorTooManyFilesInDir=Unable to create a file in the directory "%1" because it contains too many files
; *** Setup common messages
ExitSetupTitle=Exit Setup
ExitSetupMessage=Setup is not complete. If you exit now, the program will not be installed.%n%nYou may run Setup again at another time to complete the installation.%n%nExit Setup?
AboutSetupMenuItem=&About Setup...
AboutSetupTitle=About Setup
AboutSetupMessage=%1 version %2%n%3%n%n%1 home page:%n%4
AboutSetupNote=
TranslatorNote=
; *** Buttons
ButtonBack=< &Back
ButtonNext=&Next >
ButtonInstall=&Install
ButtonOK=OK
ButtonCancel=Cancel
ButtonYes=&Yes
ButtonYesToAll=Yes to &All
ButtonNo=&No
ButtonNoToAll=N&o to All
ButtonFinish=&Finish
ButtonBrowse=&Browse...
ButtonWizardBrowse=B&rowse...
ButtonNewFolder=&Make New Folder
; *** "Select Language" dialog messages
SelectLanguageTitle=Select Setup Language
SelectLanguageLabel=Select the language to use during the installation.
; *** Common wizard text
ClickNext=Click Next to continue, or Cancel to exit Setup.
BeveledLabel=
BrowseDialogTitle=Browse For Folder
BrowseDialogLabel=Select a folder in the list below, then click OK.
NewFolderName=New Folder
; *** "Welcome" wizard page
WelcomeLabel1=Welcome to the [name] Setup Wizard
WelcomeLabel2=This will install [name/ver] on your computer.%n%nIt is recommended that you close all other applications before continuing.
; *** "Password" wizard page
WizardPassword=Password
PasswordLabel1=This installation is password protected.
PasswordLabel3=Please provide the password, then click Next to continue. Passwords are case-sensitive.
PasswordEditLabel=&Password:
IncorrectPassword=The password you entered is not correct. Please try again.
; *** "License Agreement" wizard page
WizardLicense=License Agreement
LicenseLabel=Please read the following important information before continuing.
LicenseLabel3=Please read the following License Agreement. You must accept the terms of this agreement before continuing with the installation.
LicenseAccepted=I &accept the agreement
LicenseNotAccepted=I &do not accept the agreement
; *** "Information" wizard pages
WizardInfoBefore=Information
InfoBeforeLabel=Please read the following important information before continuing.
InfoBeforeClickLabel=When you are ready to continue with Setup, click Next.
WizardInfoAfter=Information
InfoAfterLabel=Please read the following important information before continuing.
InfoAfterClickLabel=When you are ready to continue with Setup, click Next.
; *** "User Information" wizard page
WizardUserInfo=User Information
UserInfoDesc=Please enter your information.
UserInfoName=&User Name:
UserInfoOrg=&Organization:
UserInfoSerial=&Serial Number:
UserInfoNameRequired=You must enter a name.
; *** "Select Destination Location" wizard page
WizardSelectDir=Select Destination Location
SelectDirDesc=Where should [name] be installed?
SelectDirLabel3=Setup will install [name] into the following folder.
SelectDirBrowseLabel=To continue, click Next. If you would like to select a different folder, click Browse.
DiskSpaceGBLabel=At least [gb] GB of free disk space is required.
DiskSpaceMBLabel=At least [mb] MB of free disk space is required.
CannotInstallToNetworkDrive=Setup cannot install to a network drive.
CannotInstallToUNCPath=Setup cannot install to a UNC path.
InvalidPath=You must enter a full path with drive letter; for example:%n%nC:\APP%n%nor a UNC path in the form:%n%n\\server\share
InvalidDrive=The drive or UNC share you selected does not exist or is not accessible. Please select another.
DiskSpaceWarningTitle=Not Enough Disk Space
DiskSpaceWarning=Setup requires at least %1 KB of free space to install, but the selected drive only has %2 KB available.%n%nDo you want to continue anyway?
DirNameTooLong=The folder name or path is too long.
InvalidDirName=The folder name is not valid.
BadDirName32=Folder names cannot include any of the following characters:%n%n%1
DirExistsTitle=Folder Exists
DirExists=The folder:%n%n%1%n%nalready exists. Would you like to install to that folder anyway?
DirDoesntExistTitle=Folder Does Not Exist
DirDoesntExist=The folder:%n%n%1%n%ndoes not exist. Would you like the folder to be created?
; *** "Select Components" wizard page
WizardSelectComponents=Select Components
SelectComponentsDesc=Which components should be installed?
SelectComponentsLabel2=Select the components you want to install; clear the components you do not want to install. Click Next when you are ready to continue.
FullInstallation=Full installation
; if possible don't translate 'Compact' as 'Minimal' (I mean 'Minimal' in your language)
CompactInstallation=Compact installation
CustomInstallation=Custom installation
NoUninstallWarningTitle=Components Exist
NoUninstallWarning=Setup has detected that the following components are already installed on your computer:%n%n%1%n%nDeselecting these components will not uninstall them.%n%nWould you like to continue anyway?
ComponentSize1=%1 KB
ComponentSize2=%1 MB
ComponentsDiskSpaceGBLabel=Current selection requires at least [gb] GB of disk space.
ComponentsDiskSpaceMBLabel=Current selection requires at least [mb] MB of disk space.
; *** "Select Additional Tasks" wizard page
WizardSelectTasks=Select Additional Tasks
SelectTasksDesc=Which additional tasks should be performed?
SelectTasksLabel2=Select the additional tasks you would like Setup to perform while installing [name], then click Next.
; *** "Select Start Menu Folder" wizard page
WizardSelectProgramGroup=Select Start Menu Folder
SelectStartMenuFolderDesc=Where should Setup place the program's shortcuts?
SelectStartMenuFolderLabel3=Setup will create the program's shortcuts in the following Start Menu folder.
SelectStartMenuFolderBrowseLabel=To continue, click Next. If you would like to select a different folder, click Browse.
MustEnterGroupName=You must enter a folder name.
GroupNameTooLong=The folder name or path is too long.
InvalidGroupName=The folder name is not valid.
BadGroupName=The folder name cannot include any of the following characters:%n%n%1
NoProgramGroupCheck2=&Don't create a Start Menu folder
; *** "Ready to Install" wizard page
WizardReady=Ready to Install
ReadyLabel1=Setup is now ready to begin installing [name] on your computer.
ReadyLabel2a=Click Install to continue with the installation, or click Back if you want to review or change any settings.
ReadyLabel2b=Click Install to continue with the installation.
ReadyMemoUserInfo=User information:
ReadyMemoDir=Destination location:
ReadyMemoType=Setup type:
ReadyMemoComponents=Selected components:
ReadyMemoGroup=Start Menu folder:
ReadyMemoTasks=Additional tasks:
; *** TDownloadWizardPage wizard page and DownloadTemporaryFile
DownloadingLabel=Downloading additional files...
ButtonStopDownload=&Stop download
StopDownload=Are you sure you want to stop the download?
ErrorDownloadAborted=Download aborted
ErrorDownloadFailed=Download failed: %1 %2
ErrorDownloadSizeFailed=Getting size failed: %1 %2
ErrorFileHash1=File hash failed: %1
ErrorFileHash2=Invalid file hash: expected %1, found %2
ErrorProgress=Invalid progress: %1 of %2
ErrorFileSize=Invalid file size: expected %1, found %2
; *** "Preparing to Install" wizard page
WizardPreparing=Preparing to Install
PreparingDesc=Setup is preparing to install [name] on your computer.
PreviousInstallNotCompleted=The installation/removal of a previous program was not completed. You will need to restart your computer to complete that installation.%n%nAfter restarting your computer, run Setup again to complete the installation of [name].
CannotContinue=Setup cannot continue. Please click Cancel to exit.
ApplicationsFound=The following applications are using files that need to be updated by Setup. It is recommended that you allow Setup to automatically close these applications.
ApplicationsFound2=The following applications are using files that need to be updated by Setup. It is recommended that you allow Setup to automatically close these applications. After the installation has completed, Setup will attempt to restart the applications.
CloseApplications=&Automatically close the applications
DontCloseApplications=&Do not close the applications
ErrorCloseApplications=Setup was unable to automatically close all applications. It is recommended that you close all applications using files that need to be updated by Setup before continuing.
PrepareToInstallNeedsRestart=Setup must restart your computer. After restarting your computer, run Setup again to complete the installation of [name].%n%nWould you like to restart now?
; *** "Installing" wizard page
WizardInstalling=Installing
InstallingLabel=Please wait while Setup installs [name] on your computer.
; *** "Setup Completed" wizard page
FinishedHeadingLabel=Completing the [name] Setup Wizard
FinishedLabelNoIcons=Setup has finished installing [name] on your computer.
FinishedLabel=Setup has finished installing [name] on your computer. The application may be launched by selecting the installed shortcuts.
ClickFinish=Click Finish to exit Setup.
FinishedRestartLabel=To complete the installation of [name], Setup must restart your computer. Would you like to restart now?
FinishedRestartMessage=To complete the installation of [name], Setup must restart your computer.%n%nWould you like to restart now?
ShowReadmeCheck=Yes, I would like to view the README file
YesRadio=&Yes, restart the computer now
NoRadio=&No, I will restart the computer later
; used for example as 'Run MyProg.exe'
RunEntryExec=Run %1
; used for example as 'View Readme.txt'
RunEntryShellExec=View %1
; *** "Setup Needs the Next Disk" stuff
ChangeDiskTitle=Setup Needs the Next Disk
SelectDiskLabel2=Please insert Disk %1 and click OK.%n%nIf the files on this disk can be found in a folder other than the one displayed below, enter the correct path or click Browse.
PathLabel=&Path:
FileNotInDir2=The file "%1" could not be located in "%2". Please insert the correct disk or select another folder.
SelectDirectoryLabel=Please specify the location of the next disk.
; *** Installation phase messages
SetupAborted=Setup was not completed.%n%nPlease correct the problem and run Setup again.
AbortRetryIgnoreSelectAction=Select action
AbortRetryIgnoreRetry=&Try again
AbortRetryIgnoreIgnore=&Ignore the error and continue
AbortRetryIgnoreCancel=Cancel installation
; *** Installation status messages
StatusClosingApplications=Closing applications...
StatusCreateDirs=Creating directories...
StatusExtractFiles=Extracting files...
StatusCreateIcons=Creating shortcuts...
StatusCreateIniEntries=Creating INI entries...
StatusCreateRegistryEntries=Creating registry entries...
StatusRegisterFiles=Registering files...
StatusSavingUninstall=Saving uninstall information...
StatusRunProgram=Finishing installation...
StatusRestartingApplications=Restarting applications...
StatusRollback=Rolling back changes...
; *** Misc. errors
ErrorInternal2=Internal error: %1
ErrorFunctionFailedNoCode=%1 failed
ErrorFunctionFailed=%1 failed; code %2
ErrorFunctionFailedWithMessage=%1 failed; code %2.%n%3
ErrorExecutingProgram=Unable to execute file:%n%1
; *** Registry errors
ErrorRegOpenKey=Error opening registry key:%n%1\%2
ErrorRegCreateKey=Error creating registry key:%n%1\%2
ErrorRegWriteKey=Error writing to registry key:%n%1\%2
; *** INI errors
ErrorIniEntry=Error creating INI entry in file "%1".
; *** File copying errors
FileAbortRetryIgnoreSkipNotRecommended=&Skip this file (not recommended)
FileAbortRetryIgnoreIgnoreNotRecommended=&Ignore the error and continue (not recommended)
SourceIsCorrupted=The source file is corrupted
SourceDoesntExist=The source file "%1" does not exist
ExistingFileReadOnly2=The existing file could not be replaced because it is marked read-only.
ExistingFileReadOnlyRetry=&Remove the read-only attribute and try again
ExistingFileReadOnlyKeepExisting=&Keep the existing file
ErrorReadingExistingDest=An error occurred while trying to read the existing file:
FileExistsSelectAction=Select action
FileExists2=The file already exists.
FileExistsOverwriteExisting=&Overwrite the existing file
FileExistsKeepExisting=&Keep the existing file
FileExistsOverwriteOrKeepAll=&Do this for the next conflicts
ExistingFileNewerSelectAction=Select action
ExistingFileNewer2=The existing file is newer than the one Setup is trying to install.
ExistingFileNewerOverwriteExisting=&Overwrite the existing file
ExistingFileNewerKeepExisting=&Keep the existing file (recommended)
ExistingFileNewerOverwriteOrKeepAll=&Do this for the next conflicts
ErrorChangingAttr=An error occurred while trying to change the attributes of the existing file:
ErrorCreatingTemp=An error occurred while trying to create a file in the destination directory:
ErrorReadingSource=An error occurred while trying to read the source file:
ErrorCopying=An error occurred while trying to copy a file:
ErrorReplacingExistingFile=An error occurred while trying to replace the existing file:
ErrorRestartReplace=RestartReplace failed:
ErrorRenamingTemp=An error occurred while trying to rename a file in the destination directory:
ErrorRegisterServer=Unable to register the DLL/OCX: %1
ErrorRegSvr32Failed=RegSvr32 failed with exit code %1
ErrorRegisterTypeLib=Unable to register the type library: %1
; *** Uninstall display name markings
; used for example as 'My Program (32-bit)'
UninstallDisplayNameMark=%1 (%2)
; used for example as 'My Program (32-bit, All users)'
UninstallDisplayNameMarks=%1 (%2, %3)
UninstallDisplayNameMark32Bit=32-bit
UninstallDisplayNameMark64Bit=64-bit
UninstallDisplayNameMarkAllUsers=All users
UninstallDisplayNameMarkCurrentUser=Current user
; *** Post-installation errors
ErrorOpeningReadme=An error occurred while trying to open the README file.
ErrorRestartingComputer=Setup was unable to restart the computer. Please do this manually.
; *** Uninstaller messages
UninstallNotFound=File "%1" does not exist. Cannot uninstall.
UninstallOpenError=File "%1" could not be opened. Cannot uninstall
UninstallUnsupportedVer=The uninstall log file "%1" is in a format not recognized by this version of the uninstaller. Cannot uninstall
UninstallUnknownEntry=An unknown entry (%1) was encountered in the uninstall log
ConfirmUninstall=Are you sure you want to completely remove %1 and all of its components?
UninstallOnlyOnWin64=This installation can only be uninstalled on 64-bit Windows.
OnlyAdminCanUninstall=This installation can only be uninstalled by a user with administrative privileges.
UninstallStatusLabel=Please wait while %1 is removed from your computer.
UninstalledAll=%1 was successfully removed from your computer.
UninstalledMost=%1 uninstall complete.%n%nSome elements could not be removed. These can be removed manually.
UninstalledAndNeedsRestart=To complete the uninstallation of %1, your computer must be restarted.%n%nWould you like to restart now?
UninstallDataCorrupted="%1" file is corrupted. Cannot uninstall
; *** Uninstallation phase messages
ConfirmDeleteSharedFileTitle=Remove Shared File?
ConfirmDeleteSharedFile2=The system indicates that the following shared file is no longer in use by any programs. Would you like for Uninstall to remove this shared file?%n%nIf any programs are still using this file and it is removed, those programs may not function properly. If you are unsure, choose No. Leaving the file on your system will not cause any harm.
SharedFileNameLabel=File name:
SharedFileLocationLabel=Location:
WizardUninstalling=Uninstall Status
StatusUninstalling=Uninstalling %1...
; *** Shutdown block reasons
ShutdownBlockReasonInstallingApp=Installing %1.
ShutdownBlockReasonUninstallingApp=Uninstalling %1.
; The custom messages below aren't used by Setup itself, but if you make
; use of them in your scripts, you'll want to translate them.
[CustomMessages]
NameAndVersion=%1 version %2
AdditionalIcons=Additional shortcuts:
CreateDesktopIcon=Create a &desktop shortcut
CreateQuickLaunchIcon=Create a &Quick Launch shortcut
ProgramOnTheWeb=%1 on the Web
UninstallProgram=Uninstall %1
LaunchProgram=Launch %1
AssocFileExtension=&Associate %1 with the %2 file extension
AssocingFileExtension=Associating %1 with the %2 file extension...
AutoStartProgramGroupDescription=Startup:
AutoStartProgram=Automatically start %1
AddonHostProgramNotFound=%1 could not be located in the folder you selected.%n%nDo you want to continue anyway?
================================================
FILE: assets/buildtools/ISPPBuiltins.iss
================================================
// Inno Setup Preprocessor
//
// Inno Setup (C) 1997-2020 Jordan Russell. All Rights Reserved.
// Portions Copyright (C) 2000-2020 Martijn Laan. All Rights Reserved.
// Portions Copyright (C) 2001-2004 Alex Yackimoff. All Rights Reserved.
//
// See the ISPP help file for more documentation of the functions defined by this file
//
#if defined(ISPP_INVOKED) && !defined(_BUILTINS_ISS_)
//
#if PREPROCVER < 0x01000000
# error Inno Setup Preprocessor version is outdated
#endif
//
#define _BUILTINS_ISS_
//
#ifdef __OPT_E__
# define private EnableOptE
# pragma option -e-
#endif
#ifndef __POPT_P__
# define private DisablePOptP
#else
# pragma parseroption -p-
#endif
#define NewLine "\n"
#define Tab "\t"
#pragma parseroption -p+
#pragma spansymbol "\"
#define True 1
#define False 0
#define Yes True
#define No False
#define MaxInt 0x7FFFFFFFFFFFFFFFL
#define MinInt 0x8000000000000000L
#define NULL
#define void
// TypeOf constants
#define TYPE_ERROR 0
#define TYPE_NULL 1
#define TYPE_INTEGER 2
#define TYPE_STRING 3
#define TYPE_MACRO 4
#define TYPE_FUNC 5
#define TYPE_ARRAY 6
// Helper macro to find out the type of an array element or expression. TypeOf
// standard function only allows identifier as its parameter. Use this macro
// to convert an expression to identifier.
#define TypeOf2(any Expr) TypeOf(Expr)
// ReadReg constants
#define HKEY_CLASSES_ROOT 0x80000000UL
#define HKEY_CURRENT_USER 0x80000001UL
#define HKEY_LOCAL_MACHINE 0x80000002UL
#define HKEY_USERS 0x80000003UL
#define HKEY_CURRENT_CONFIG 0x80000005UL
#define HKEY_CLASSES_ROOT_64 0x82000000UL
#define HKEY_CURRENT_USER_64 0x82000001UL
#define HKEY_LOCAL_MACHINE_64 0x82000002UL
#define HKEY_USERS_64 0x82000003UL
#define HKEY_CURRENT_CONFIG_64 0x82000005UL
#define HKCR HKEY_CLASSES_ROOT
#define HKCU HKEY_CURRENT_USER
#define HKLM HKEY_LOCAL_MACHINE
#define HKU HKEY_USERS
#define HKCC HKEY_CURRENT_CONFIG
#define HKCR64 HKEY_CLASSES_ROOT_64
#define HKCU64 HKEY_CURRENT_USER_64
#define HKLM64 HKEY_LOCAL_MACHINE_64
#define HKU64 HKEY_USERS_64
#define HKCC64 HKEY_CURRENT_CONFIG_64
// Exec constants
#define SW_HIDE 0
#define SW_SHOWNORMAL 1
#define SW_NORMAL 1
#define SW_SHOWMINIMIZED 2
#define SW_SHOWMAXIMIZED 3
#define SW_MAXIMIZE 3
#define SW_SHOWNOACTIVATE 4
#define SW_SHOW 5
#define SW_MINIMIZE 6
#define SW_SHOWMINNOACTIVE 7
#define SW_SHOWNA 8
#define SW_RESTORE 9
#define SW_SHOWDEFAULT 10
#define SW_MAX 10
// Find constants
#define FIND_MATCH 0x00
#define FIND_BEGINS 0x01
#define FIND_ENDS 0x02
#define FIND_CONTAINS 0x03
#define FIND_CASESENSITIVE 0x04
#define FIND_SENSITIVE FIND_CASESENSITIVE
#define FIND_AND 0x00
#define FIND_OR 0x08
#define FIND_NOT 0x10
#define FIND_TRIM 0x20
// FindFirst constants
#define faReadOnly 0x00000001
#define faHidden 0x00000002
#define faSysFile 0x00000004
#define faVolumeID 0x00000008
#define faDirectory 0x00000010
#define faArchive 0x00000020
#define faSymLink 0x00000040
#define faAnyFile 0x0000003F
// GetStringFileInfo standard names
#define COMPANY_NAME "CompanyName"
#define FILE_DESCRIPTION "FileDescription"
#define FILE_VERSION "FileVersion"
#define INTERNAL_NAME "InternalName"
#define LEGAL_COPYRIGHT "LegalCopyright"
#define ORIGINAL_FILENAME "OriginalFilename"
#define PRODUCT_NAME "ProductName"
#define PRODUCT_VERSION "ProductVersion"
// GetStringFileInfo helpers
#define GetFileCompany(str FileName) GetStringFileInfo(FileName, COMPANY_NAME)
#define GetFileDescription(str FileName) GetStringFileInfo(FileName, FILE_DESCRIPTION)
#define GetFileVersionString(str FileName) GetStringFileInfo(FileName, FILE_VERSION)
#define GetFileCopyright(str FileName) GetStringFileInfo(FileName, LEGAL_COPYRIGHT)
#define GetFileOriginalFilename(str FileName) GetStringFileInfo(FileName, ORIGINAL_FILENAME)
#define GetFileProductVersion(str FileName) GetStringFileInfo(FileName, PRODUCT_VERSION)
#define DeleteToFirstPeriod(str *S) \
Local[1] = Copy(S, 1, (Local[0] = Pos(".", S)) - 1), \
S = Copy(S, Local[0] + 1), \
Local[1]
#define GetVersionComponents(str FileName, *Major, *Minor, *Rev, *Build) \
Local[1] = Local[0] = GetVersionNumbersString(FileName), \
Local[1] == "" ? "" : ( \
Major = Int(DeleteToFirstPeriod(Local[1])), \
Minor = Int(DeleteToFirstPeriod(Local[1])), \
Rev = Int(DeleteToFirstPeriod(Local[1])), \
Build = Int(Local[1]), \
Local[0])
#define GetPackedVersion(str FileName, *Version) \
Local[0] = GetVersionComponents(FileName, Local[1], Local[2], Local[3], Local[4]), \
Version = PackVersionComponents(Local[1], Local[2], Local[3], Local[4]), \
Local[0]
#define GetVersionNumbers(str FileName, *MS, *LS) \
Local[0] = GetPackedVersion(FileName, Local[1]), \
UnpackVersionNumbers(Local[1], MS, LS), \
Local[0]
#define PackVersionNumbers(int VersionMS, int VersionLS) \
VersionMS << 32 | (VersionLS & 0xFFFFFFFF)
#define PackVersionComponents(int Major, int Minor, int Rev, int Build) \
Major << 48 | (Minor & 0xFFFF) << 32 | (Rev & 0xFFFF) << 16 | (Build & 0xFFFF)
#define UnpackVersionNumbers(int Version, *VersionMS, *VersionLS) \
VersionMS = Version >> 32, \
VersionLS = Version & 0xFFFFFFFF, \
void
#define UnpackVersionComponents(int Version, *Major, *Minor, *Rev, *Build) \
Major = Version >> 48, \
Minor = (Version >> 32) & 0xFFFF, \
Rev = (Version >> 16) & 0xFFFF, \
Build = Version & 0xFFFF, \
void
#define VersionToStr(int Version) \
Str(Version >> 48 & 0xFFFF) + "." + Str(Version >> 32 & 0xFFFF) + "." + \
Str(Version >> 16 & 0xFFFF) + "." + Str(Version & 0xFFFF)
#define EncodeVer(int Major, int Minor, int Revision = 0, int Build = -1) \
(Major & 0xFF) << 24 | (Minor & 0xFF) << 16 | (Revision & 0xFF) << 8 | (Build >= 0 ? Build & 0xFF : 0)
#define DecodeVer(int Version, int Digits = 3) \
Str(Version >> 24 & 0xFF) + (Digits > 1 ? "." : "") + \
(Digits > 1 ? \
Str(Version >> 16 & 0xFF) + (Digits > 2 ? "." : "") : "") + \
(Digits > 2 ? \
Str(Version >> 8 & 0xFF) + (Digits > 3 && (Local = Version & 0xFF) ? "." : "") : "") + \
(Digits > 3 && Local ? \
Str(Version & 0xFF) : "")
#define FindSection(str Section = "Files") \
Find(0, "[" + Section + "]", FIND_MATCH | FIND_TRIM) + 1
#if VER >= 0x03000000
# define FindNextSection(int Line) \
Find(Line, "[", FIND_BEGINS | FIND_TRIM, "]", FIND_ENDS | FIND_AND)
# define FindSectionEnd(str Section = "Files") \
FindNextSection(FindSection(Section))
#else
# define FindSectionEnd(str Section = "Files") \
FindSection(Section) + EntryCount(Section)
#endif
#define FindCode() \
Local[1] = FindSection("Code"), \
Local[0] = Find(Local[1] - 1, "program", FIND_BEGINS, ";", FIND_ENDS | FIND_AND), \
(Local[0] < 0 ? Local[1] : Local[0] + 1)
#define ExtractFilePath(str PathName) \
(Local[0] = \
!(Local[1] = RPos("\", PathName)) ? \
"" : \
Copy(PathName, 1, Local[1] - 1)), \
Local[0] + \
((Local[2] = Len(Local[0])) == 2 && Copy(Local[0], Local[2]) == ":" ? \
"\" : \
"")
#define ExtractFileDir(str PathName) \
RemoveBackslash(ExtractFilePath(PathName))
#define ExtractFileExt(str PathName) \
Local[0] = RPos(".", PathName), \
Copy(PathName, Local[0] + 1)
#define ExtractFileName(str PathName) \
!(Local[0] = RPos("\", PathName)) ? \
PathName : \
Copy(PathName, Local[0] + 1)
#define ChangeFileExt(str FileName, str NewExt) \
!(Local[0] = RPos(".", FileName)) ? \
FileName + "." + NewExt : \
Copy(FileName, 1, Local[0]) + NewExt
#define RemoveFileExt(str FileName) \
!(Local[0] = RPos(".", FileName)) ? \
FileName : \
Copy(FileName, 1, Local[0] - 1)
#define AddBackslash(str S) \
Copy(S, Len(S)) == "\" ? S : S + "\"
#define RemoveBackslash(str S) \
Local[0] = Len(S), \
Local[0] > 0 ? \
Copy(S, Local[0]) == "\" ? \
(Local[0] == 3 && Copy(S, 2, 1) == ":" ? \
S : \
Copy(S, 1, Local[0] - 1)) : \
S : \
""
#define Delete(str *S, int Index, int Count = MaxInt) \
S = Copy(S, 1, Index - 1) + Copy(S, Index + Count)
#define Insert(str *S, int Index, str Substr) \
Index > Len(S) + 1 ? \
S : \
S = Copy(S, 1, Index - 1) + SubStr + Copy(S, Index)
#define YesNo(str S) \
(S = LowerCase(S)) == "yes" || S == "true" || S == "1"
#define IsDirSet(str SetupDirective) \
YesNo(SetupSetting(SetupDirective))
#define Power(int X, int P = 2) \
!P ? 1 : X * Power(X, P - 1)
#define Min(int A, int B, int C = MaxInt) \
A < B ? A < C ? Int(A) : Int(C) : Int(B)
#define Max(int A, int B, int C = MinInt) \
A > B ? A > C ? Int(A) : Int(C) : Int(B)
#define SameText(str S1, str S2) \
LowerCase(S1) == LowerCase(S2)
#define SameStr(str S1, str S2) \
S1 == S2
#define WarnRenamedVersion(str OldName, str NewName) \
Warning("Function """ + OldName + """ has been renamed. Use """ + NewName + """ instead.")
#define ParseVersion(str FileName, *Major, *Minor, *Rev, *Build) \
WarnRenamedVersion("ParseVersion", "GetVersionComponents"), \
GetVersionComponents(FileName, Major, Minor, Rev, Build)
#define GetFileVersion(str FileName) \
WarnRenamedVersion("GetFileVersion", "GetVersionNumbersString"), \
GetVersionNumbersString(FileName)
#ifdef DisablePOptP
# pragma parseroption -p-
#endif
#ifdef EnableOptE
# pragma option -e+
#endif
#endif
================================================
FILE: assets/elevate.cmd
================================================
@setlocal
@echo off
:: Try without elevation, in case %NVM_SYMLINK% is a user-owned path and the
:: machine has Windows 10 Developer Mode enabled
%*
if %ERRORLEVEL% LSS 1 goto :EOF
:: The command failed without elevation, try with elevation
set CMD=%*
set APP=%1
start wscript //nologo "%~dpn0.vbs" %*
================================================
FILE: assets/elevate.vbs
================================================
Set Shell = CreateObject("Shell.Application")
Set WShell = WScript.CreateObject("WScript.Shell")
Set ProcEnv = WShell.Environment("PROCESS")
cmd = ProcEnv("CMD")
app = ProcEnv("APP")
args= Right(cmd,(Len(cmd)-Len(app)))
If (WScript.Arguments.Count >= 1) Then
Shell.ShellExecute app, args, "", "runas", 0
Else
WScript.Quit
End If
================================================
FILE: assets/install.cmd
================================================
@echo off
set /P NVM_PATH="Enter the absolute path where the nvm-windows zip file is extracted/copied to: "
set NVM_HOME=%NVM_PATH%
set NVM_SYMLINK=C:\Program Files\nodejs
setx /M NVM_HOME "%NVM_HOME%"
setx /M NVM_SYMLINK "%NVM_SYMLINK%"
echo PATH=%PATH% > %NVM_HOME%\PATH.txt
for /f "skip=2 tokens=2,*" %%A in ('reg query "HKLM\System\CurrentControlSet\Control\Session Manager\Environment" /v Path 2^>nul') do (
setx /M PATH "%%B;%%NVM_HOME%%;%%NVM_SYMLINK%%"
)
if exist "%SYSTEMDRIVE%\Program Files (x86)\" (
set SYS_ARCH=64
) else (
set SYS_ARCH=32
)
(echo root: %NVM_HOME% && echo path: %NVM_SYMLINK% && echo arch: %SYS_ARCH% && echo proxy: none) > %NVM_HOME%\settings.txt
notepad %NVM_HOME%\settings.txt
@echo on
================================================
FILE: assets/run.cmd
================================================
@echo off
powershell -NoProfile -WindowStyle Hidden -Command "Start-Process -Filepath 'C:\Users\corey\OneDrive\Documents\workspace\libraries\oss\coreybutler\nvm-windows\bin\nvm.exe' -ArgumentList '\"%*\"' -NoNewWindow"
================================================
FILE: assets/setuserenv.vbs
================================================
Set WSHShell = CreateObject("WScript.Shell")
userpath = WSHShell.RegRead("HKCU\Environment\Path")
If InStr(userpath, "%NVM_HOME%") = False Then
userpath = userpath & ";%NVM_HOME%;"
End If
If InStr(userpath, "%NVM_SYMLINK%") = False Then
userpath = userpath & ";%NVM_SYMLINK%;"
End If
userpath = Replace(userpath, ";;", ";")
WSHShell.RegWrite "HKCU\Environment\Path", userpath
================================================
FILE: assets/unsetuserenv.vbs
================================================
Set WSHShell = CreateObject("WScript.Shell")
userpath = WSHShell.RegRead("HKCU\Environment\Path")
userpath = Replace(userpath, "%NVM_HOME%", "")
userpath = Replace(userpath, "%NVM_SYMLINK%", "")
userpath = Replace(userpath, ";;", ";")
WSHShell.RegWrite "HKCU\Environment\Path", userpath
================================================
FILE: build.js
================================================
// This is a deno helper script to locally build the installer using Inno Setup
// Yeah, I know it's not Node, but we need to compile this and Node SEAs on Win32 are a PITA.
const content = await Deno.readTextFile('./nvm.iss')
const data = JSON.parse(await Deno.readTextFile('./src/manifest.json'))
const {version} = data
const output = content.replaceAll('{{VERSION}}', version)
await Deno.writeTextFile('./.tmp.iss', output)
console.log('Viewing /.tmp.iss')
output.split("\n").forEach((line, num) => {
let n = `${num+1}`
while (n.length < 3) {
n = ' ' + n
}
console.log(`${n} | ${line}`)
})
const command = await new Deno.Command('.\\assets\\buildtools\\iscc.exe', {
args: ['.\\.tmp.iss'],
stdout: 'piped',
stderr: 'piped',
})
const process = command.spawn();
// Stream stdout
(async () => {
const decoder = new TextDecoder();
for await (const chunk of process.stdout) {
console.log(decoder.decode(chunk));
}
})();
// Stream stderr
(async () => {
const decoder = new TextDecoder();
for await (const chunk of process.stderr) {
console.error(decoder.decode(chunk));
}
})();
// Wait for completion
const status = await process.status;
Deno.remove('.\\.tmp.iss');
if (!status.success) {
Deno.exit(status.code);
}
================================================
FILE: examples/settings.txt
================================================
root: C:\Users\Corey\AppData\Roaming\nvm
path: C:\Program Files\nodejs
arch: 64
proxy: none
================================================
FILE: nvm.iss
================================================
#define MyAppName "NVM for Windows"
#define MyAppShortName "nvm"
#define MyAppLCShortName "nvm"
#define MyAppVersion "{{VERSION}}"
#define MyAppPublisher "Author Software Inc."
#define MyAppURL "https://github.com/coreybutler/nvm-windows"
#define MyAppExeName "nvm.exe"
#define MyIcon "bin\nvm.ico"
#define MyAppId "40078385-F676-4C61-9A9C-F9028599D6D3"
#define ProjectRoot "."
[Setup]
; NOTE: The value of AppId uniquely identifies this application.
; Do not use the same AppId value in installers for other applications.
PrivilegesRequired=admin
SignTool=signtool
SignedUninstaller=yes
AppId={#MyAppId}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
AppCopyright=Copyright (C) 2018-{code:GetCurrentYear} Author Software Inc., Ecor Ventures LLC, Corey Butler, and contributors.
AppVerName={#MyAppName} {#MyAppVersion}
AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
DefaultDirName={localappdata}\{#MyAppShortName}
DisableDirPage=no
DefaultGroupName={#MyAppName}
AllowNoIcons=yes
LicenseFile={#ProjectRoot}\LICENSE
OutputDir={#ProjectRoot}\dist\{#MyAppVersion}
OutputBaseFilename={#MyAppLCShortName}-setup
SetupIconFile={#ProjectRoot}\{#MyIcon}
Compression=lzma
SolidCompression=yes
ChangesEnvironment=yes
DisableProgramGroupPage=yes
ArchitecturesInstallIn64BitMode=x64
UninstallDisplayIcon={app}\{#MyIcon}
; Version information
VersionInfoVersion={{VERSION}}.0
VersionInfoCopyright=Copyright © {code:GetCurrentYear} Author Software Inc., Ecor Ventures LLC, Corey Butler, and contributors.
VersionInfoCompany=Author Software Inc.
VersionInfoDescription=Node.js version manager for Windows
VersionInfoProductName={#MyAppShortName}
VersionInfoProductTextVersion={#MyAppVersion}
[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"
[Tasks]
Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked; OnlyBelowVersion: 0,6.1
[Files]
Source: "{#ProjectRoot}\bin\*"; DestDir: "{app}"; BeforeInstall: PreInstall; Flags: ignoreversion recursesubdirs createallsubdirs; Excludes: "{#ProjectRoot}\bin\install.cmd"
[Icons]
Name: "{group}\{#MyAppShortName}"; Filename: "{app}\{#MyAppExeName}"; IconFilename: "{#MyIcon}"
Name: "{group}\Uninstall {#MyAppShortName}"; Filename: "{uninstallexe}"
[Registry]
; Register the URL protocol 'nvm'
Root: HKCR; Subkey: "{#MyAppShortName}"; ValueType: string; ValueName: ""; ValueData: "URL:nvm"; Flags: uninsdeletekey
Root: HKCR; Subkey: "{#MyAppShortName}"; ValueType: string; ValueName: "URL Protocol"; ValueData: ""; Flags: uninsdeletekey
Root: HKCR; Subkey: "{#MyAppShortName}\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#MyAppExeName},0"; Flags: uninsdeletekey
Root: HKCR; Subkey: "{#MyAppShortName}\shell\launch\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#MyAppExeName}"" ""%1"""; Flags: uninsdeletekey
[Code]
var
SymlinkPage: TInputDirWizardPage;
NotificationOptionPage: TInputOptionWizardPage;
EmailPage: TWizardPage;
EmailEdit: TEdit;
EmailLabel: TLabel;
PreText: TLabel;
function GetCurrentYear(Param: String): String;
begin
result := GetDateTimeString('yyyy', '-', ':');
end;
function IsDirEmpty(dir: string): Boolean;
var
FindRec: TFindRec;
ct: Integer;
begin
ct := 0;
if FindFirst(ExpandConstant(dir + '\*'), FindRec) then
try
repeat
if FindRec.Attributes and FILE_ATTRIBUTE_DIRECTORY = 0 then
ct := ct+1;
until
not FindNext(FindRec);
finally
FindClose(FindRec);
Result := ct = 0;
end;
end;
//function getInstalledVersions(dir: string):
var
nodeInUse: string;
procedure TakeControl(np: string; nv: string);
var
path: string;
begin
// Move the existing node.js installation directory to the nvm root & update the path
RenameFile(np,ExpandConstant('{app}')+'\'+nv);
RegQueryStringValue(HKEY_LOCAL_MACHINE,
'SYSTEM\CurrentControlSet\Control\Session Manager\Environment',
'Path', path);
StringChangeEx(path,np+'\','',True);
StringChangeEx(path,np,'',True);
StringChangeEx(path,np+';;',';',True);
RegWriteExpandStringValue(HKEY_LOCAL_MACHINE, 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment', 'Path', path);
RegQueryStringValue(HKEY_CURRENT_USER,
'Environment',
'Path', path);
StringChangeEx(path,np+'\','',True);
StringChangeEx(path,np,'',True);
StringChangeEx(path,np+';;',';',True);
RegWriteExpandStringValue(HKEY_CURRENT_USER, 'Environment', 'Path', path);
nodeInUse := ExpandConstant('{app}')+'\'+nv;
end;
function Ansi2String(AString:AnsiString):String;
var
i : Integer;
iChar : Integer;
outString : String;
begin
outString :='';
for i := 1 to Length(AString) do
begin
iChar := Ord(AString[i]); //get int value
outString := outString + Chr(iChar);
end;
Result := outString;
end;
procedure PreInstall();
var
TmpResultFile, TmpJS, NodeVersion, NodePath: string;
stdout: Ansistring;
ResultCode: integer;
msg1, msg2, msg3, dir1: Boolean;
begin
// Create a file to check for Node.JS
TmpJS := ExpandConstant('{tmp}') + '\nvm_check.js';
SaveStringToFile(TmpJS, 'console.log(require("path").dirname(process.execPath));', False);
// Execute the node file and save the output temporarily
TmpResultFile := ExpandConstant('{tmp}') + '\nvm_node_check.txt';
Exec(ExpandConstant('{cmd}'), '/C node "'+TmpJS+'" > "' + TmpResultFile + '"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
DeleteFile(TmpJS)
// Process the results
LoadStringFromFile(TmpResultFile,stdout);
NodePath := Trim(Ansi2String(stdout));
if DirExists(NodePath) then begin
Exec(ExpandConstant('{cmd}'), '/C node -v > "' + TmpResultFile + '"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
LoadStringFromFile(TmpResultFile, stdout);
NodeVersion := Trim(Ansi2String(stdout));
msg1 := SuppressibleMsgBox('Node '+NodeVersion+' is already installed. Do you want NVM to control this version?', mbConfirmation, MB_YESNO, IDYES) = IDNO;
if msg1 then begin
msg2 := SuppressibleMsgBox('NVM cannot run in parallel with an existing Node.js installation. Node.js must be uninstalled before NVM can be installed, or you must allow NVM to control the existing installation. Do you want NVM to control node '+NodeVersion+'?', mbConfirmation, MB_YESNO, IDYES) = IDYES;
if msg2 then begin
TakeControl(NodePath, NodeVersion);
end;
if not msg2 then begin
DeleteFile(TmpResultFile);
WizardForm.Close;
end;
end;
if not msg1 then
begin
TakeControl(NodePath, NodeVersion);
end;
end;
// Make sure the symlink directory doesn't exist
if DirExists(SymlinkPage.Values[0]) then begin
// If the directory is empty, just delete it since it will be recreated anyway.
dir1 := IsDirEmpty(SymlinkPage.Values[0]);
if dir1 then begin
RemoveDir(SymlinkPage.Values[0]);
end;
if not dir1 then begin
msg3 := SuppressibleMsgBox(SymlinkPage.Values[0]+' will be overwritten and all contents will be lost. Do you want to proceed?', mbConfirmation, MB_OKCANCEL, IDOK) = IDOK;
if msg3 then begin
RemoveDir(SymlinkPage.Values[0]);
end;
if not msg3 then begin
//RaiseException('The symlink cannot be created due to a conflict with the existing directory at '+SymlinkPage.Values[0]);
WizardForm.Close;
end;
end;
end;
end;
function IsSymbolicLink(const Path: string): Boolean;
var
FindRec: TFindRec;
begin
Result := False;
if FindFirst(Path, FindRec) then
begin
Result := (FindRec.Attributes and FILE_ATTRIBUTE_REPARSE_POINT) <> 0;
FindClose(FindRec);
end;
end;
procedure SymlinkPageChange(Sender: TObject);
var
NewPath: string;
begin
// Append '\nodejs' to the path if it is not already appended
NewPath := AddBackslash(SymlinkPage.Values[0]) + 'nodejs';
if Copy(SymlinkPage.Values[0], Length(SymlinkPage.Values[0]) - Length('\nodejs') + 1, Length('\nodejs')) <> '\nodejs' then
SymlinkPage.Values[0] := NewPath;
// Check if the new path exists and is not a symbolic link
if DirExists(NewPath) and not IsSymbolicLink(NewPath) then
begin
MsgBox('The directory "' + NewPath + '" already exists as a physical directory. Please choose a different location.', mbError, MB_OK);
SymlinkPage.Values[0] := '';
end;
end;
procedure InitializeWizard;
begin
SymlinkPage := CreateInputDirPage(wpSelectDir,
'Active Version Location',
'The active version of Node.js will always be available at this location.',
'Select the folder in which Setup should create the symlink, then click Next.',
False, '');
SymlinkPage.Add('This directory will automatically be added to your system path.');
SymlinkPage.Values[0] := ExpandConstant('C:\nvm4w\nodejs');
// Assign the OnChange event handler
SymlinkPage.Edits[0].OnChange := @SymlinkPageChange;
// Notification option page (after the Symlink page)
NotificationOptionPage := CreateInputOptionPage(
SymlinkPage.ID, // Ensures the Notification page appears right after the Symlink page
'Desktop Notifications (PREVIEW)',
'NVM for Windows supports the basic (free) edition of Author Notifications.',
'Select the events you wish to be notified of. Your choices can be modified at any time in the future.',
FALSE,
FALSE);
// Pre-checked checkbox
NotificationOptionPage.AddEx('Node.js LTS releases (Long-Term Support/Stable)', 0, FALSE);
NotificationOptionPage.AddEx('Node.js Current releases (Latest/Testing)', 0, FALSE);
NotificationOptionPage.AddEx('NVM For Windows releases', 0, FALSE);
NotificationOptionPage.AddEx('Author updates and releases (upcoming NVM for Windows successor)', 0, FALSE);
NotificationOptionPage.Values[0] := TRUE;
NotificationOptionPage.Values[1] := TRUE;
NotificationOptionPage.Values[2] := TRUE;
NotificationOptionPage.Values[3] := TRUE;
// Email Input Page
EmailPage := CreateCustomPage(
NotificationOptionPage.ID,
'Author Progress Email Signup',
'Get details about Author development milestones in your inbox.');
// Add introductory text above the input field
PreText := TLabel.Create(WizardForm);
PreText.Parent := EmailPage.Surface;
PreText.Caption := 'Author is the upcoming successor to NVM for Windows. Provide your email address to be informed of development milestones, release timelines, and enterprise capabilities. ' +
'Leave it blank if you do not wish to receive notifications.';
PreText.Left := 10;
PreText.Top := 10;
PreText.Width := 600; // Adjust width to fit the text
PreText.WordWrap := True; // Ensures the text wraps if it exceeds the width
// Add a label for the email input field
EmailLabel := TLabel.Create(WizardForm);
EmailLabel.Parent := EmailPage.Surface;
EmailLabel.Caption := 'Email Address: (Optional)';
EmailLabel.Font.Style := [fsBold];
EmailLabel.Left := 10; // Position from the left
EmailLabel.Top := 80; // Position from the top
// Add an email input field on the EmailPage
EmailEdit := TEdit.Create(WizardForm);
EmailEdit.Parent := EmailPage.Surface;
EmailEdit.Left := 10; // Align with the label
EmailEdit.Top := 110; // Position just below the label
EmailEdit.Width := 610;
EmailEdit.Text := ''; // Default value
end;
function LastPos(const SubStr, S: string): Integer;
var
I: Integer;
begin
Result := 0;
for I := Length(S) downto 1 do
begin
if Copy(S, I, Length(SubStr)) = SubStr then
begin
Result := I;
Exit;
end;
end;
end;
function IsValidEmail(const Email: string): Boolean;
var
AtPos, DotPos: Integer;
begin
AtPos := Pos('@', Email);
DotPos := LastPos('.', Email);
Result := (AtPos > 1) and (DotPos > AtPos + 1) and (DotPos < Length(Email));
end;
function NextButtonClick(CurPageID: Integer): Boolean;
var
Email: string;
begin
Result := True; // Allow navigation by default
if CurPageID = SymlinkPage.ID then
begin
// Check if the directory is empty
if DirExists(SymlinkPage.Values[0]) then
begin
if IsDirEmpty(SymlinkPage.Values[0]) then
begin
// If the directory is empty, just delete it since it will be recreated anyway.
RemoveDir(SymlinkPage.Values[0]);
end
else
begin
// Show a warning if the directory is not empty
MsgBox('The selected directory is not empty. Please choose a different path.', mbError, MB_OK);
Result := False; // Prevent navigation to the next page
end;
end;
end;
if CurPageID = EmailPage.ID then
begin
Email := Trim(EmailEdit.Text); // Remove leading/trailing spaces
if (Email <> '') and not IsValidEmail(Email) then
begin
MsgBox('Please enter a valid email address or leave the field blank.', mbError, MB_OK);
Result := False; // Prevent navigation to the next page
end
else
begin
WizardForm.NextButton.Enabled := True; // Allow navigation to the next page
end;
end;
// Handle the Notification page logic
if CurPageID = NotificationOptionPage.ID then
begin
if NotificationOptionPage.Values[0] then
begin
Log('User opted to enable Node.js release notifications.');
// Add your logic for enabling notifications here
end
else
begin
Log('User opted out of Node.js release notifications.');
end;
end;
end;
function InitializeUninstall(): Boolean;
var
path: string;
nvm_symlink: string;
begin
SuppressibleMsgBox('Removing NVM for Windows will remove the nvm command and all versions of node.js, including global npm modules.', mbInformation, MB_OK, IDOK);
// Remove the symlink
RegQueryStringValue(HKEY_LOCAL_MACHINE,
'SYSTEM\CurrentControlSet\Control\Session Manager\Environment',
'NVM_SYMLINK', nvm_symlink);
RemoveDir(nvm_symlink);
// Clean the registry
RegDeleteValue(HKEY_LOCAL_MACHINE,
'SYSTEM\CurrentControlSet\Control\Session Manager\Environment',
'NVM_HOME')
RegDeleteValue(HKEY_LOCAL_MACHINE,
'SYSTEM\CurrentControlSet\Control\Session Manager\Environment',
'NVM_SYMLINK')
RegDeleteValue(HKEY_CURRENT_USER,
'Environment',
'NVM_HOME')
RegDeleteValue(HKEY_CURRENT_USER,
'Environment',
'NVM_SYMLINK')
RegQueryStringValue(HKEY_LOCAL_MACHINE,
'SYSTEM\CurrentControlSet\Control\Session Manager\Environment',
'Path', path);
StringChangeEx(path,'%NVM_HOME%','',True);
StringChangeEx(path,'%NVM_SYMLINK%','',True);
StringChangeEx(path,';;',';',True);
RegWriteExpandStringValue(HKEY_LOCAL_MACHINE, 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment', 'Path', path);
RegQueryStringValue(HKEY_CURRENT_USER,
'Environment',
'Path', path);
StringChangeEx(path,'%NVM_HOME%','',True);
StringChangeEx(path,'%NVM_SYMLINK%','',True);
StringChangeEx(path,';;',';',True);
RegWriteExpandStringValue(HKEY_CURRENT_USER, 'Environment', 'Path', path);
Result := True;
end;
// Generate the settings file based on user input & update registry
procedure CurStepChanged(CurStep: TSetupStep);
var
path: string;
rootDir: string;
begin
if CurStep = ssInstall then
begin
// Ensure the root directory exists
rootDir := ExtractFileDir(SymlinkPage.Values[0]);
if not DirExists(rootDir) then
begin
if not CreateDir(rootDir) then
begin
MsgBox('Failed to create root directory: ' + rootDir, mbError, MB_OK);
WizardForm.Close;
end;
end;
end;
if CurStep = ssPostInstall then
begin
SaveStringToFile(ExpandConstant('{app}\settings.txt'), 'root: ' + ExpandConstant('{app}') + #13#10 + 'path: ' + SymlinkPage.Values[0] + #13#10, False);
// Add Registry settings
RegWriteExpandStringValue(HKEY_LOCAL_MACHINE, 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment', 'NVM_HOME', ExpandConstant('{app}'));
RegWriteExpandStringValue(HKEY_LOCAL_MACHINE, 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment', 'NVM_SYMLINK', SymlinkPage.Values[0]);
RegWriteExpandStringValue(HKEY_CURRENT_USER, 'Environment', 'NVM_HOME', ExpandConstant('{app}'));
RegWriteExpandStringValue(HKEY_CURRENT_USER, 'Environment', 'NVM_SYMLINK', SymlinkPage.Values[0]);
RegWriteStringValue(HKEY_LOCAL_MACHINE, 'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{#MyAppId}_is1', 'DisplayVersion', '{#MyAppVersion}');
// Update system and user PATH if needed
RegQueryStringValue(HKEY_LOCAL_MACHINE,
'SYSTEM\CurrentControlSet\Control\Session Manager\Environment',
'Path', path);
if Pos('%NVM_HOME%',path) = 0 then begin
path := path+';%NVM_HOME%';
StringChangeEx(path,';;',';',True);
RegWriteExpandStringValue(HKEY_LOCAL_MACHINE, 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment', 'Path', path);
end;
if Pos('%NVM_SYMLINK%',path) = 0 then begin
path := path+';%NVM_SYMLINK%';
StringChangeEx(path,';;',';',True);
RegWriteExpandStringValue(HKEY_LOCAL_MACHINE, 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment', 'Path', path);
end;
RegQueryStringValue(HKEY_CURRENT_USER,
'Environment',
'Path', path);
if Pos('%NVM_HOME%',path) = 0 then begin
path := path+';%NVM_HOME%';
StringChangeEx(path,';;',';',True);
RegWriteExpandStringValue(HKEY_CURRENT_USER, 'Environment', 'Path', path);
end;
if Pos('%NVM_SYMLINK%',path) = 0 then begin
path := path+';%NVM_SYMLINK%';
StringChangeEx(path,';;',';',True);
RegWriteExpandStringValue(HKEY_CURRENT_USER, 'Environment', 'Path', path);
end;
end;
end;
function GetNotificationString(Param: String): String;
begin
Result := ' subscribe';
if NotificationOptionPage.Values[0] then
begin
Result := Result + ' --lts';
end;
if NotificationOptionPage.Values[1] then
begin
Result := Result + ' --current';
end;
if NotificationOptionPage.Values[2] then
begin
Result := Result + ' --nvm4w';
end;
if NotificationOptionPage.Values[3] then
begin
Result := Result + ' --author';
end;
Result := Trim(Result);
end;
function getSymLink(o: string): string;
begin
Result := SymlinkPage.Values[0];
end;
function getCurrentVersion(o: string): string;
begin
Result := nodeInUse;
end;
function isNodeAlreadyInUse(): boolean;
begin
Result := Length(nodeInUse) > 0;
end;
function isEmailSupplied(): boolean;
begin
Result := Trim(EmailEdit.Text) <> '';
end;
function GetEmailRegistrationString(Param: String): string;
begin
Result := ' author newsletter --notify ' + Trim(EmailEdit.Text);
end;
[Run]
Filename: "{app}\nvm.exe"; Parameters: "{code:GetNotificationString}"; Flags: waituntilidle runhidden;
Filename: "{app}\nvm.exe"; Parameters: "{code:GetEmailRegistrationString}"; Check: isEmailSupplied; Flags: waituntilidle runhidden;
Filename: "{cmd}"; Parameters: "/C ""mklink /D ""{code:getSymLink}"" ""{code:getCurrentVersion}"""" "; Check: isNodeAlreadyInUse; Flags: waituntilidle runhidden;
Filename: "powershell.exe"; Parameters: "-NoExit -Command refreshenv; cls; Write-Host 'Welcome to NVM for Windows v{{VERSION}}'"; Description: "Open with Powershell"; Flags: postinstall skipifsilent;
[UninstallRun]
Filename: "{app}\nvm.exe"; Parameters: "unsubscribe --lts --current --nvm4w --author"; Flags: runhidden; RunOnceId: "UnregisterNVMForWindows";
Filename: "{app}\nvm.exe"; Parameters: "off"; Flags: runhidden; RunOnceId: "RemoveNVMForWindowsSymlink";
Filename: "{cmd}"; Parameters: "/C rmdir /S /Q ""{code:getSymLink}"""; Flags: runhidden; RunOnceId: "RemoveSymlink";
[UninstallDelete]
Type: filesandordirs; Name: "{app}";
Type: filesandordirs; Name: "{localappdata}\.nvm";
================================================
FILE: src/arch/arch.go
================================================
package arch
import (
//"regexp"
"os"
//"os/exec"
"strings"
//"fmt"
"encoding/hex"
)
func SearchBytesInFile( path string, match string, limit int) bool {
// Transform to byte array the string
toMatch, err := hex.DecodeString(match);
if (err != nil) {
return false;
}
// Opening the file and checking if there is an error
file, err := os.Open(path)
if err != nil {
return false;
}
// Close file upon return
defer file.Close()
// Allocate 1 byte array to perform the match
bit := make([]byte, 1);
j := 0
for i := 0; i < limit; i++ {
file.Read(bit);
if bit[0] != toMatch[j] {
j = 0;
}
if bit[0] == toMatch[j] {
j++;
if (j >= len(toMatch)) {
file.Close();
return true;
}
}
}
file.Close();
return false;
}
func Bit(path string) string {
isarm64 := SearchBytesInFile(path, "5045000064AA", 400)
is64 := SearchBytesInFile(path, "504500006486", 400);
is32 := SearchBytesInFile(path, "504500004C", 400);
if isarm64 {
return "arm64";
} else if is64 {
return "64";
} else if is32 {
return "32";
}
return "?";
}
func Validate(str string) (string){
if str == "" {
str = strings.ToLower(os.Getenv("PROCESSOR_ARCHITECTURE"))
}
if strings.Contains(str, "arm64") {
return "arm64"
}
if strings.Contains(str, "64") {
return "64"
}
return "32"
}
================================================
FILE: src/author/bridge.go
================================================
package author
import (
"bufio"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"syscall"
"time"
"github.com/coreybutler/go-fsutil"
"golang.org/x/sys/windows"
)
const (
// Constants for SetWindowPos
SWP_NOMOVE = 0x0002
SWP_NOZORDER = 0x0004
SWP_SHOWWINDOW = 0x0040
SW_HIDE = 0 // Hide the window (for the main console window)
)
var (
modkernel32 = syscall.NewLazyDLL("kernel32.dll")
moduser32 = syscall.NewLazyDLL("user32.dll")
procGetConsole = modkernel32.NewProc("GetConsoleWindow")
procShowWindow = moduser32.NewProc("ShowWindow")
)
// Hide the main console window (the one running the Go app)
func hideConsole() {
hwnd, _, _ := procGetConsole.Call()
if hwnd != 0 {
procShowWindow.Call(hwnd, uintptr(SW_HIDE)) // Hide the main console window
}
}
func Bridge(args ...string) {
exe, _ := os.Executable()
bridge := filepath.Join(filepath.Dir(exe), "author-nvm.exe")
if !fsutil.Exists(bridge) {
fmt.Println("error: author bridge not found")
os.Exit(1)
}
if len(args) < 2 {
if !(len(args) == 1 && args[0] == "version") {
fmt.Printf("error: invalid number of arguments passed to author bridge: %d\n", len(args))
os.Exit(1)
}
}
command := args[0]
args = args[1:]
// fmt.Printf("running author bridge: %s %v\n", command, args)
hideConsole()
cmd := exec.Command(bridge, append([]string{command}, args...)...)
cmd.SysProcAttr = &windows.SysProcAttr{
CreationFlags: windows.CREATE_NEW_PROCESS_GROUP | windows.DETACHED_PROCESS | windows.CREATE_NO_WINDOW,
}
// Create pipes for Stdout and Stderr
stdoutPipe, _ := cmd.StdoutPipe()
stderrPipe, _ := cmd.StderrPipe()
// Start the command
if err := cmd.Start(); err != nil {
fmt.Println("error starting bridge command:", err)
os.Exit(1)
}
// Stream Stdout
go func() {
scanner := bufio.NewScanner(stdoutPipe)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
}()
// Stream Stderr
go func() {
scanner := bufio.NewScanner(stderrPipe)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
}()
if command == "upgrade" {
for _, arg := range args {
if strings.Contains(arg, "--rollback") {
fmt.Println("exiting to rollback nvm.exe...")
time.Sleep(1 * time.Second)
os.Exit(0)
}
}
}
// Wait for the command to finish
if err := cmd.Wait(); err != nil {
fmt.Println("bridge command finished with error:", err)
}
}
================================================
FILE: src/encoding/encoding.go
================================================
package encoding
import (
"strings"
"unicode/utf8"
"github.com/saintfish/chardet"
)
func DetectCharset(content []byte) (string, error) {
detector := chardet.NewTextDetector()
result, err := detector.DetectBest(content)
if err != nil {
return "", err
}
return strings.ToUpper(result.Charset), nil
}
func ToUTF8(content string) []byte {
b := make([]byte, len(content))
i := 0
for _, r := range content {
i += utf8.EncodeRune(b[i:], r)
}
return b[:i]
}
// func ToUTF8(content []byte, ignoreInvalidITF8Chars ...bool) (string, error) {
// ignore := false
// if len(ignoreInvalidITF8Chars) > 0 {
// ignore = ignoreInvalidITF8Chars[0]
// }
// cs, err := DetectCharset(content)
// if err != nil {
// if !ignore {
// return "", err
// }
// cs = "UTF-8"
// }
// bs := string(content)
// if ignore {
// if !utf8.ValidString(bs) {
// v := make([]rune, 0, len(bs))
// for i, r := range bs {
// if r == utf8.RuneError {
// _, size := utf8.DecodeRuneInString(bs[i:])
// if size == 1 {
// continue
// }
// }
// v = append(v, r)
// }
// bs = string(v)
// }
// }
// if cs == "UTF-8" {
// return bs, nil
// }
// converter, err := iconv.NewConverter(cs, "UTF-8")
// if err != nil {
// err = errors.New("Failed to convert " + cs + " to UTF-8: " + err.Error())
// return bs, err
// }
// return converter.ConvertString(bs)
// }
================================================
FILE: src/file/file.go
================================================
package file
import (
"archive/zip"
"bufio"
"io"
"log"
"os"
"path/filepath"
"strings"
)
// Function courtesy http://stackoverflow.com/users/1129149/swtdrgn
func Unzip(src, dest string) error {
r, err := zip.OpenReader(src)
if err != nil {
return err
}
defer r.Close()
for _, f := range r.File {
if !strings.Contains(f.Name, "..") {
rc, err := f.Open()
if err != nil {
return err
}
defer rc.Close()
fpath := filepath.Join(dest, f.Name)
if f.FileInfo().IsDir() {
os.MkdirAll(fpath, f.Mode())
} else {
var fdir string
if lastIndex := strings.LastIndex(fpath, string(os.PathSeparator)); lastIndex > -1 {
fdir = fpath[:lastIndex]
}
err = os.MkdirAll(fdir, f.Mode())
if err != nil {
log.Fatal(err)
return err
}
f, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(f, rc)
if err != nil {
return err
}
}
} else {
log.Printf("failed to extract file: %s (cannot validate)\n", f.Name)
}
}
return nil
}
func ReadLines(path string) ([]string, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
var lines []string
scanner := bufio.NewScanner(file)
for scanner.Scan() {
lines = append(lines, scanner.Text())
}
return lines, scanner.Err()
}
func Exists(filename string) bool {
_, err := os.Stat(filename)
return err == nil
}
================================================
FILE: src/go.mod
================================================
module nvm
go 1.18
require (
github.com/blang/semver v3.5.1+incompatible
github.com/coreybutler/go-fsutil v1.2.0
github.com/coreybutler/go-where v1.0.2
github.com/dustin/go-humanize v1.0.1
github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4
github.com/ncruces/zenity v0.10.14
github.com/olekukonko/tablewriter v0.0.5
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d
golang.org/x/sys v0.25.0
)
require (
github.com/akavel/rsrc v0.10.2 // indirect
github.com/dchest/jsmin v0.0.0-20220218165748-59f39799265f // indirect
github.com/josephspurrier/goversioninfo v1.4.1 // indirect
github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
github.com/randall77/makefat v0.0.0-20210315173500-7ddd0e42c844 // indirect
golang.org/x/image v0.20.0 // indirect
)
================================================
FILE: src/go.sum
================================================
github.com/akavel/rsrc v0.10.2 h1:Zxm8V5eI1hW4gGaYsJQUhxpjkENuG91ki8B4zCrvEsw=
github.com/akavel/rsrc v0.10.2/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/coreybutler/go-fsutil v1.2.0 h1:kbm62NSofawglUppEOhpHC3NDf/J7ZpguBirBnsgUwU=
github.com/coreybutler/go-fsutil v1.2.0/go.mod h1:B+6ufEkkRZgFwyR2sHEVG9dMzVBU3GbyGyYmCq7YkEk=
github.com/coreybutler/go-where v1.0.2 h1:Omit67KeTtKpvSJjezVxnVD4qMtvlXDlItiKpVCdcl4=
github.com/coreybutler/go-where v1.0.2/go.mod h1:IqV4saJiDXdNJURfTfVRywDHvY1IG5F+GXb2kmnmEe8=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dchest/jsmin v0.0.0-20220218165748-59f39799265f h1:OGqDDftRTwrvUoL6pOG7rYTmWsTCvyEWFsMjg+HcOaA=
github.com/dchest/jsmin v0.0.0-20220218165748-59f39799265f/go.mod h1:Dv9D0NUlAsaQcGQZa5kc5mqR9ua72SmA8VXi4cd+cBw=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4 h1:qZNfIGkIANxGv/OqtnntR4DfOY2+BgwR60cAcu/i3SE=
github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4/go.mod h1:kW3HQ4UdaAyrUCSSDR4xUzBKW6O2iA4uHhk7AtyYp10=
github.com/josephspurrier/goversioninfo v1.4.1 h1:5LvrkP+n0tg91J9yTkoVnt/QgNnrI1t4uSsWjIonrqY=
github.com/josephspurrier/goversioninfo v1.4.1/go.mod h1:JWzv5rKQr+MmW+LvM412ToT/IkYDZjaclF2pKDss8IY=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/ncruces/zenity v0.10.14 h1:OBFl7qfXcvsdo1NUEGxTlZvAakgWMqz9nG38TuiaGLI=
github.com/ncruces/zenity v0.10.14/go.mod h1:ZBW7uVe/Di3IcRYH0Br8X59pi+O6EPnNIOU66YHpOO4=
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ=
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/randall77/makefat v0.0.0-20210315173500-7ddd0e42c844 h1:GranzK4hv1/pqTIhMTXt2X8MmMOuH3hMeUR0o9SP5yc=
github.com/randall77/makefat v0.0.0-20210315173500-7ddd0e42c844/go.mod h1:T1TLSfyWVBRXVGzWd0o9BI4kfoO9InEgfQe4NV3mLz8=
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA=
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/zmb3/gogetdoc v0.0.0-20190228002656-b37376c5da6a/go.mod h1:ofmGw6LrMypycsiWcyug6516EXpIxSbZ+uI9ppGypfY=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/image v0.20.0 h1:7cVCUjQwfL18gyBJOmYvptfSHS8Fb3YUDtfLIZ7Nbpw=
golang.org/x/image v0.20.0/go.mod h1:0a88To4CYVBAHp5FXJm8o7QbUl37Vd85ply1vyD8auM=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181207195948-8634b1ecd393/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190825031127-d72b05d2b1b6/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: src/manifest.json
================================================
{
"name": "nvm",
"version": "1.2.2",
"description": "Node.js version manager for Windows",
"license": "SEE LICENSE IN LICENSE",
"author": "coreybutler",
"build": "nvm.go",
"output": "../bin",
"update": true,
"upx": false,
"tiny": false,
"variables": {
"main.NvmVersion": "manifest.version",
"nvm/web.nvmversion": "manifest.version"
},
"minify": true,
"default_profiles": [
"dev"
],
"profile": {
"release": {
"upx": false,
"variables": {
"main.debug": "false",
"main.mode": "prod"
}
},
"dev": {
"variables": {
"main.debug": "true",
"main.mode": "dev"
}
}
}
}
================================================
FILE: src/node/node.go
================================================
package node
import (
"encoding/json"
"fmt"
"io/ioutil"
"nvm/arch"
"nvm/file"
"nvm/web"
"os"
"os/exec"
"regexp"
"strings"
// "../semver"
"github.com/blang/semver"
)
/**
* Returns version, architecture
*/
func GetCurrentVersion() (string, string) {
cmd := exec.Command("node", "-v")
str, err := cmd.Output()
if err == nil {
v := strings.Trim(regexp.MustCompile("-.*$").ReplaceAllString(regexp.MustCompile("v").ReplaceAllString(strings.Trim(string(str), " \n\r"), ""), ""), " \n\r")
cmd := exec.Command("node", "-p", "console.log(process.execPath)")
str, _ := cmd.Output()
file := strings.Trim(regexp.MustCompile("undefined").ReplaceAllString(string(str), ""), " \n\r")
bit := arch.Bit(file)
if bit == "?" {
cmd := exec.Command("node", "-e", "console.log(process.arch)")
str, err := cmd.Output()
if err == nil {
if string(str) == "x64" {
bit = "64"
} else if string(str) == "arm64" {
bit = "arm64"
} else {
bit = "32"
}
} else {
return v, "Unknown"
}
}
return v, bit
}
return "Unknown", ""
}
func IsVersionInstalled(root string, version string, cpu string) bool {
e32 := file.Exists(root + "\\v" + version + "\\node32.exe")
e64 := file.Exists(root + "\\v" + version + "\\node64.exe")
used := file.Exists(root + "\\v" + version + "\\node.exe")
if cpu == "all" {
return ((e32 || e64) && used) || e32 && e64
}
if file.Exists(root + "\\v" + version + "\\node" + cpu + ".exe") {
return true
}
if ((e32 || e64) && used) || (e32 && e64) {
return true
}
if !e32 && !e64 && used && arch.Validate(cpu) == arch.Bit(root+"\\v"+version+"\\node.exe") {
return true
}
if cpu == "32" {
return e32
}
if cpu == "64" {
return e64
}
return false
}
func IsVersionAvailable(v string) bool {
// Check the service to make sure the version is available
avail, _, _, _, _, _ := GetAvailable()
for _, b := range avail {
if b == v {
return true
}
}
return false
}
func reverseStringArray(str []string) []string {
for i := 0; i < len(str)/2; i++ {
j := len(str) - i - 1
str[i], str[j] = str[j], str[i]
}
return str
}
func GetInstalled(root string) []string {
list := make([]semver.Version, 0)
files, _ := ioutil.ReadDir(root)
for i := len(files) - 1; i >= 0; i-- {
if files[i].IsDir() || (files[i].Mode()&os.ModeSymlink == os.ModeSymlink) {
isnode, _ := regexp.MatchString("v", files[i].Name())
if isnode {
currentVersionString := strings.Replace(files[i].Name(), "v", "", 1)
currentVersion, _ := semver.Make(currentVersionString)
list = append(list, currentVersion)
}
}
}
semver.Sort(list)
loggableList := make([]string, 0)
for _, version := range list {
loggableList = append(loggableList, "v"+version.String())
}
loggableList = reverseStringArray(loggableList)
return loggableList
}
// Sorting
type BySemanticVersion []string
func (s BySemanticVersion) Len() int {
return len(s)
}
func (s BySemanticVersion) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func (s BySemanticVersion) Less(i, j int) bool {
v1, _ := semver.Make(s[i])
v2, _ := semver.Make(s[j])
return v1.GTE(v2)
}
// Identifies a version as "LTS"
func isLTS(element map[string]interface{}) bool {
switch datatype := element["lts"].(type) {
case bool:
return datatype
case string:
return true
}
return false
}
// Identifies a version as "current"
func isCurrent(element map[string]interface{}) bool {
if isLTS(element) {
return false
}
version, _ := semver.Make(element["version"].(string)[1:])
benchmark, _ := semver.Make("1.0.0")
if version.LT(benchmark) {
return false
}
return true
// return version.Major%2 == 1
}
// Identifies a stable old version.
func isStable(element map[string]interface{}) bool {
if isCurrent(element) {
return false
}
version, _ := semver.Make(element["version"].(string)[1:])
if version.Major != 0 {
return false
}
return version.Minor%2 == 0
}
// Identifies an unstable old version.
func isUnstable(element map[string]interface{}) bool {
if isStable(element) {
return false
}
version, _ := semver.Make(element["version"].(string)[1:])
if version.Major != 0 {
return false
}
return version.Minor%2 != 0
}
// Retrieve the remotely available versions
func GetAvailable() ([]string, []string, []string, []string, []string, map[string]string) {
all := make([]string, 0)
lts := make([]string, 0)
current := make([]string, 0)
stable := make([]string, 0)
unstable := make([]string, 0)
npm := make(map[string]string)
url := web.GetFullNodeUrl("index.json")
// Check the service to make sure the version is available
text, err := web.GetRemoteTextFile(url)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
if len(text) == 0 {
fmt.Println("Error retrieving version list: \"" + url + "\" returned blank results. This can happen when the remote file is being updated. Please try again in a few minutes.")
os.Exit(0)
}
// Parse
var data = make([]map[string]interface{}, 0)
err = json.Unmarshal([]byte(text), &data)
if err != nil {
fmt.Printf("Error retrieving versions from \"%s\": %v", url, err.Error())
os.Exit(1)
}
for _, element := range data {
var version = element["version"].(string)[1:]
all = append(all, version)
if val, ok := element["npm"].(string); ok {
npm[version] = val
}
if isLTS(element) {
lts = append(lts, version)
} else if isCurrent(element) {
current = append(current, version)
} else if isStable(element) {
stable = append(stable, version)
} else if isUnstable(element) {
unstable = append(unstable, version)
}
}
return all, lts, current, stable, unstable, npm
}
================================================
FILE: src/nvm.go
================================================
package main
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/url"
"os"
"os/exec"
"os/signal"
"os/user"
"path/filepath"
"regexp"
"strconv"
"strings"
"sync"
"syscall"
"time"
"unsafe"
"nvm/arch"
"nvm/author"
"nvm/encoding"
"nvm/file"
"nvm/node"
"nvm/upgrade"
"nvm/utility"
"nvm/web"
"github.com/blang/semver"
// "github.com/fatih/color"
"github.com/coreybutler/go-where"
"github.com/ncruces/zenity"
"github.com/olekukonko/tablewriter"
"golang.org/x/sys/windows"
"golang.org/x/sys/windows/registry"
)
// Replaced at build time
var NvmVersion = ""
type Environment struct {
settings string
root string
symlink string
arch string
node_mirror string
npm_mirror string
proxy string
originalpath string
originalversion string
verifyssl bool
}
var home = filepath.Clean(os.Getenv("NVM_HOME") + "\\settings.txt")
var symlink = filepath.Clean(os.Getenv("NVM_SYMLINK"))
var env = &Environment{
settings: home,
root: "",
symlink: symlink,
arch: strings.ToLower(os.Getenv("PROCESSOR_ARCHITECTURE")),
node_mirror: "",
npm_mirror: "",
proxy: "none",
originalpath: "",
originalversion: "",
verifyssl: true,
}
func writeToErrorLog(i interface{}, abort ...bool) {
exe, _ := os.Executable()
file, err := os.OpenFile(filepath.Join(filepath.Dir(exe), "error.log"), os.O_APPEND|os.O_CREATE|os.O_WRONLY, os.ModePerm)
if err != nil {
panic(err)
}
defer file.Close()
msg := fmt.Sprintf("%v\n", i)
if _, ferr := file.WriteString(msg); ferr != nil {
panic(ferr)
}
if len(abort) > 0 && abort[0] {
fmt.Println(msg)
os.Exit(1)
}
}
type Notification struct {
AppID string `json:"app_id"`
Title string `json:"title"`
Message string `json:"message"`
Icon string `json:"icon"`
Actions []Action `json:"actions"`
Duration string `json:"duration"`
Link string `json:"link"`
}
type Action struct {
Type string `json:"type"`
Label string `json:"label"`
URI string `json:"uri"`
}
func notify(data Notification) {
data.AppID = "NVM for Windows"
content, _ := json.Marshal(data)
go author.Bridge("notify", string(content))
}
func init() {
if len(os.Args) > 1 {
if strings.HasPrefix(os.Args[1], "nvm://") {
// Decode the URL
uri, _ := url.Parse(strings.ReplaceAll(os.Args[1], "%26", "&"))
action := strings.TrimSpace(uri.Query().Get("action"))
switch strings.ToLower(action) {
case "install":
version := strings.Replace(uri.Query().Get("version"), "v", "", 1)
os.Args[1] = "install"
if len(os.Args) < 3 {
os.Args = append(os.Args, "")
}
os.Args[2] = version
if len(os.Args) < 4 {
os.Args = append(os.Args, "")
}
os.Args[3] = "--show-progress-ui"
os.Args = append(os.Args, "--insecure")
case "use":
version := strings.Replace(uri.Query().Get("version"), "v", "", 1)
os.Args[1] = "use"
if len(os.Args) < 3 {
os.Args = append(os.Args, "")
}
os.Args[2] = version
if len(os.Args) < 4 {
os.Args = append(os.Args, "")
}
os.Args[3] = "--notify"
case "upgrade":
os.Args[1] = "upgrade"
if len(os.Args) < 3 {
os.Args = append(os.Args, "")
}
os.Args[2] = "--show-progress-ui"
case "upgrade_notify":
notify(Notification{
Title: "Upgrade Complete",
Message: fmt.Sprintf("Now running v%v.", NvmVersion),
Icon: "success",
Actions: []Action{
{Type: "protocol", Label: "Release Notes", URI: fmt.Sprintf("https://github.com/coreybutler/nvm-windows/releases/tag/%v", NvmVersion)},
},
})
time.Sleep(300 * time.Millisecond)
os.Exit(0)
default:
writeToErrorLog(fmt.Sprintf("%s command not recognized", action), true)
}
}
}
// Turn on debugging output
for _, arg := range os.Args[1:] {
if strings.ToLower(strings.ReplaceAll(arg, "-", "")) == "verbose" {
utility.EnableDebugLogs()
break
}
}
}
func main() {
utility.DebugLogf("command: %v", strings.Join(os.Args, " "))
args := os.Args
detail := ""
procarch := arch.Validate(env.arch)
// Capture any additional arguments
if len(args) > 2 {
detail = args[2]
}
if len(args) > 3 {
if args[3] == "32" || args[3] == "arm64" || args[3] == "64" {
procarch = args[3]
}
}
if len(args) < 2 {
help()
return
}
utility.DebugLogf("arch: %v", procarch)
if args[1] != "version" && args[1] != "--version" && args[1] != "v" && args[1] != "-v" && args[1] != "--v" {
setup()
}
// Run the appropriate method
switch args[1] {
case "i":
fallthrough
case "install":
install(detail, procarch)
case "rm":
fallthrough
case "uninstall":
uninstall(detail)
case "reinstall":
reinstall(detail, procarch)
case "u":
fallthrough
case "use":
use(detail, procarch)
case "ls":
fallthrough
case "list":
list(detail)
case "on":
enable()
case "off":
disable()
case "root":
if len(args) == 3 {
updateRootDir(args[2])
} else {
fmt.Println("\nCurrent Root: " + env.root)
}
case "v":
fallthrough
case "--version":
fallthrough
case "-version":
fallthrough
case "--v":
fallthrough
case "-v":
fallthrough
case "version":
fmt.Println(NvmVersion)
case "arch":
if strings.Trim(detail, " \r\n") != "" {
detail = strings.Trim(detail, " \r\n")
if detail != "32" && detail != "64" && detail != "arm64" {
fmt.Println("\"" + detail + "\" is an invalid architecture. Use 32, 64, or arm64.")
return
}
env.arch = detail
saveSettings()
fmt.Println("Default architecture set to " + detail + "-bit.")
return
}
_, a := node.GetCurrentVersion()
fmt.Println("System Default: " + env.arch + "-bit.")
fmt.Println("Currently Configured: " + a + "-bit.")
case "proxy":
if detail == "" {
fmt.Println("Current proxy: " + env.proxy)
} else {
env.proxy = detail
saveSettings()
}
case "current":
inuse, _ := node.GetCurrentVersion()
v, _ := semver.Make(inuse)
err := v.Validate()
if err != nil {
fmt.Println(inuse)
} else if inuse == "Unknown" {
fmt.Println("No current version. Run 'nvm use x.x.x' to set a version.")
} else {
fmt.Println("v" + inuse)
}
//case "update": update()
case "node_mirror":
setNodeMirror(detail)
case "npm_mirror":
setNpmMirror(detail)
case "debug":
checkLocalEnvironment()
case "subscribe":
fallthrough
case "unsubscribe":
author.Bridge(args[1:]...)
case "author":
author.Bridge(args[2:]...)
case "upgrade":
upgrade.Run(NvmVersion)
default:
fmt.Printf(`"%s" is not a valid command.`+"\n", args[1])
help()
}
}
// ===============================================================
// BEGIN | CLI functions
// ===============================================================
func setNodeMirror(uri string) {
env.node_mirror = uri
saveSettings()
}
func setNpmMirror(uri string) {
env.npm_mirror = uri
saveSettings()
}
func getVersion(version string, cpuarch string, localInstallsOnly ...bool) (string, string, error) {
requestedVersion := version
cpuarch = strings.ToLower(cpuarch)
if cpuarch != "" {
if cpuarch != "32" && cpuarch != "64" && cpuarch != "arm64" && cpuarch != "all" {
return version, cpuarch, errors.New("\"" + cpuarch + "\" is not a valid CPU architecture. Must be 32, 64, or arm64.")
}
} else {
cpuarch = env.arch
}
if cpuarch != "all" {
cpuarch = arch.Validate(cpuarch)
}
if version == "" {
return "", cpuarch, errors.New("A version argument is required but missing.")
}
// If user specifies "latest" version, find out what version is
if version == "latest" || version == "node" {
version = getLatest()
fmt.Println(version)
}
if version == "lts" {
version = getLTS()
}
if version == "newest" {
installed := node.GetInstalled(env.root)
if len(installed) == 0 {
return version, "", errors.New("No versions of node.js found. Try installing the latest by typing nvm install latest.")
}
version = installed[0]
}
if version == "32" || version == "64" || version == "arm64" {
cpuarch = version
v, _ := node.GetCurrentVersion()
version = v
}
version = versionNumberFrom(version)
v, err := semver.Make(version)
if err == nil {
err = v.Validate()
}
if err == nil {
// if the user specifies only the major/minor version, identify the latest
// version applicable to what was provided.
sv := strings.Split(version, ".")
if len(sv) < 3 {
version = findLatestSubVersion(version)
} else {
version = cleanVersion(version)
}
version = versionNumberFrom(version)
} else if strings.Contains(err.Error(), "No Major.Minor.Patch") {
latestLocalInstall := false
if len(localInstallsOnly) > 0 {
latestLocalInstall = localInstallsOnly[0]
}
version = findLatestSubVersion(version, latestLocalInstall)
if len(version) == 0 {
err = errors.New("Unrecognized version: \"" + requestedVersion + "\"")
}
}
return version, cpuarch, err
}
type Status struct {
Text string
Err error
Done bool
Help bool
}
func rollback(version string) error {
p := filepath.Join(env.root, "v"+version)
_, err := os.Lstat(p)
if err != nil {
if !os.IsNotExist(err) {
writeToErrorLog(err)
return fmt.Errorf("Error rolling back node v%s installation: %v.", version, err)
}
}
return nil
}
func install(version string, cpuarch string) {
requestedVersion := version
args := os.Args
lastarg := args[len(args)-1]
if lastarg == "--insecure" {
env.verifyssl = false
}
if strings.HasPrefix(version, "--") {
fmt.Println("\"--\" prefixes are unnecessary in NVM for Windows!")
version = strings.ReplaceAll(version, "-", "")
fmt.Printf("attempting to install \"%v\" instead...\n\n", version)
time.Sleep(2 * time.Second)
}
var exitCode = 0
var status = make(chan Status)
var cancel = make(chan bool)
var show_progress bool = false
var dlg zenity.ProgressDialog
wg := &sync.WaitGroup{}
wg.Add(1)
go func() {
defer func() {
// Reset the SSL verification
env.verifyssl = true
// sleep for 1 second to give users a chance to see the completion notice before exiting
if show_progress {
time.Sleep(1 * time.Second)
fmt.Println("exiting installer dialog")
}
wg.Done()
}()
for {
select {
case s := <-status:
if s.Err != nil {
exitCode = 1
if show_progress && dlg != nil {
// Close progress dialog and send error notificaion
notify(Notification{
Title: "Node.js Installation Error",
Message: s.Err.Error(),
Icon: "error",
})
dlg.Text(fmt.Sprintf("error: %v", s.Err))
dlg.Close()
// Cleanup
err := rollback(version)
if err != nil {
notify(Notification{
Title: "Rollback Error",
Message: err.Error(),
Icon: "error",
Actions: []Action{
{Type: "protocol", Label: "Open Explorer", URI: fmt.Sprintf("file://%s", filepath.ToSlash(filepath.Join(env.root)))},
},
})
}
} else {
fmt.Printf("error installing %s: %v\n", version, s.Err)
if s.Help {
fmt.Println(" ")
help()
}
}
return
}
if s.Done {
if show_progress && dlg != nil {
notify(Notification{
Title: fmt.Sprintf("Node.js v%s", version),
Message: "Installation complete.",
Icon: "node",
Actions: []Action{
{Type: "protocol", Label: "Use", URI: fmt.Sprintf("nvm://launch?action=use%%26version=%s", version)},
{Type: "protocol", Label: "Changelog", URI: fmt.Sprintf("https://github.com/nodejs/node/releases/tag/v%s", version)},
},
})
dlg.Text("Installation complete.")
time.Sleep(1 * time.Second)
} else {
fmt.Printf("Installation complete.\nIf you want to use this version, type:\n\nnvm use %s\n", version)
}
return
}
if show_progress && dlg != nil {
dlg.Text(s.Text)
} else {
fmt.Println(s.Text)
}
case <-cancel:
fmt.Printf("Node.js %s installation canceled by user\n", version)
if show_progress {
notify(Notification{
Title: fmt.Sprintf("Node.js v%s", version),
Message: "Installation canceled by user",
Icon: "error",
Actions: []Action{
{Type: "protocol", Label: "Restart Installation", URI: fmt.Sprintf("nvm://launch?action=install%%26version=%s%%26use=false%%26=show=true", version)},
},
})
}
err := rollback(version)
if err != nil {
if show_progress {
notify(Notification{
Title: "Rollback Error",
Message: err.Error(),
Icon: "error",
Actions: []Action{
{Type: "protocol", Label: "Open Explorer", URI: fmt.Sprintf("file://%s", filepath.ToSlash(filepath.Join(env.root)))},
},
})
} else {
fmt.Printf("rollback error: %v\n", err)
}
} else {
fmt.Println("Rollback complete.")
}
if cpuarch == "arm64" && !web.IsNodeArm64bitAvailable(version) {
status <- Status{Err: fmt.Errorf("Node.js v%s is only available in 32-bit and 64-bit.", version)}
}
if !node.IsVersionInstalled(env.root, version, cpuarch) {
if !node.IsVersionAvailable(version) {
url := web.GetFullNodeUrl("index.json")
status <- Status{Err: fmt.Errorf("Version %s is not available.\n\nThe complete list of available versions can be found at %s", version, url)}
}
}
return
}
}
}()
// Wait for the prior subroutine to initialize before starting the next dependent thread
time.Sleep(300 * time.Millisecond)
go func() {
v, a, err := getVersion(version, cpuarch)
version = v
cpuarch = a
// Setup signal handling first
signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, os.Interrupt, syscall.SIGTERM)
defer signal.Stop(signalChan)
// Add signal handler
go func() {
<-signalChan
cancel <- true
}()
// Determine whether to show the progress dialog
for _, arg := range os.Args {
if arg == "--show-progress-ui" {
show_progress = true
exe, _ := os.Executable()
winIco := filepath.Join(filepath.Dir(exe), "nvm.ico")
ico := filepath.Join(filepath.Dir(exe), "nodejs.ico")
var perr error
dlg, perr = zenity.Progress(
zenity.Title(fmt.Sprintf("Installing Node.js v%s", version)),
zenity.Icon(ico),
zenity.WindowIcon(winIco),
zenity.AutoClose(),
zenity.NoCancel(),
zenity.Pulsate())
if perr != nil {
fmt.Println("Failed to create progress dialog")
}
go func() {
for {
select {
case <-dlg.Done():
if err := dlg.Complete(); err == zenity.ErrCanceled {
cancel <- true
}
return
}
}
}()
status <- Status{Text: "Validating version..."}
break
}
}
if err != nil {
if strings.Contains(err.Error(), "No Major.Minor.Patch") {
sv, sverr := semver.Make(version)
if sverr == nil {
sverr = sv.Validate()
}
if sverr != nil {
version = findLatestSubVersion(version)
if len(version) == 0 {
sverr = errors.New("Unrecognized version: \"" + requestedVersion + "\"")
}
}
err = sverr
}
if err != nil {
status <- Status{Err: err, Help: true}
return
}
}
if err != nil {
status <- Status{Err: fmt.Errorf(`"%s" is not a valid version.`+"\n"+`Please use a valid semantic version number, "lts", or "latest".`, requestedVersion)}
return
}
if checkVersionExceedsLatest(version) {
status <- Status{Err: fmt.Errorf("Node.js v%s is not yet released or is not available for download yet.", version)}
return
}
if cpuarch == "64" && !web.IsNode64bitAvailable(version) {
status <- Status{Err: fmt.Errorf("Node.js v%s is only available in 32-bit.", version)}
return
}
// Check to see if the version is already installed
if !node.IsVersionInstalled(env.root, version, cpuarch) {
if !node.IsVersionAvailable(version) {
url := web.GetFullNodeUrl("index.json")
status <- Status{Err: fmt.Errorf("Version %s is not available.\n\nThe complete list of available versions can be found at %s", version, url)}
return
}
// Make the output directories
root, err := os.MkdirTemp("", "nvm-install-*")
if err != nil {
status <- Status{Err: err}
}
defer os.RemoveAll(root)
os.MkdirAll(filepath.Join(root, "v"+version, "node_modules"), os.ModeDir)
// os.MkdirAll(filepath.Join(env.root, "v"+version, "node_modules"), os.ModeDir)
// Warn the user if they're attempting to install without verifying the remote SSL cert
if !env.verifyssl {
fmt.Println("\nWARNING: The remote SSL certificate will not be validated during the download process.\n")
}
// Download node
if show_progress {
status <- Status{Text: "Downloading & extracting..."}
}
append32 := node.IsVersionInstalled(env.root, version, "64")
append64 := node.IsVersionInstalled(env.root, version, "32")
if (cpuarch == "32" || cpuarch == "all") && !node.IsVersionInstalled(root, version, "32") {
success := web.GetNodeJS(root, version, "32", append32)
if !success {
status <- Status{Err: fmt.Errorf("failed to download v%v 32-bit executable", version)}
return
}
}
if (cpuarch == "64" || cpuarch == "all") && !node.IsVersionInstalled(root, version, "64") {
success := web.GetNodeJS(root, version, "64", append64)
if !success {
status <- Status{Err: fmt.Errorf("failed to download v%v 64-bit executable", version)}
return
}
}
if (cpuarch == "arm64" || cpuarch == "all") && !node.IsVersionInstalled(root, version, "arm64") {
success := web.GetNodeJS(root, version, "arm64", append64)
if !success {
status <- Status{Err: fmt.Errorf("failed to download v%v arm 64-bit executable", version)}
return
}
}
if file.Exists(filepath.Join(root, "v"+version, "node_modules", "npm")) {
utility.DebugLogf("move %v to %v", filepath.Join(root, "v"+version), filepath.Join(env.root, "v"+version))
if rnerr := utility.Rename(filepath.Join(root, "v"+version), filepath.Join(env.root, "v"+version)); rnerr != nil {
status <- Status{Err: err}
}
utility.DebugFn(func() {
utility.DebugLogf("env root: %v", env.root)
cmd := exec.Command("cmd", "/C", "dir", filepath.Join(env.root, "v"+version))
out, err := cmd.CombinedOutput()
if err != nil {
utility.DebugLog(err.Error())
} else {
utility.DebugLog(string(out))
}
})
if show_progress {
status <- Status{Text: "Configuring npm..."}
time.Sleep(1 * time.Second)
status <- Status{Done: true}
} else {
npmv := getNpmVersion(version)
status <- Status{Text: fmt.Sprintf("npm v%s installed successfully.\n\nIf you want to use this version, type\n\nnvm use %s", npmv, version), Done: true}
}
}
// If successful, add npm
status <- Status{Text: "Downloading npm..."}
npmv := getNpmVersion(version)
success := web.GetNpm(root, getNpmVersion(version))
if success {
status <- Status{Text: fmt.Sprintf("Installing npm v%s...", npmv)}
// new temp directory under the nvm root
tempDir, err := os.MkdirTemp("", "nvm-npm-*")
if err != nil {
status <- Status{Err: err}
}
defer os.RemoveAll(tempDir)
// Extract npm to the temp directory
err = file.Unzip(filepath.Join(tempDir, "npm-v"+npmv+".zip"), filepath.Join(tempDir, "nvm-npm"))
if err != nil {
status <- Status{Err: err}
}
// Copy the npm and npm.cmd files to the installation directory
tempNpmBin := filepath.Join(tempDir, "nvm-npm", "cli-"+npmv, "bin")
// Support npm < 6.2.0
if file.Exists(tempNpmBin) == false {
tempNpmBin = filepath.Join(tempDir, "nvm-npm", "npm-"+npmv, "bin")
}
if file.Exists(tempNpmBin) == false {
status <- Status{Err: fmt.Errorf("Failed to extract npm. Could not find %s", tempNpmBin), Done: true}
return
}
// Standard npm support
utility.Rename(filepath.Join(tempNpmBin, "npm"), filepath.Join(root, "v"+version, "npm"))
utility.Rename(filepath.Join(tempNpmBin, "npm.cmd"), filepath.Join(root, "v"+version, "npm.cmd"))
// npx support
if _, err := os.Stat(filepath.Join(tempNpmBin, "npx")); err == nil {
utility.Rename(filepath.Join(tempNpmBin, "npx"), filepath.Join(root, "v"+version, "npx"))
utility.Rename(filepath.Join(tempNpmBin, "npx.cmd"), filepath.Join(root, "v"+version, "npx.cmd"))
}
npmSourcePath := filepath.Join(tempDir, "nvm-npm", "npm-"+npmv)
if file.Exists(npmSourcePath) == false {
npmSourcePath = filepath.Join(tempDir, "nvm-npm", "cli-"+npmv)
}
moveNpmErr := utility.Rename(npmSourcePath, filepath.Join(root, "v"+version, "node_modules", "npm"))
if moveNpmErr != nil {
// sometimes Windows can take some time to enable access to large amounts of files after unzip, use exponential backoff to wait until it is ready
for _, i := range [5]int{1, 2, 4, 8, 16} {
time.Sleep(time.Duration(i) * time.Second)
moveNpmErr = utility.Rename(npmSourcePath, filepath.Join(root, "v"+version, "node_modules", "npm"))
if moveNpmErr == nil {
break
}
}
}
if err == nil && moveNpmErr == nil {
err = utility.Rename(filepath.Join(root, "v"+version), filepath.Join(env.root, "v"+version))
if err != nil {
status <- Status{Err: err}
}
if show_progress {
status <- Status{Done: true}
} else {
status <- Status{Text: fmt.Sprintf("Installation complete. If you want to use this version, type\n\nnvm use %s", version), Done: true}
}
} else if moveNpmErr != nil {
status <- Status{Err: fmt.Errorf("Unable to move directory %s to node_modules: %v", npmSourcePath, moveNpmErr), Done: true}
} else {
status <- Status{Err: fmt.Errorf("Failed to extract npm: %v", err), Done: true}
}
} else {
err = utility.Rename(filepath.Join(root, "v"+version), filepath.Join(env.root, "v"+version))
if err != nil {
status <- Status{Err: err}
}
npmurl := web.GetFullNpmUrl(version)
if show_progress {
// Send special error notification with link to npm release when it cannot be downloaded
notify(Notification{
Title: "Download Failure (npm)",
Message: fmt.Sprintf("Please download npm v%s manually and extract to %s\\v%s", version, env.root, version),
Icon: "error",
Actions: []Action{
{Type: "protocol", Label: "Manually Download", URI: npmurl},
},
})
status <- Status{Done: true}
} else {
status <- Status{Err: fmt.Errorf("Could not download npm for node v%s.\nPlease visit %s to download npm.\nIt should be extracted to %s\\v%s", version, npmurl, env.root, version), Done: true}
}
}
} else {
status <- Status{Text: "Version " + version + " is already installed.", Done: true}
}
}()
// Wait for the process to complete before exiting
wg.Wait()
os.Exit(exitCode)
}
func reinstall(version, cpuarch string) {
// Make sure a version is specified
if len(version) == 0 {
fmt.Println("Provide the version you want to uninstall.")
help()
return
}
if strings.ToLower(version) == "latest" || strings.ToLower(version) == "node" {
version = getLatest()
} else if strings.ToLower(version) == "lts" {
version = getLTS()
} else if strings.ToLower(version) == "newest" {
installed := node.GetInstalled(env.root)
if len(installed) == 0 {
fmt.Println("No versions of node.js found. Try installing the latest by typing nvm install latest.")
return
}
version = installed[0]
}
version = cleanVersion(version)
// Determine if the version exists and skip if it doesn't
if node.IsVersionInstalled(env.root, version, "32") || node.IsVersionInstalled(env.root, version, "64") {
v, _ := node.GetCurrentVersion()
fmt.Printf("Removing v%v...\n", version)
if v == version {
// _, err := runElevated(fmt.Sprintf(`"%s" cmd /C rmdir "%s"`, filepath.Join(env.root, "elevate.cmd"), filepath.Clean(env.symlink)))
abortOnBadSymlink(env.symlink)
_, err := elevatedRun("rmdir", filepath.Clean(env.symlink))
if err != nil {
fmt.Println(fmt.Sprint(err))
return
}
}
e := os.RemoveAll(filepath.Join(env.root, "v"+version))
if e != nil {
fmt.Printf("error: failed to remove v%v: %v\n", version, e)
os.Exit(1)
}
} else {
fmt.Printf("node v%v is not installed. Type \"nvm list\" to see what is installed.\n", version)
}
install(version, cpuarch)
}
func uninstall(version string) {
// Make sure a version is specified
if len(version) == 0 {
fmt.Println("Provide the version you want to uninstall.")
help()
return
}
if strings.ToLower(version) == "latest" || strings.ToLower(version) == "node" {
version = getLatest()
} else if strings.ToLower(version) == "lts" {
version = getLTS()
} else if strings.ToLower(version) == "newest" {
installed := node.GetInstalled(env.root)
if len(installed) == 0 {
fmt.Println("No versions of node.js found. Try installing the latest by typing nvm install latest.")
return
}
version = installed[0]
}
version = cleanVersion(version)
// Determine if the version exists and skip if it doesn't
if node.IsVersionInstalled(env.root, version, "32") || node.IsVersionInstalled(env.root, version, "64") || node.IsVersionInstalled(env.root, version, "arm64") {
fmt.Printf("Uninstalling node v" + version + "...")
v, _ := node.GetCurrentVersion()
if v == version {
// _, err := runElevated(fmt.Sprintf(`"%s" cmd /C rmdir "%s"`, filepath.Join(env.root, "elevate.cmd"), filepath.Clean(env.symlink)))
abortOnBadSymlink(env.symlink)
_, err := elevatedRun("rmdir", filepath.Clean(env.symlink))
if err != nil {
fmt.Println(fmt.Sprint(err))
return
}
}
e := os.RemoveAll(filepath.Join(env.root, "v"+version))
if e != nil {
fmt.Println("Error removing node v" + version)
fmt.Println("Manually remove " + filepath.Join(env.root, "v"+version) + ".")
} else {
fmt.Printf(" done")
}
} else {
fmt.Println("node v" + version + " is not installed. Type \"nvm list\" to see what is installed.")
}
return
}
func versionNumberFrom(version string) string {
reg, _ := regexp.Compile("[^0-9]")
if reg.MatchString(version[:1]) {
if version[0:1] != "v" {
url := web.GetFullNodeUrl("latest-" + version + "/SHASUMS256.txt")
remoteContent, err := web.GetRemoteTextFile(url)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
content := strings.Split(remoteContent, "\n")[0]
if strings.Contains(content, "node") {
parts := strings.Split(content, "-")
if len(parts) > 1 {
if parts[1][0:1] == "v" {
return parts[1][1:]
}
}
}
fmt.Printf("\"%v\" is not a valid version or known alias.\n", version)
fmt.Println("\nAvailable aliases: latest, node (latest), lts\nNamed releases (boron, dubnium, etc) are also supported.")
os.Exit(0)
}
}
for reg.MatchString(version[:1]) {
version = version[1:]
}
return version
}
func splitVersion(version string) map[string]int {
parts := strings.Split(version, ".")
var result = make([]int, 3)
for i, item := range parts {
v, _ := strconv.Atoi(item)
result[i] = v
}
return map[string]int{
"major": result[0],
"minor": result[1],
"patch": result[2],
}
}
func findLatestSubVersion(version string, localOnly ...bool) string {
if len(localOnly) > 0 && localOnly[0] {
installed := node.GetInstalled(env.root)
result := ""
for _, v := range installed {
if strings.HasPrefix(v, "v"+version) {
if result != "" {
current, _ := semver.New(versionNumberFrom(result))
next, _ := semver.New(versionNumberFrom(v))
if current.LT(*next) {
result = v
}
} else {
result = v
}
}
}
if len(strings.TrimSpace(result)) > 0 {
return versionNumberFrom(result)
}
}
if len(strings.Split(version, ".")) == 2 {
all, _, _, _, _, _ := node.GetAvailable()
requested := splitVersion(version + ".0")
for _, v := range all {
available := splitVersion(v)
if requested["major"] == available["major"] {
if requested["minor"] == available["minor"] {
if available["patch"] > requested["patch"] {
requested["patch"] = available["patch"]
}
}
if requested["minor"] > available["minor"] {
break
}
}
if requested["major"] > available["major"] {
break
}
}
return fmt.Sprintf("%v.%v.%v", requested["major"], requested["minor"], requested["patch"])
}
url := web.GetFullNodeUrl("latest-v" + version + ".x" + "/SHASUMS256.txt")
content, err := web.GetRemoteTextFile(url)
if err != nil {
if strings.Contains(err.Error(), "HTTP Status 404") {
fmt.Printf("\"%s\" is not a valid version number (or partial version number).\n\nIf you are trying to install a version that was just announced within the last few minutes, it may not be available for download yet (try again in 15 minutes).\n", version)
} else {
fmt.Println(err)
}
os.Exit(1)
}
re := regexp.MustCompile("node-v(.+)+msi")
reg := regexp.MustCompile("node-v|-[xa].+")
latest := reg.ReplaceAllString(re.FindString(content), "")
return latest
}
func accessDenied(err error) bool {
fmt.Println(fmt.Sprintf("%v", err))
if strings.Contains(strings.ToLower(err.Error()), "access is denied") {
fmt.Println("See https://bit.ly/nvm4w-help")
return true
}
return false
}
func isSymlink(path string) (bool, error) {
info, err := os.Lstat(path)
if err != nil {
return false, err
}
return info.Mode()&os.ModeSymlink != 0, nil
}
func use(version string, cpuarch string, reload ...bool) {
version, cpuarch, err := getVersion(version, cpuarch, true)
exitCode := 0
status := make(chan Status)
wg := &sync.WaitGroup{}
wg.Add(1)
notifications := false
if os.Args[len(os.Args)-1] == "--notify" {
notifications = true
}
go func() {
defer func() {
if notifications {
time.Sleep(1 * time.Second)
}
wg.Done()
}()
for {
select {
case s := <-status:
if s.Err != nil {
exitCode = 1
if notifications {
// Close progress dialog and send error notificaion
notify(Notification{
Title: "Node.js Activation Error",
Message: fmt.Sprintf("nvm use %s failed because %v", version, s.Err),
Icon: "error",
})
}
fmt.Printf("activation error: %v\n", s.Err)
if s.Help {
fmt.Println(" ")
help()
}
return
}
if s.Done {
if s.Err == nil {
if notifications {
notify(Notification{
Title: "Node.js Activated",
Message: fmt.Sprintf("Your system is now configured to use v%s (%v-bit).", version, cpuarch),
Icon: "success",
Actions: []Action{
{Type: "protocol", Label: "View Changelog", URI: fmt.Sprintf("https://github.com/nodejs/node/releases/tag/v%s", version)},
},
})
}
fmt.Printf("Now using node v%s (%v-bit)\n", version, cpuarch)
}
return
}
if len(strings.TrimSpace(s.Text)) > 0 {
fmt.Println(s.Text)
}
}
}
}()
time.Sleep(300 * time.Millisecond)
go func() {
if err != nil {
if !strings.Contains(err.Error(), "No Major.Minor.Patch") {
status <- Status{Err: err, Done: true}
}
}
// Check if a change is needed
curVersion, curCpuarch := node.GetCurrentVersion()
if version == curVersion && cpuarch == curCpuarch {
fmt.Println("node v" + version + " (" + cpuarch + "-bit) is already in use.")
status <- Status{Done: true}
return
}
// Make sure the version is installed. If not, warn.
if !node.IsVersionInstalled(env.root, version, cpuarch) {
err = fmt.Errorf("node v%s (%v-bit) is not installed.", version, cpuarch)
if notifications {
status <- Status{Err: err, Done: true}
}
if (cpuarch == "32" && node.IsVersionInstalled(env.root, version, "64")) || (cpuarch == "64" && node.IsVersionInstalled(env.root, version, "32")) {
status <- Status{Err: fmt.Errorf("Did you mean node v%s (%v-bit)?\nIf so, type \"nvm use %s %v\" to use it.", version, cpuarch, version, cpuarch), Done: true}
}
status <- Status{Err: fmt.Errorf("Version not installed. Run \"nvm ls\" to see available versions."), Done: true}
}
// Remove symlink if it already exists
sym, _ := os.Lstat(env.symlink)
if sym != nil {
err = validSymlink(env.symlink)
if err != nil {
status <- Status{Err: err, Done: true}
}
// _, err := runElevated(fmt.Sprintf(`"%s" cmd /C rmdir "%s"`, filepath.Join(env.root, "elevate.cmd"), filepath.Clean(env.symlink)))
_, err := elevatedRun("rmdir", filepath.Clean(env.symlink))
if err != nil {
if accessDenied(err) {
status <- Status{Err: err, Done: true}
}
}
}
// Create new symlink
var ok bool
// ok, err = runElevated(fmt.Sprintf(`"%s" cmd /C mklink /D "%s" "%s"`, filepath.Join(env.root, "elevate.cmd"), filepath.Clean(env.symlink), filepath.Join(env.root, "v"+version)))
ok, err = elevatedRun("mklink", "/D", filepath.Clean(env.symlink), filepath.Join(env.root, "v"+version))
if err != nil {
if strings.Contains(err.Error(), "not have sufficient privilege") || strings.Contains(strings.ToLower(err.Error()), "access is denied") {
ok, err = elevatedRun("mklink", "/D", filepath.Clean(env.symlink), filepath.Join(env.root, "v"+version))
if err != nil {
ok = false
status <- Status{Err: err, Done: true}
// fmt.Println(fmt.Sprint(err)) // + ": " + _stderr.String())
} else {
ok = true
}
} else if strings.Contains(err.Error(), "file already exists") {
err = validSymlink(env.symlink)
if err != nil {
status <- Status{Err: err, Done: true}
}
ok, err = elevatedRun("rmdir", filepath.Clean(env.symlink))
// ok, err = runElevated(fmt.Sprintf(`"%s" cmd /C rmdir "%s"`, filepath.Join(env.root, "elevate.cmd"), filepath.Clean(env.symlink)))
reloadable := true
if len(reload) > 0 {
reloadable = reload[0]
}
if err != nil {
status <- Status{Err: err, Done: true}
} else if reloadable {
use(version, cpuarch, false)
return
}
} else {
status <- Status{Err: err, Done: true}
}
}
if !ok {
status <- Status{Err: fmt.Errorf("failed to elevate permissions to create symlink"), Done: true}
}
// Use the assigned CPU architecture
cpuarch = arch.Validate(cpuarch)
nodepath := filepath.Join(env.root, "v"+version, "node.exe")
node32path := filepath.Join(env.root, "v"+version, "node32.exe")
node64path := filepath.Join(env.root, "v"+version, "node64.exe")
node32exists := file.Exists(node32path)
node64exists := file.Exists(node64path)
nodeexists := file.Exists(nodepath)
if node32exists && cpuarch == "32" { // user wants 32, but node.exe is 64
if nodeexists {
utility.Rename(nodepath, node64path) // node.exe -> node64.exe
}
utility.Rename(node32path, nodepath) // node32.exe -> node.exe
}
if node64exists && cpuarch == "64" { // user wants 64, but node.exe is 32
if nodeexists {
utility.Rename(nodepath, node32path) // node.exe -> node32.exe
}
utility.Rename(node64path, nodepath) // node64.exe -> node.exe
}
status <- Status{Done: true}
}()
// Wait for the process to complete before exiting
wg.Wait()
os.Exit(exitCode)
}
func abortOnBadSymlink(symlinkpath string) {
if err := validSymlink(symlinkpath); err != nil {
fmt.Printf("%v\n", err)
os.Exit(1)
}
}
func validSymlink(symlinkpath string) error {
symlinkpath = filepath.Clean(symlinkpath)
// Prevent deletion if the symlink has been set to a physical directpry/file.
// This isn't supposed to ever happen, but users have manually changed the settings.txt,
// removing the physical file/directory unintentionally.
// This is an anti-footgun.
if symlink, err := isSymlink(symlinkpath); !symlink && err == nil {
return fmt.Errorf("NVM_SYMLINK is set to a physical file/directory at %s\nPlease remove the location and try again, or select a different location for NVM_SYMLINK.\n", env.symlink)
}
return nil
}
func useArchitecture(a string) {
if strings.ContainsAny("32", os.Getenv("PROCESSOR_ARCHITECTURE")) {
fmt.Println("This computer only supports 32-bit processing.")
return
}
if strings.Contains("arm64", strings.ToLower(os.Getenv("PROCESSOR_ARCHITECTURE"))) {
fmt.Println("This computer only supports arm64-bit processing.")
return
}
if a == "32" || a == "64" {
env.arch = a
saveSettings()
fmt.Println("Set to " + a + "-bit mode")
} else {
fmt.Println("Cannot set architecture to " + a + ". Must be 32 or 64 are acceptable values.")
}
}
func list(listtype string) {
if listtype == "" {
listtype = "installed"
}
if listtype != "installed" && listtype != "available" {
fmt.Println("\nInvalid list option.\n\nPlease use on of the following\n - nvm list\n - nvm list installed\n - nvm list available")
help()
return
}
if listtype == "installed" {
fmt.Println("")
inuse, a := node.GetCurrentVersion()
v := node.GetInstalled(env.root)
for i := 0; i < len(v); i++ {
version := v[i]
isnode, _ := regexp.MatchString("v", version)
str := ""
if isnode {
if "v"+inuse == version {
str = str + " * "
} else {
str = str + " "
}
str = str + regexp.MustCompile("v").ReplaceAllString(version, "")
if "v"+inuse == version {
str = str + " (Currently using " + a + "-bit executable)"
// str = ansi.Color(str,"green:black")
}
fmt.Printf(str + "\n")
}
}
if len(v) == 0 {
fmt.Println("No installations recognized.")
}
} else {
_, lts, current, stable, unstable, _ := node.GetAvailable()
releases := 20
data := make([][]string, releases, releases+5)
for i := 0; i < releases; i++ {
release := make([]string, 4, 6)
release[0] = ""
release[1] = ""
release[2] = ""
release[3] = ""
if len(current) > i {
if len(current[i]) > 0 {
release[0] = current[i]
}
}
if len(lts) > i {
if len(lts[i]) > 0 {
release[1] = lts[i]
}
}
if len(stable) > i {
if len(stable[i]) > 0 {
release[2] = stable[i]
}
}
if len(unstable) > i {
if len(unstable[i]) > 0 {
release[3] = unstable[i]
}
}
data[i] = release
}
fmt.Println("")
table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{" Current ", " LTS ", " Old Stable ", "Old Unstable"})
table.SetBorders(tablewriter.Border{Left: true, Top: false, Right: true, Bottom: false})
table.SetAlignment(tablewriter.ALIGN_CENTER)
table.SetCenterSeparator("|")
table.AppendBulk(data) // Add Bulk Data
table.Render()
fmt.Println("\nThis is a partial list. For a complete list, visit https://nodejs.org/en/download/releases")
}
}
func enable() {
dir := ""
files, _ := ioutil.ReadDir(env.root)
for _, f := range files {
if f.IsDir() {
isnode, _ := regexp.MatchString("v", f.Name())
if isnode {
dir = f.Name()
}
}
}
fmt.Println("nvm enabled")
if dir != "" {
use(strings.Trim(regexp.MustCompile("v").ReplaceAllString(dir, ""), " \n\r"), env.arch)
} else {
fmt.Println("No versions of node.js found. Try installing the latest by typing nvm install latest")
}
}
func disable() {
// ok, err := runElevated(fmt.Sprintf(`"%s" cmd /C rmdir "%s"`, filepath.Join(env.root, "elevate.cmd"), filepath.Clean(env.symlink)))
abortOnBadSymlink(env.symlink)
ok, err := elevatedRun("rmdir", filepath.Clean(env.symlink))
if !ok {
return
}
if err != nil {
fmt.Print(fmt.Sprint(err))
}
fmt.Println("nvm disabled")
}
const (
VER_PLATFORM_WIN32s = 0
VER_PLATFORM_WIN32_WINDOWS = 1
VER_PLATFORM_WIN32_NT = 2
)
type OSVersionInfoEx struct {
OSVersionInfoSize uint32
MajorVersion uint32
MinorVersion uint32
BuildNumber uint32
PlatformId uint32
CSDVersion [128]uint16
}
func checkLocalEnvironment() {
problems := make([]string, 0)
// Check for PATH problems
paths := strings.Split(os.Getenv("PATH"), ";")
current := env.symlink
if strings.HasSuffix(current, "/") || strings.HasSuffix(current, "\\") {
current = current[:len(current)-1]
}
nvmsymlinkfound := false
for _, path := range paths {
if strings.HasSuffix(path, "/") || strings.HasSuffix(path, "\\") {
path = path[:len(path)-1]
}
if strings.EqualFold(path, current) {
nvmsymlinkfound = true
break
}
if _, err := os.Stat(filepath.Join(path, "node.exe")); err == nil {
problems = append(problems, "Another Node.js installation is blocking NVM4W installations from running. Please uninstall the conflicting version or update the PATH environment variable to assure \""+current+"\" precedes \""+path+"\".")
break
} else if !errors.Is(err, os.ErrNotExist) {
fmt.Println("Error running environment check:\n" + err.Error())
}
}
// Check for developer mode
devmode := "OFF"
k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock`, registry.QUERY_VALUE)
if err == nil {
value, _, err := k.GetIntegerValue("AllowDevelopmentWithoutDevLicense")
if err == nil {
if value > 0 {
devmode = "ON"
}
} else {
devmode = "UNKNOWN"
}
} else {
devmode = "UNKNOWN (user cannot read registry)"
}
defer k.Close()
// Check for permission problems
admin, elevated, err := getProcessPermissions()
if err == nil {
if !admin && !elevated {
user, _ := user.Current()
username := strings.Split(user.Username, "\\")
fmt.Printf("%v is not using admin or elevated rights", username[len(username)-1])
if devmode == "ON" {
fmt.Printf(", but windows developer mode is\nenabled. Most commands will still work unless %v lacks rights to\nmodify the %v symlink.\n", username[len(username)-1], current)
} else {
fmt.Println(".")
}
} else {
if admin {
fmt.Println("Running NVM for Windows with administrator privileges.")
} else if elevated {
fmt.Println("Running NVM for Windows with elevated permissions.")
}
}
} else {
fmt.Println(err)
}
kernel32 := syscall.NewLazyDLL("kernel32.dll")
handle, _, err := kernel32.NewProc("GetStdHandle").Call(uintptr(0xfffffff5)) // get handle for console input
if err != nil && err.Error() != "The operation completed successfully." {
fmt.Printf("Error getting console handle: %v", err)
} else {
var mode uint32
result, _, _ := kernel32.NewProc("GetConsoleMode").Call(handle, uintptr(unsafe.Pointer(&mode)))
if result != 0 {
var title [256]uint16
_, _, err := kernel32.NewProc("GetConsoleTitleW").Call(uintptr(unsafe.Pointer(&title)), uintptr(len(title)))
if err != nil && err.Error() != "The operation completed successfully." {
fmt.Printf("Error getting console title: %v", err)
} else {
consoleTitle := syscall.UTF16ToString(title[:])
if !strings.Contains(strings.ToLower(consoleTitle), "command prompt") && !strings.Contains(strings.ToLower(consoleTitle), "powershell") && !strings.Contains(strings.ToLower(consoleTitle), "cmd.exe") && !strings.Contains(strings.ToLower(consoleTitle), "pwsh.exe") && !strings.Contains(strings.ToLower(consoleTitle), "powershell.exe") {
problems = append(problems, fmt.Sprintf("\"%v\" not recognized: the Command Prompt and Powershell are the only officially supported consoles. Some features may not work as expected.\n", consoleTitle))
}
}
}
}
// Get Windows details
getVersionEx := kernel32.NewProc("GetVersionExW")
versionInfo := OSVersionInfoEx{
OSVersionInfoSize: uint32(unsafe.Sizeof(OSVersionInfoEx{})),
}
ret, _, _ := getVersionEx.Call(uintptr(unsafe.Pointer(&versionInfo)))
if ret == 0 {
fmt.Println("Failed to retrieve version information.")
}
// fmt.Printf(" %d.%d\n", versionInfo.MajorVersion, versionInfo.MinorVersion)
maj, min, patch := windows.RtlGetNtVersionNumbers()
fmt.Printf("\nWindows Version: %d.%d (Build %d)\n", maj, min, patch)
// SHELL in Linux
// TERM in Windows
// COMSPEC in Windows provides the terminal path
// shell := os.Getenv("ConEmuANSI")
// fmt.Printf("Shell: %v\n", shell)
// Display developer mode status
if !admin {
// if devmode == "ON" {
// devmode = color.GreenString(devmode)
// } else if devmode == "OFF" {
// devmode = color.YellowString(devmode)
// } else {
// devmode = color.YellowString(devmode)
// }
fmt.Printf("\n%v %v\n", "Windows Developer Mode:", devmode)
}
executable := os.Args[0]
path, err := where.Find(filepath.Base(executable))
if err != nil {
path = "UNKNOWN: " + err.Error()
}
out := "none\n(run \"nvm use <version>\" to activate a version)\n"
output, err := exec.Command(os.Getenv("NVM_SYMLINK")+"\\node.exe", "-v").Output()
if err == nil {
out = string(output)
}
v := node.GetInstalled(env.root)
// Make sure author-nvm.exe is available and runs
exe, _ := os.Executable()
valueBytes, err := exec.Command(exe, "author", "version").Output()
authorNvmVersion := "Not Detected"
if err != nil {
// fmt.Println("Error running author-nvm.exe: " + err.Error())
problems = append(problems, "The author-nvm.exe file is missing or not executable.")
} else {
authorNvmVersion = strings.TrimSpace(string(valueBytes))
}
nvmhome := os.Getenv("NVM_HOME")
mirrors := "No mirrors configured"
if len(env.node_mirror) > 0 && len(env.npm_mirror) > 0 {
mirrors = env.node_mirror + " (node) and " + env.npm_mirror + " (npm)"
} else if len(env.node_mirror) > 0 {
mirrors = env.node_mirror + " (node)"
} else if len(env.npm_mirror) > 0 {
mirrors = env.npm_mirror + " (npm)"
}
fmt.Printf("\nNVM4W Version: %v\nNVM4W Author Bridge: %v\nNVM4W Path: %v\nNVM4W Settings: %v\nNVM_HOME: %v\nNVM_SYMLINK: %v\nNode Installations: %v\nDefault Architecture: %v-bit\nMirrors: %v\nHTTP Proxy: %v\n\nTotal Node.js Versions: %v\nActive Node.js Version: %v", NvmVersion, authorNvmVersion, path, home, nvmhome, symlink, env.root, env.arch, mirrors, env.proxy, len(v), out)
if !nvmsymlinkfound {
problems = append(problems, "The NVM4W symlink ("+env.symlink+") was not found in the PATH environment variable.")
}
if home == symlink {
problems = append(problems, "NVM_HOME and NVM_SYMLINK cannot be the same value ("+symlink+"). Change NVM_SYMLINK.")
}
fileInfo, err := os.Lstat(symlink)
if err != nil {
if os.IsNotExist(err) {
fmt.Println("NVM_SYMLINK does not exist yet. This is auto-created when \"nvm use\" is run.")
} else {
problems = append(problems, "Could not determine if NVM_SYMLINK is actually a symlink: "+err.Error())
}
} else {
if fileInfo.Mode()&os.ModeSymlink != 0 {
targetPath, err := os.Readlink(symlink)
if err != nil {
problems = append(problems, fmt.Sprintf("SYMLINK_READ Error: %v", err))
} else {
targetFileInfo, err := os.Lstat(targetPath)
if err != nil {
problems = append(problems, fmt.Sprintf("SYMLINK_READ Error: %v", err))
} else if !targetFileInfo.Mode().IsDir() {
problems = append(problems, "NVM_SYMLINK is a symlink linking to a file instead of a directory.")
}
}
} else {
problems = append(problems, "NVM_SYMLINK ("+symlink+") is not a valid symlink.")
}
}
if strings.Contains(symlink, home) {
problems = append(problems, "Storing the NVM_SYMLINK ("+symlink+") within the NVM_HOME directory ("+home+") has been known to cause problems in many Windows environments. Change NVM_SYMLINK to a different directory that does not already exist.")
}
ipv6, err := web.IsLocalIPv6()
if err != nil {
problems = append(problems, "Connection type cannot be determined: "+err.Error())
} else if !ipv6 {
fmt.Println("\nIPv6 is enabled. This has been known to slow downloads significantly.")
}
nodelist := web.Ping(web.GetFullNodeUrl("index.json"))
if !nodelist {
if len(env.node_mirror) > 0 && env.node_mirror != "none" {
problems = append(problems, "Connection to "+env.node_mirror+" (mirror) cannot be established. Check the mirror server to assure it is online.")
} else {
if len(env.proxy) > 0 {
problems = append(problems, "Connection to nodejs.org cannot be established. Check your proxy ("+env.proxy+") and your physical internet connection.")
} else {
problems = append(problems, "Connection to nodejs.org cannot be established. Check your internet connection.")
}
}
}
invalid := make([]string, 0)
invalidnpm := make([]string, 0)
for i := 0; i < len(v); i++ {
if _, err = os.Stat(filepath.Join(env.root, v[i], "node.exe")); err != nil {
invalid = append(invalid, v[i])
} else if _, err = os.Stat(filepath.Join(env.root, v[i], "npm.cmd")); err != nil {
fmt.Println(err)
invalidnpm = append(invalid, v[i])
}
}
if len(invalid) > 0 {
problems = append(problems, "The following Node installations are invalid (missing node.exe): "+strings.Join(invalid, ", ")+" - consider reinstalling these versions")
}
if len(invalidnpm) > 0 {
fmt.Printf("\nWARNING: The following Node installations are missing npm: %v\n (Node will still run, but npm will not work on these versions)\n", strings.Join(invalidnpm, ", "))
}
if len(env.npm_mirror) > 0 {
fmt.Println("If you are experiencing npm problems, check the npm mirror (" + env.npm_mirror + ") to assure it is online and accessible.")
}
if _, err := os.Stat(env.settings); err != nil {
problems = append(problems, "Cannot find "+env.settings)
}
if len(problems) == 0 {
fmt.Println("\n" + "No problems detected.")
} else {
fmt.Println("\nPROBLEMS DETECTED\n-----------------")
for _, p := range problems {
fmt.Println(p + "\n")
}
}
// Check for updates
colorize := true
if err := upgrade.EnableVirtualTerminalProcessing(); err != nil {
colorize = false
}
update, checkerr := upgrade.Get()
if checkerr == nil {
if len(update.Warnings) > 0 {
fmt.Println("")
}
for _, warning := range update.Warnings {
upgrade.Warn(warning, colorize)
}
for _, warning := range update.VersionWarnings {
upgrade.Warn(warning, colorize)
}
if len(update.Warnings) > 0 || len(update.VersionWarnings) > 0 {
fmt.Println("")
}
}
if checkerr != nil {
fmt.Println("error checking for updates: " + checkerr.Error())
} else {
newVersion, available, err := update.Available(NvmVersion)
if err != nil {
fmt.Println("Error checking for updates: " + err.Error())
} else if available {
upgrade.Warn(fmt.Sprintf("An upgrade is available: v%s", newVersion), colorize)
fmt.Println(" run \"nvm upgrade\" to update.\n")
}
}
fmt.Println("\n" + "Find help at https://github.com/coreybutler/nvm-windows/wiki/Common-Issues")
}
func help() {
fmt.Println("\nRunning version " + NvmVersion + ".")
fmt.Println("\nUsage:")
fmt.Println(" ")
fmt.Println(" nvm arch : Show if node is running in 32 or 64 bit mode.")
fmt.Println(" nvm current : Display active version.")
fmt.Println(" nvm debug : Check the NVM4W process for known problems (troubleshooter).")
fmt.Println(" nvm install <version> [arch] : The version can be a specific version, \"latest\" for the latest current version, or \"lts\" for the")
fmt.Println(" most recent LTS version. Optionally specify whether to install the 32 or 64 bit version (defaults")
fmt.Println(" to system arch). Set [arch] to \"all\" to install 32 AND 64 bit versions.")
fmt.Println(" Add --insecure to the end of this command to bypass SSL validation of the remote download server.")
fmt.Println(" nvm list [available] : List the node.js installations. Type \"available\" at the end to see what can be installed. Aliased as ls.")
fmt.Println(" nvm on : Enable node.js version management.")
fmt.Println(" nvm off : Disable node.js version management.")
fmt.Println(" nvm proxy [url] : Set a proxy to use for downloads. Leave [url] blank to see the current proxy.")
fmt.Println(" Set [url] to \"none\" to remove the proxy.")
fmt.Println(" nvm node_mirror [url] : Set the node mirror. Defaults to https://nodejs.org/dist/. Leave [url] blank to use default url.")
fmt.Println(" nvm npm_mirror [url] : Set the npm mirror. Defaults to https://github.com/npm/cli/archive/. Leave [url] blank to default url.")
fmt.Println(" nvm uninstall <version> : The version must be a specific version.")
fmt.Println(" nvm upgrade : Update nvm to the latest version. Manual rollback available for 7 days after upgrade.")
fmt.Println(" nvm use [version] [arch] : Switch to use the specified version. Optionally use \"latest\", \"lts\", or \"newest\".")
fmt.Println(" \"newest\" is the latest installed version. Optionally specify 32/64bit architecture.")
fmt.Println(" nvm use <arch> will continue using the selected version, but switch to 32/64 bit mode.")
fmt.Println(" nvm reinstall <version> : A shortcut method to clean and reinstall a specific version.")
fmt.Println(" nvm root [path] : Set the directory where nvm should store different versions of node.js.")
fmt.Println(" If <path> is not set, the current root will be displayed.")
fmt.Println(" nvm subscribe [--]<topic> : Subscribe to desktop notifications.")
fmt.Println(" Valid topics: lts, current, nvm4w, author")
fmt.Println(" nvm unsubscribe [--]<topic> : Unsubscribe from desktop notifications.")
fmt.Println(" Valid topics: lts, current, nvm4w, author")
fmt.Println(" nvm [--]version : Displays the current running version of nvm for Windows. Aliased as v.")
fmt.Println(" ")
}
// ===============================================================
// END | CLI functions
// ===============================================================
// ===============================================================
// BEGIN | Utility functions
// ===============================================================
func checkVersionExceedsLatest(version string) bool {
//content := web.GetRemoteTextFile("http://nodejs.org/dist/latest/SHASUMS256.txt")
url := web.GetFullNodeUrl("latest/SHASUMS256.txt")
content, err := web.GetRemoteTextFile(url)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
re := regexp.MustCompile("node-v(.+)+msi")
reg := regexp.MustCompile("node-v|-[xa].+")
latest := reg.ReplaceAllString(re.FindString(content), "")
var vArr = strings.Split(version, ".")
var lArr = strings.Split(latest, ".")
for index := range lArr {
lat, _ := strconv.Atoi(lArr[index])
ver, _ := strconv.Atoi(vArr[index])
//Should check for valid input (checking for conversion errors) but this tool is made to trust the user
if ver < lat {
return false
} else if ver > lat {
return true
}
}
return false
}
func cleanVersion(version string) string {
re := regexp.MustCompile("\\d+.\\d+.\\d+")
matched := re.FindString(version)
if len(matched) == 0 {
re = regexp.MustCompile("\\d+.\\d+")
matched = re.FindString(version)
if len(matched) == 0 {
matched = version + ".0.0"
} else {
matched = matched + ".0"
}
fmt.Println(matched)
}
return matched
}
// Given a node.js version, returns the associated npm version
func getNpmVersion(nodeversion string) string {
_, _, _, _, _, npm := node.GetAvailable()
if len(npm) == 0 {
fmt.Println("Error looking up versions: Remote host returned no results. This usually indicates a problem with with Node.js web server. Please try again in a few minutes.")
os.Exit(0)
}
return npm[nodeversion]
}
func getLatest() string {
url := web.GetFullNodeUrl("latest/SHASUMS256.txt")
content, err := web.GetRemoteTextFile(url)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
re := regexp.MustCompile("node-v(.+)+msi")
reg := regexp.MustCompile("node-v|-[xa].+")
return reg.ReplaceAllString(re.FindString(content), "")
}
func getLTS() string {
// all, ltsList, current, stable, unstable, npm := node.GetAvailable()
// fmt.Println(all)
// fmt.Println(ltsList)
// fmt.Println(current)
// fmt.Println(stable)
// fmt.Println(unstable)
// fmt.Println(npm)
_, ltsList, _, _, _, _ := node.GetAvailable()
if len(ltsList) == 0 {
fmt.Println("Error looking up LTS version: Remote host returned no results. This usually indicates a problem with with Node.js web server. Please try again in a few minutes.")
os.Exit(0)
}
// ltsList has already been numerically sorted
return ltsList[0]
}
func updateRootDir(path string) {
_, err := os.Stat(path)
if err != nil {
fmt.Println(path + " does not exist or could not be found.")
return
}
currentRoot := env.root
env.root = filepath.Clean(path)
// Copy command files
os.Link(filepath.Clean(currentRoot+"/elevate.cmd"), filepath.Clean(env.root+"/elevate.cmd"))
os.Link(filepath.Clean(currentRoot+"/elevate.vbs"), filepath.Clean(env.root+"/elevate.vbs"))
saveSettings()
if currentRoot != env.root {
fmt.Println("\nRoot has been changed from " + currentRoot + " to " + path)
}
}
func elevatedRun(name string, arg ...string) (bool, error) {
ok, err := run("cmd", nil, append([]string{"/C", name}, arg...)...)
if err != nil {
exe, _ := os.Executable()
cmd := filepath.Join(filepath.Dir(exe), "elevate.cmd")
ok, err = run(cmd, &env.root, append([]string{"cmd", "/C", name}, arg...)...)
}
return ok, err
}
func run(name string, dir *string, arg ...string) (bool, error) {
c := exec.Command(name, arg...)
if dir != nil {
c.Dir = *dir
}
var stderr bytes.Buffer
c.Stderr = &stderr
err := c.Run()
if err != nil {
return false, errors.New(fmt.Sprint(err) + ": " + stderr.String())
}
return true, nil
}
func runElevated(command string, forceUAC ...bool) (bool, error) {
uac := true //false
if len(forceUAC) > 0 {
uac = forceUAC[0]
}
if uac {
// Alternative elevation option at stackoverflow.com/questions/31558066/how-to-ask-for-administer-privileges-on-windows-with-go
cmd := exec.Command(filepath.Join(env.root, "elevate.cmd"), command)
var output bytes.Buffer
var _stderr bytes.Buffer
cmd.Stdout = &output
cmd.Stderr = &_stderr
perr := cmd.Run()
if perr != nil {
return false, errors.New(fmt.Sprint(perr) + ": " + _stderr.String())
}
}
c := exec.Command("cmd") // dummy executable that actually needs to exist but we'll overwrite using .SysProcAttr
// Based on the official docs, syscall.SysProcAttr.CmdLine doesn't exist.
// But it does and is vital:
// https://github.com/golang/go/issues/15566#issuecomment-333274825
// https://medium.com/@felixge/killing-a-child-process-and-all-of-its-children-in-go-54079af94773
c.SysProcAttr = &syscall.SysProcAttr{CmdLine: command}
var stderr bytes.Buffer
c.Stderr = &stderr
err := c.Run()
if err != nil {
msg := stderr.String()
if strings.Contains(msg, "not have sufficient privilege") && uac {
return runElevated(command, false)
}
// fmt.Println(fmt.Sprint(err) + ": " + stderr.String())
return false, errors.New(fmt.Sprint(err) + ": " + msg)
}
return true, nil
}
func saveSettings() {
content := "root: " + strings.Trim(encode(env.root), " \n\r") + "\r\narch: " + strings.Trim(encode(env.arch), " \n\r") + "\r\nproxy: " + strings.Trim(encode(env.proxy), " \n\r") + "\r\noriginalpath: " + strings.Trim(encode(env.originalpath), " \n\r") + "\r\noriginalversion: " + strings.Trim(encode(env.originalversion), " \n\r")
content = content + "\r\nnode_mirror: " + strings.Trim(encode(env.node_mirror), " \n\r") + "\r\nnpm_mirror: " + strings.Trim(encode(env.npm_mirror), " \n\r")
ioutil.WriteFile(env.settings, []byte(content), 0644)
os.Setenv("NVM_HOME", strings.Trim(encode(env.root), " \n\r"))
}
func getProcessPermissions() (admin bool, elevated bool, err error) {
admin = false
elevated = false
var sid *windows.SID
err = windows.AllocateAndInitializeSid(
&windows.SECURITY_NT_AUTHORITY,
2,
windows.SECURITY_BUILTIN_DOMAIN_RID,
windows.DOMAIN_ALIAS_RID_ADMINS,
0, 0, 0, 0, 0, 0,
&sid)
if err != nil {
return
}
defer windows.FreeSid(sid)
token := windows.Token(0)
elevated = token.IsElevated()
admin, err = token.IsMember(sid)
return
}
func encode(val string) string {
converted := encoding.ToUTF8(val)
// if err != nil {
// fmt.Printf("WARNING: [encoding error] - %v\n", err.Error())
// return val
// }
return string(converted)
}
// ===============================================================
// END | Utility functions
// ===============================================================
func setup() {
lines, err := file.ReadLines(env.settings)
if err != nil {
fmt.Println("\nERROR", err)
os.Exit(1)
}
// Process each line and extract the value
m := make(map[string]string)
for _, line := range lines {
line = strings.TrimSpace(line)
line = os.ExpandEnv(line)
res := strings.Split(line, ":")
if len(res) < 2 {
continue
}
m[res[0]] = strings.TrimSpace(strings.Join(res[1:], ":"))
}
if val, ok := m["root"]; ok {
env.root = filepath.Clean(val)
}
if val, ok := m["originalpath"]; ok {
env.originalpath = filepath.Clean(val)
}
if val, ok := m["originalversion"]; ok {
env.originalversion = val
}
if val, ok := m["arch"]; ok {
env.arch = val
}
if val, ok := m["node_mirror"]; ok {
env.node_mirror = val
}
if val, ok := m["npm_mirror"]; ok {
env.npm_mirror = val
}
if val, ok := m["proxy"]; ok {
if val != "none" && val != "" {
if strings.ToLower(val[0:4]) != "http" {
val = "http://" + val
}
res, err := url.Parse(val)
if err == nil {
web.SetProxy(res.String(), env.verifyssl)
env.proxy = res.String()
}
}
}
web.SetMirrors(env.node_mirror, env.npm_mirror)
env.arch = arch.Validate(env.arch)
// Make sure the directories exist
_, e := os.Stat(env.root)
if e != nil {
fmt.Println(env.root + " could not be found or does not exist. Exiting.")
return
}
}
================================================
FILE: src/semver/semver.go
================================================
/**
* Used under the MIT License.
* Semver courtesy Benedikt Lang (https://github.com/blang)
*/
package semver
import (
"errors"
"fmt"
"strconv"
"strings"
)
const (
numbers string = "0123456789"
alphas = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-"
alphanum = alphas + numbers
dot = "."
hyphen = "-"
plus = "+"
)
// Latest fully supported spec version
var SPEC_VERSION = Version{
Major: 2,
Minor: 0,
Patch: 0,
}
type Version struct {
Major uint64
Minor uint64
Patch uint64
Pre []*PRVersion
Build []string //No Precedence
}
// Version to string
func (v *Version) String() string {
versionArray := []string{
strconv.FormatUint(v.Major, 10),
dot,
strconv.FormatUint(v.Minor, 10),
dot,
strconv.FormatUint(v.Patch, 10),
}
if len(v.Pre) > 0 {
versionArray = append(versionArray, hyphen)
for i, pre := range v.Pre {
if i > 0 {
versionArray = append(versionArray, dot)
}
versionArray = append(versionArray, pre.String())
}
}
if len(v.Build) > 0 {
versionArray = append(versionArray, plus, strings.Join(v.Build, dot))
}
return strings.Join(versionArray, "")
}
// Checks if v is greater than o.
func (v *Version) GT(o *Version) bool {
return (v.Compare(o) == 1)
}
// Checks if v is greater than or equal to o.
func (v *Version) GTE(o *Version) bool {
return (v.Compare(o) >= 0)
}
// Checks if v is less than o.
func (v *Version) LT(o *Version) bool {
return (v.Compare(o) == -1)
}
// Checks if v is less than or equal to o.
func (v *Version) LTE(o *Version) bool {
return (v.Compare(o) <= 0)
}
// Compares Versions v to o:
// -1 == v is less than o
// 0 == v is equal to o
// 1 == v is greater than o
func (v *Version) Compare(o *Version) int {
if v.Major != o.Major {
if v.Major > o.Major {
return 1
} else {
return -1
}
}
if v.Minor != o.Minor {
if v.Minor > o.Minor {
return 1
} else {
return -1
}
}
if v.Patch != o.Patch {
if v.Patch > o.Patch {
return 1
} else {
return -1
}
}
// Quick comparison if a version has no prerelease versions
if len(v.Pre) == 0 && len(o.Pre) == 0 {
return 0
} else if len(v.Pre) == 0 && len(o.Pre) > 0 {
return 1
} else if len(v.Pre) > 0 && len(o.Pre) == 0 {
return -1
} else {
i := 0
for ; i < len(v.Pre) && i < len(o.Pre); i++ {
if comp := v.Pre[i].Compare(o.Pre[i]); comp == 0 {
continue
} else if comp == 1 {
return 1
} else {
return -1
}
}
// If all pr versions are the equal but one has further pr version, this one greater
if i == len(v.Pre) && i == len(o.Pre) {
return 0
} else if i == len(v.Pre) && i < len(o.Pre) {
return -1
} else {
return 1
}
}
}
// Validates v and returns error in case
func (v *Version) Validate() error {
// Major, Minor, Patch already validated using uint64
if len(v.Pre) > 0 {
for _, pre := range v.Pre {
if !pre.IsNum { //Numeric prerelease versions already uint64
if len(pre.VersionStr) == 0 {
return fmt.Errorf("Prerelease can not be empty %q", pre.VersionStr)
}
if !containsOnly(pre.VersionStr, alphanum) {
return fmt.Errorf("Invalid character(s) found in prerelease %q", pre.VersionStr)
}
}
}
}
if len(v.Build) > 0 {
for _, build := range v.Build {
if len(build) == 0 {
return fmt.Errorf("Build meta data can not be empty %q", build)
}
if !containsOnly(build, alphanum) {
return fmt.Errorf(
gitextract_df78uor0/
├── .gitattributes
├── .github/
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.yml
│ │ ├── config.yml
│ │ └── feaure_request.yml
│ ├── OLD_ISSUE_TEMPLATE
│ ├── dependabot.yml
│ └── workflows/
│ ├── autotag.yml
│ ├── bot.yml
│ ├── release.yml
│ ├── scanner.yml
│ └── winget.yml
├── .gitignore
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── SUPPORT.md
├── assets/
│ ├── buildtools/
│ │ ├── Default.isl
│ │ ├── ISPPBuiltins.iss
│ │ ├── Setup.e32
│ │ └── SetupLdr.e32
│ ├── elevate.cmd
│ ├── elevate.vbs
│ ├── install.cmd
│ ├── run.cmd
│ ├── setuserenv.vbs
│ └── unsetuserenv.vbs
├── build.js
├── examples/
│ └── settings.txt
├── nvm.iss
└── src/
├── arch/
│ └── arch.go
├── author/
│ └── bridge.go
├── encoding/
│ └── encoding.go
├── file/
│ └── file.go
├── go.mod
├── go.sum
├── manifest.json
├── node/
│ └── node.go
├── nvm.go
├── semver/
│ └── semver.go
├── upgrade/
│ ├── check.go
│ ├── notification.go
│ ├── register.go
│ └── upgrade.go
├── utility/
│ ├── logging.go
│ └── rename.go
└── web/
└── web.go
SYMBOL INDEX (185 symbols across 14 files)
FILE: src/arch/arch.go
function SearchBytesInFile (line 12) | func SearchBytesInFile( path string, match string, limit int) bool {
function Bit (line 49) | func Bit(path string) string {
function Validate (line 63) | func Validate(str string) (string){
FILE: src/author/bridge.go
constant SWP_NOMOVE (line 19) | SWP_NOMOVE = 0x0002
constant SWP_NOZORDER (line 20) | SWP_NOZORDER = 0x0004
constant SWP_SHOWWINDOW (line 21) | SWP_SHOWWINDOW = 0x0040
constant SW_HIDE (line 22) | SW_HIDE = 0
function hideConsole (line 33) | func hideConsole() {
function Bridge (line 40) | func Bridge(args ...string) {
FILE: src/encoding/encoding.go
function DetectCharset (line 10) | func DetectCharset(content []byte) (string, error) {
function ToUTF8 (line 20) | func ToUTF8(content string) []byte {
FILE: src/file/file.go
function Unzip (line 14) | func Unzip(src, dest string) error {
function ReadLines (line 62) | func ReadLines(path string) ([]string, error) {
function Exists (line 77) | func Exists(filename string) bool {
FILE: src/node/node.go
function GetCurrentVersion (line 22) | func GetCurrentVersion() (string, string) {
function IsVersionInstalled (line 51) | func IsVersionInstalled(root string, version string, cpu string) bool {
function IsVersionAvailable (line 76) | func IsVersionAvailable(v string) bool {
function reverseStringArray (line 88) | func reverseStringArray(str []string) []string {
function GetInstalled (line 97) | func GetInstalled(root string) []string {
type BySemanticVersion (line 128) | type BySemanticVersion
method Len (line 130) | func (s BySemanticVersion) Len() int {
method Swap (line 134) | func (s BySemanticVersion) Swap(i, j int) {
method Less (line 138) | func (s BySemanticVersion) Less(i, j int) bool {
function isLTS (line 145) | func isLTS(element map[string]interface{}) bool {
function isCurrent (line 156) | func isCurrent(element map[string]interface{}) bool {
function isStable (line 173) | func isStable(element map[string]interface{}) bool {
function isUnstable (line 188) | func isUnstable(element map[string]interface{}) bool {
function GetAvailable (line 203) | func GetAvailable() ([]string, []string, []string, []string, []string, m...
FILE: src/nvm.go
type Environment (line 46) | type Environment struct
function writeToErrorLog (line 75) | func writeToErrorLog(i interface{}, abort ...bool) {
type Notification (line 94) | type Notification struct
type Action (line 104) | type Action struct
function notify (line 110) | func notify(data Notification) {
function init (line 116) | func init() {
function main (line 186) | func main() {
function setNodeMirror (line 313) | func setNodeMirror(uri string) {
function setNpmMirror (line 318) | func setNpmMirror(uri string) {
function getVersion (line 323) | func getVersion(version string, cpuarch string, localInstallsOnly ...boo...
type Status (line 399) | type Status struct
function rollback (line 406) | func rollback(version string) error {
function install (line 420) | func install(version string, cpuarch string) {
function reinstall (line 846) | func reinstall(version, cpuarch string) {
function uninstall (line 898) | func uninstall(version string) {
function versionNumberFrom (line 948) | func versionNumberFrom(version string) string {
function splitVersion (line 981) | func splitVersion(version string) map[string]int {
function findLatestSubVersion (line 997) | func findLatestSubVersion(version string, localOnly ...bool) string {
function accessDenied (line 1059) | func accessDenied(err error) bool {
function isSymlink (line 1070) | func isSymlink(path string) (bool, error) {
function use (line 1078) | func use(version string, cpuarch string, reload ...bool) {
function abortOnBadSymlink (line 1263) | func abortOnBadSymlink(symlinkpath string) {
function validSymlink (line 1270) | func validSymlink(symlinkpath string) error {
function useArchitecture (line 1283) | func useArchitecture(a string) {
function list (line 1301) | func list(listtype string) {
function enable (line 1392) | func enable() {
function disable (line 1411) | func disable() {
constant VER_PLATFORM_WIN32s (line 1426) | VER_PLATFORM_WIN32s = 0
constant VER_PLATFORM_WIN32_WINDOWS (line 1427) | VER_PLATFORM_WIN32_WINDOWS = 1
constant VER_PLATFORM_WIN32_NT (line 1428) | VER_PLATFORM_WIN32_NT = 2
type OSVersionInfoEx (line 1431) | type OSVersionInfoEx struct
function checkLocalEnvironment (line 1440) | func checkLocalEnvironment() {
function help (line 1730) | func help() {
function checkVersionExceedsLatest (line 1771) | func checkVersionExceedsLatest(version string) bool {
function cleanVersion (line 1797) | func cleanVersion(version string) string {
function getNpmVersion (line 1816) | func getNpmVersion(nodeversion string) string {
function getLatest (line 1825) | func getLatest() string {
function getLTS (line 1837) | func getLTS() string {
function updateRootDir (line 1856) | func updateRootDir(path string) {
function elevatedRun (line 1877) | func elevatedRun(name string, arg ...string) (bool, error) {
function run (line 1888) | func run(name string, dir *string, arg ...string) (bool, error) {
function runElevated (line 1903) | func runElevated(command string, forceUAC ...bool) (bool, error) {
function saveSettings (line 1947) | func saveSettings() {
function getProcessPermissions (line 1954) | func getProcessPermissions() (admin bool, elevated bool, err error) {
function encode (line 1977) | func encode(val string) string {
function setup (line 1991) | func setup() {
FILE: src/semver/semver.go
constant numbers (line 15) | numbers string = "0123456789"
constant alphas (line 16) | alphas = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-"
constant alphanum (line 17) | alphanum = alphas + numbers
constant dot (line 18) | dot = "."
constant hyphen (line 19) | hyphen = "-"
constant plus (line 20) | plus = "+"
type Version (line 30) | type Version struct
method String (line 39) | func (v *Version) String() string {
method GT (line 63) | func (v *Version) GT(o *Version) bool {
method GTE (line 68) | func (v *Version) GTE(o *Version) bool {
method LT (line 73) | func (v *Version) LT(o *Version) bool {
method LTE (line 78) | func (v *Version) LTE(o *Version) bool {
method Compare (line 86) | func (v *Version) Compare(o *Version) int {
method Validate (line 142) | func (v *Version) Validate() error {
function New (line 173) | func New(s string) (*Version, error) {
function Parse (line 178) | func Parse(s string) (*Version, error) {
type PRVersion (line 287) | type PRVersion struct
method IsNumeric (line 321) | func (v *PRVersion) IsNumeric() bool {
method Compare (line 329) | func (v *PRVersion) Compare(o *PRVersion) int {
method String (line 354) | func (v *PRVersion) String() string {
function NewPRVersion (line 294) | func NewPRVersion(s string) (*PRVersion, error) {
function containsOnly (line 361) | func containsOnly(s string, set string) bool {
function hasLeadingZeroes (line 367) | func hasLeadingZeroes(s string) bool {
function NewBuildVersion (line 372) | func NewBuildVersion(s string) (string, error) {
FILE: src/upgrade/check.go
function Check (line 18) | func Check(root string, nvmversion string) {
function alertNvmRelease (line 95) | func alertNvmRelease(current, next *semver.Version, data map[string]inte...
function in (line 120) | func in(item string, set []string) bool {
function noupdate (line 129) | func noupdate() {
function UpgradeCompleteAlert (line 133) | func UpgradeCompleteAlert(version string) {
function alertRelease (line 156) | func alertRelease(data map[string]interface{}) error {
FILE: src/upgrade/notification.go
type LastNotification (line 10) | type LastNotification struct
method Path (line 34) | func (ln *LastNotification) Path() string {
method File (line 41) | func (ln *LastNotification) File() string {
method Save (line 45) | func (ln *LastNotification) Save() {
method LastLTS (line 53) | func (ln *LastNotification) LastLTS() time.Time {
method LastCurrent (line 62) | func (ln *LastNotification) LastCurrent() time.Time {
function LoadNotices (line 18) | func LoadNotices() *LastNotification {
FILE: src/upgrade/register.go
constant NODE_LTS_SCHEDULE_NAME (line 12) | NODE_LTS_SCHEDULE_NAME = "NVM for Windows Node.js LTS Update Check"
constant NODE_CURRENT_SCHEDULE_NAME (line 13) | NODE_CURRENT_SCHEDULE_NAME = "NVM for Windows Node.js Current Update Check"
constant NVM4W_SCHEDULE_NAME (line 14) | NVM4W_SCHEDULE_NAME = "NVM for Windows Update Check"
constant AUTHOR_SCHEDULE_NAME (line 15) | AUTHOR_SCHEDULE_NAME = "NVM for Windows Author Update Check"
type Registration (line 18) | type Registration struct
function LoadRegistration (line 25) | func LoadRegistration(args ...string) *Registration {
function abortOnError (line 50) | func abortOnError(err error) {
function logError (line 58) | func logError(err error) {
function Register (line 65) | func Register() {
function Unregister (line 83) | func Unregister() {
function ScheduleTask (line 111) | func ScheduleTask(name string, command string, interval string, startTim...
function UnscheduleTask (line 178) | func UnscheduleTask(name string) error {
FILE: src/upgrade/upgrade.go
constant UPDATE_URL (line 29) | UPDATE_URL = "https://api.github.com/repos/coreybutler/nvm-windows/relea...
constant ALERTS_URL (line 30) | ALERTS_URL = "https://author.io/nvm4w/feed/alerts"
constant yellow (line 32) | yellow = "\033[33m"
constant reset (line 33) | reset = "\033[0m"
constant ENABLE_VIRTUAL_TERMINAL_PROCESSING (line 36) | ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
constant FILE_ATTRIBUTE_HIDDEN (line 37) | FILE_ATTRIBUTE_HIDDEN = 0x2
constant CREATE_NEW_CONSOLE (line 38) | CREATE_NEW_CONSOLE = 0x00000010
constant DETACHED_PROCESS (line 39) | DETACHED_PROCESS = 0x00000008
constant warningIcon (line 41) | warningIcon = "⚠️"
type Notification (line 45) | type Notification struct
type Action (line 55) | type Action struct
function display (line 61) | func display(data Notification) {
type Update (line 67) | type Update struct
method Available (line 527) | func (u *Update) Available(sinceVersion string) (string, bool, error) {
type Release (line 75) | type Release struct
function Run (line 81) | func Run(version string) error {
function run (line 271) | func run(version string, status chan Status, updateMetadata ...*Update) ...
type Status (line 518) | type Status struct
function Warn (line 545) | func Warn(msg string, colorized ...bool) {
function Get (line 553) | func Get() (*Update, error) {
function autoupdate (line 557) | func autoupdate(status chan Status) {
function escapeBackslashes (line 691) | func escapeBackslashes(path string) string {
function tree (line 695) | func tree(dir string, title ...string) {
function get (line 707) | func get(url string, verbose ...bool) ([]byte, error) {
function checkForUpdate (line 734) | func checkForUpdate(url string) (*Update, error) {
function EnableVirtualTerminalProcessing (line 811) | func EnableVirtualTerminalProcessing() error {
function highlight (line 830) | func highlight(message string) string {
function unzip (line 835) | func unzip(src string, dest string) error {
function computeMD5Checksum (line 886) | func computeMD5Checksum(filePath string) (string, error) {
function readChecksumFromFile (line 904) | func readChecksumFromFile(checksumFile string) (string, error) {
function copyFile (line 920) | func copyFile(src, dst string) error {
function copyDirContents (line 956) | func copyDirContents(srcDir, dstDir string) error {
function zipDirectory (line 990) | func zipDirectory(sourceDir, outputZip string) error {
function setHidden (line 1058) | func setHidden(path string) error {
FILE: src/utility/logging.go
constant enableVirtualTerminalProcessing (line 18) | enableVirtualTerminalProcessing = 0x0004
constant BOLD (line 19) | BOLD = "\033[38;2;255;165;0m"
constant TEXT (line 20) | TEXT = "\033[38;2;255;200;100m"
constant RESET (line 21) | RESET = "\033[0m"
function enableANSI (line 24) | func enableANSI() {
function bold (line 43) | func bold(text string) string {
function text (line 47) | func text(txt string) string {
function EnableDebugLogs (line 51) | func EnableDebugLogs() {
function DebugLog (line 58) | func DebugLog(args ...interface{}) {
function DebugLogf (line 67) | func DebugLogf(tpl string, args ...interface{}) {
function DebugFn (line 74) | func DebugFn(fn func()) {
FILE: src/utility/rename.go
function Rename (line 10) | func Rename(old, new string) error {
function copyFile (line 48) | func copyFile(old, new string) error {
function copyDir (line 87) | func copyDir(old, new string) error {
FILE: src/web/web.go
function SetProxy (line 36) | func SetProxy(p string, verifyssl bool) {
function SetMirrors (line 45) | func SetMirrors(node_mirror string, npm_mirror string) {
function GetFullNodeUrl (line 66) | func GetFullNodeUrl(path string) string {
function GetFullNpmUrl (line 70) | func GetFullNpmUrl(path string) string {
function IsLocalIPv6 (line 74) | func IsLocalIPv6() (bool, error) {
function Ping (line 102) | func Ping(url string) bool {
function Download (line 123) | func Download(url string, target string, version string) bool {
function GetNodeJS (line 218) | func GetNodeJS(root string, v string, a string, append bool) bool {
function GetNpm (line 321) | func GetNpm(root string, v string) bool {
function GetRemoteTextFile (line 351) | func GetRemoteTextFile(url string) (string, error) {
function IsNode64bitAvailable (line 371) | func IsNode64bitAvailable(v string) bool {
function IsNodeArm64bitAvailable (line 388) | func IsNodeArm64bitAvailable(v string) bool {
function getNodeUrl (line 409) | func getNodeUrl(v string, vpre string, arch string, append bool) string {
function unzip (line 444) | func unzip(src, dest string) error {
Condensed preview — 47 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (280K chars).
[
{
"path": ".gitattributes",
"chars": 53,
"preview": "* text eol=lf\n*.exe binary\n*.dll binary\n*.e32 binary\n"
},
{
"path": ".github/FUNDING.yml",
"chars": 595,
"preview": "# These are supported funding model platforms\n\ngithub: [coreybutler]\npatreon: # coreybutler\nopen_collective: # Replace w"
},
{
"path": ".github/ISSUE_TEMPLATE/bug_report.yml",
"chars": 6900,
"preview": "name: \"🕷️ Bug Report\"\ndescription: Report errors or unexpected behavior\ntitle: \"[Issue]: \"\nlabels:\n - \"needs review\"\nbo"
},
{
"path": ".github/ISSUE_TEMPLATE/config.yml",
"chars": 191,
"preview": "blank_issues_enabled: false\ncontact_links:\n - name: \"\\u003F General Question\"\n url: https://github.com/coreybutler/n"
},
{
"path": ".github/ISSUE_TEMPLATE/feaure_request.yml",
"chars": 1899,
"preview": "name: \"⭐ Feature or enhancement request\"\ntitle: \"[Feature]: \"\ndescription: Propose something new.\nlabels:\n - \"needs rev"
},
{
"path": ".github/OLD_ISSUE_TEMPLATE",
"chars": 2286,
"preview": "<!-- \n\n👋 WELCOME\n \n===============================================================================\nPLEASE, pretty please"
},
{
"path": ".github/dependabot.yml",
"chars": 117,
"preview": "version: 2\nupdates:\n - package-ecosystem: \"github-actions\"\n directory: \"/\"\n schedule:\n interval: \"daily\"\n"
},
{
"path": ".github/workflows/autotag.yml",
"chars": 1457,
"preview": "name: Autotag\n\non:\n push:\n branches:\n - main\n - master\n\njobs:\n autotag:\n runs-on: ubuntu-latest\n\n o"
},
{
"path": ".github/workflows/bot.yml",
"chars": 845,
"preview": "name: 'Close stale issues and PR'\non:\n schedule:\n - cron: '30 1 * * *'\n\njobs:\n stale:\n runs-on: ubuntu-latest\n "
},
{
"path": ".github/workflows/release.yml",
"chars": 15604,
"preview": "name: Release\n\non:\n workflow_run:\n workflows: [\"Autotag\"]\n types:\n - completed\n\njobs:\n build:\n runs-on: "
},
{
"path": ".github/workflows/scanner.yml",
"chars": 2743,
"preview": "name: Virus Scan\n\non:\n workflow_run:\n workflows: [\"Release\"]\n types:\n - completed\n\njobs:\n scan:\n if: ${{"
},
{
"path": ".github/workflows/winget.yml",
"chars": 377,
"preview": "name: Publish to Winget\n\non:\n release:\n types: [released]\n\njobs:\n publish:\n runs-on: ubuntu-latest\n steps:\n "
},
{
"path": ".gitignore",
"chars": 451,
"preview": "# Compiled Object files, Static and Dynamic libs (Shared Objects)\n*.o\n*.a\n*.so\n*.old\n\n# Folders\n_obj\n_test\nbin\n\n# Archit"
},
{
"path": "CONTRIBUTING.md",
"chars": 564,
"preview": "## Before you submit an issue....\n\nThere have been a lot of duplicate issues filed. Please do a simple search for your i"
},
{
"path": "LICENSE",
"chars": 1125,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2025 Author Software Inc.\nCopyright (c) 2022 Ecor Ventures LLC.\n\nPermission is here"
},
{
"path": "README.md",
"chars": 20566,
"preview": "<div align=\"center\"><h2>We are working full time on Author, including <a href=\"https://github.com/coreybutler/nvm-window"
},
{
"path": "SUPPORT.md",
"chars": 808,
"preview": "# Need Help?\n\nIf you're running into problems using NVM4W, try the following resources.\n\n1. Review the [wiki](https://gi"
},
{
"path": "assets/buildtools/Default.isl",
"chars": 20279,
"preview": "; *** Inno Setup version 6.1.0+ English messages ***\n;\n; To download user-contributed translations of this file, go to:"
},
{
"path": "assets/buildtools/ISPPBuiltins.iss",
"chars": 9839,
"preview": "// Inno Setup Preprocessor\n//\n// Inno Setup (C) 1997-2020 Jordan Russell. All Rights Reserved.\n// Portions Copyright (C)"
},
{
"path": "assets/elevate.cmd",
"chars": 305,
"preview": "@setlocal\n@echo off\n\n:: Try without elevation, in case %NVM_SYMLINK% is a user-owned path and the \n:: machine has Window"
},
{
"path": "assets/elevate.vbs",
"chars": 335,
"preview": "Set Shell = CreateObject(\"Shell.Application\")\nSet WShell = WScript.CreateObject(\"WScript.Shell\")\nSet ProcEnv = WShell.En"
},
{
"path": "assets/install.cmd",
"chars": 724,
"preview": "@echo off\nset /P NVM_PATH=\"Enter the absolute path where the nvm-windows zip file is extracted/copied to: \"\nset NVM_HOME"
},
{
"path": "assets/run.cmd",
"chars": 218,
"preview": "@echo off\npowershell -NoProfile -WindowStyle Hidden -Command \"Start-Process -Filepath 'C:\\Users\\corey\\OneDrive\\Documents"
},
{
"path": "assets/setuserenv.vbs",
"chars": 384,
"preview": "Set WSHShell = CreateObject(\"WScript.Shell\")\nuserpath = WSHShell.RegRead(\"HKCU\\Environment\\Path\")\nIf InStr(userpath, \"%N"
},
{
"path": "assets/unsetuserenv.vbs",
"chars": 286,
"preview": "Set WSHShell = CreateObject(\"WScript.Shell\")\nuserpath = WSHShell.RegRead(\"HKCU\\Environment\\Path\")\nuserpath = Replace(use"
},
{
"path": "build.js",
"chars": 1259,
"preview": "// This is a deno helper script to locally build the installer using Inno Setup\n// Yeah, I know it's not Node, but we n"
},
{
"path": "examples/settings.txt",
"chars": 92,
"preview": "root: C:\\Users\\Corey\\AppData\\Roaming\\nvm\npath: C:\\Program Files\\nodejs\narch: 64\nproxy: none\n"
},
{
"path": "nvm.iss",
"chars": 19692,
"preview": "#define MyAppName \"NVM for Windows\"\n#define MyAppShortName \"nvm\"\n#define MyAppLCShortName \"nvm\"\n#define MyAppVersion \"{{"
},
{
"path": "src/arch/arch.go",
"chars": 1394,
"preview": "package arch\n\nimport (\n\t//\"regexp\"\n\t\"os\"\n\t//\"os/exec\"\n\t\"strings\"\n\t//\"fmt\"\n\t\"encoding/hex\"\n)\n\nfunc SearchBytesInFile( pat"
},
{
"path": "src/author/bridge.go",
"chars": 2389,
"preview": "package author\n\nimport (\n\t\"bufio\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/c"
},
{
"path": "src/encoding/encoding.go",
"chars": 1415,
"preview": "package encoding\n\nimport (\n\t\"strings\"\n\t\"unicode/utf8\"\n\n\t\"github.com/saintfish/chardet\"\n)\n\nfunc DetectCharset(content []b"
},
{
"path": "src/file/file.go",
"chars": 1490,
"preview": "package file\n\nimport (\n\t\"archive/zip\"\n\t\"bufio\"\n\t\"io\"\n\t\"log\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n)\n\n// Function courtesy ht"
},
{
"path": "src/go.mod",
"chars": 859,
"preview": "module nvm\n\ngo 1.18\n\nrequire (\n\tgithub.com/blang/semver v3.5.1+incompatible\n\tgithub.com/coreybutler/go-fsutil v1.2.0\n\tgi"
},
{
"path": "src/go.sum",
"chars": 5754,
"preview": "github.com/akavel/rsrc v0.10.2 h1:Zxm8V5eI1hW4gGaYsJQUhxpjkENuG91ki8B4zCrvEsw=\ngithub.com/akavel/rsrc v0.10.2/go.mod h1:"
},
{
"path": "src/manifest.json",
"chars": 681,
"preview": "{\n \"name\": \"nvm\",\n \"version\": \"1.2.2\",\n \"description\": \"Node.js version manager for Windows\",\n \"license\": \"SEE LICEN"
},
{
"path": "src/node/node.go",
"chars": 5653,
"preview": "package node\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"nvm/arch\"\n\t\"nvm/file\"\n\t\"nvm/web\"\n\t\"os\"\n\t\"os/exec\"\n\t\"regexp"
},
{
"path": "src/nvm.go",
"chars": 62098,
"preview": "package main\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"net/url\"\n\t\"os\"\n\t\"os/exec\"\n\t\"os/signal\"\n"
},
{
"path": "src/semver/semver.go",
"chars": 8953,
"preview": "/**\n * Used under the MIT License.\n * Semver courtesy Benedikt Lang (https://github.com/blang)\n */\npackage semver\n\nimpor"
},
{
"path": "src/upgrade/check.go",
"chars": 5003,
"preview": "package upgrade\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"nvm/node\"\n\t\"nvm/semver\"\n\t\"nvm/web\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\""
},
{
"path": "src/upgrade/notification.go",
"chars": 1358,
"preview": "package upgrade\n\nimport (\n\t\"encoding/json\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"time\"\n)\n\ntype LastNotification struct {\n\toutpath str"
},
{
"path": "src/upgrade/register.go",
"chars": 5530,
"preview": "package upgrade\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"strings\"\n)\n\nconst (\n\tNODE_LTS_SCHEDULE_NAME = \""
},
{
"path": "src/upgrade/upgrade.go",
"chars": 27488,
"preview": "package upgrade\n\nimport (\n\t\"archive/zip\"\n\t\"crypto/md5\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"nvm/author\"\n\t\"nvm/sem"
},
{
"path": "src/utility/logging.go",
"chars": 1891,
"preview": "package utility\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strings\"\n\t\"syscall\"\n)\n\nvar debug bool = false\nvar e"
},
{
"path": "src/utility/rename.go",
"chars": 2644,
"preview": "package utility\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n)\n\nfunc Rename(old, new string) error {\n\told_drive := file"
},
{
"path": "src/web/web.go",
"chars": 11833,
"preview": "package web\n\nimport (\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"nvm/arch\"\n\t\"nvm/file\"\n\t\"os"
}
]
// ... and 2 more files (download for full content)
About this extraction
This page contains the full source code of the coreybutler/nvm-windows GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 47 files (250.4 KB), approximately 72.5k tokens, and a symbol index with 185 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.