Full Code of coreybutler/nvm-windows for AI

master 5b18223ca19f cached
47 files
250.4 KB
72.5k tokens
185 symbols
1 requests
Download .txt
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&#39;s the big difference?](#bulb-whats-the-big-difference).
</details>

[![Download Latest Now](https://img.shields.io/badge/-Download%20Now!-%2322A6F2)](https://github.com/coreybutler/nvm-windows/releases) [![GitHub tag (latest SemVer)](https://img.shields.io/github/v/tag/coreybutler/nvm-windows?label=Latest%20Release&style=social&x=1)]((https://github.com/coreybutler/nvm-windows/releases)) ![GitHub Release Date](https://img.shields.io/github/release-date/coreybutler/nvm-windows?label=Released&style=social) ![GitHub all releases](https://img.shields.io/github/downloads/coreybutler/nvm-windows/total?label=Downloads&style=social) [![Discuss](https://img.shields.io/badge/-Discuss-blue)](https://github.com/coreybutler/nvm-windows/discussions) [![Twitter URL](https://img.shields.io/twitter/url?style=social&url=https%3A%2F%2Ftwitter.com%2Fintent%2Ftweet%3Fhashtags%3Dnodejs%26original_referer%3Dhttp%253A%252F%252F127.0.0.1%253A91%252F%26text%3DCheck%2520out%2520NVM%2520for%2520Windows%21%26tw_p%3Dtweetbutton%26url%3Dhttp%253A%252F%252Fgithub.com%252Fcoreybutler%252Fnvm-windows%26via%3Dgoldglovecb)](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/>
[![Download Stable](https://img.shields.io/badge/-Download%20Stable-%2322A6F2)](https://github.com/coreybutler/nvm-windows/releases/tag/1.1.12) [![GitHub tag (stable SemVer)](https://img.shields.io/badge/Stable%20Version-v1.1.12-1?style=social)]((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>
        &nbsp;<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`.

![NVM for Windows](https://github.com/coreybutler/staticassets/raw/master/images/nvm-1.1.8-screenshot.jpg)

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.

![Switch between stable and unstable versions.](https://github.com/coreybutler/staticassets/raw/master/images/nvm-usage-highlighted.jpg)

### 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)._

![NVM for Windows Installer](https://github.com/coreybutler/staticassets/raw/master/images/nvm-installer.jpg)

#### 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/>

![Contributors](https://contrib.rocks/image?repo=coreybutler/nvm-windows)


================================================
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(
Download .txt
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
Download .txt
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.

Copied to clipboard!